1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

New FileSink channel plugin

This commit is contained in:
f4exb 2020-08-06 10:46:49 +02:00
parent d80d050992
commit 6bfc6bacee
26 changed files with 3752 additions and 30 deletions

BIN
doc/img/FileSink_plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
doc/img/FileSink_plugin.xcf Normal file

Binary file not shown.

View File

@ -0,0 +1,61 @@
project(filesink)
set(filesink_SOURCES
filesink.cpp
filesinkbaseband.cpp
filesinksink.cpp
filesinksettings.cpp
filesinkmessages.cpp
filesinkwebapiadapter.cpp
filesinkplugin.cpp
)
set(filesink_HEADERS
filesink.h
filesinkbaseband.h
filesinksink.h
filesinksettings.h
filesinkmessages.h
filesinkwebapiadapter.h
filesinkplugin.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(filesink_SOURCES
${filesink_SOURCES}
filesinkgui.cpp
filesinkgui.ui
)
set(filesink_HEADERS
${filesink_HEADERS}
filesinkgui.h
)
set(TARGET_NAME filesink)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME filesinksrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${filesink_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,607 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <boost/crc.hpp>
#include <boost/cstdint.hpp>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGChannelSettings.h"
#include "SWGChannelReport.h"
#include "SWGChannelActions.h"
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "dsp/dspdevicesourceengine.h"
#include "dsp/dspengine.h"
#include "dsp/devicesamplesource.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "filesinkmessages.h"
#include "filesinkbaseband.h"
#include "filesink.h"
MESSAGE_CLASS_DEFINITION(FileSink::MsgConfigureFileSink, Message)
const QString FileSink::m_channelIdURI = "sdrangel.channel.filesink";
const QString FileSink::m_channelId = "FileSink";
FileSink::FileSink(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_centerFrequency(0),
m_frequencyOffset(0),
m_spectrumVis(SDR_RX_SCALEF),
m_basebandSampleRate(48000)
{
setObjectName(m_channelId);
m_basebandSink = new FileSinkBaseband();
m_basebandSink->setSpectrumSink(&m_spectrumVis);
m_basebandSink->moveToThread(&m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
FileSink::~FileSink()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_basebandSink->isRunning()) {
stop();
}
delete m_basebandSink;
}
uint32_t FileSink::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void FileSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void FileSink::start()
{
qDebug("FileSink::start");
m_basebandSink->reset();
m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI());
m_basebandSink->setDeviceHwId(m_deviceAPI->getHardwareId());
m_basebandSink->setDeviceUId(m_deviceAPI->getDeviceUID());
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
FileSinkBaseband::MsgConfigureFileSinkBaseband *msg = FileSinkBaseband::MsgConfigureFileSinkBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void FileSink::stop()
{
qDebug("FileSink::stop");
m_basebandSink->stopWork();
m_thread.exit();
m_thread.wait();
}
bool FileSink::handleMessage(const Message& cmd)
{
if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& cfg = (DSPSignalNotification&) cmd;
qDebug() << "FileSink::handleMessage: DSPSignalNotification:"
<< " inputSampleRate: " << cfg.getSampleRate()
<< " centerFrequency: " << cfg.getCenterFrequency();
m_basebandSampleRate = cfg.getSampleRate();
m_centerFrequency = cfg.getCenterFrequency();
DSPSignalNotification *notif = new DSPSignalNotification(cfg);
m_basebandSink->getInputMessageQueue()->push(notif);
if (getMessageQueueToGUI())
{
DSPSignalNotification *notifToGUI = new DSPSignalNotification(cfg);
getMessageQueueToGUI()->push(notifToGUI);
}
return true;
}
else if (MsgConfigureFileSink::match(cmd))
{
MsgConfigureFileSink& cfg = (MsgConfigureFileSink&) cmd;
qDebug() << "FileSink::handleMessage: MsgConfigureFileSink";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else
{
return false;
}
}
QByteArray FileSink::serialize() const
{
return m_settings.serialize();
}
bool FileSink::deserialize(const QByteArray& data)
{
(void) data;
if (m_settings.deserialize(data))
{
MsgConfigureFileSink *msg = MsgConfigureFileSink::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureFileSink *msg = MsgConfigureFileSink::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void FileSink::getLocalDevices(std::vector<uint32_t>& indexes)
{
indexes.clear();
DSPEngine *dspEngine = DSPEngine::instance();
for (uint32_t i = 0; i < dspEngine->getDeviceSourceEnginesNumber(); i++)
{
DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(i);
DeviceSampleSource *deviceSource = deviceSourceEngine->getSource();
if (deviceSource->getDeviceDescription() == "LocalInput") {
indexes.push_back(i);
}
}
}
DeviceSampleSource *FileSink::getLocalDevice(uint32_t index)
{
DSPEngine *dspEngine = DSPEngine::instance();
if (index < dspEngine->getDeviceSourceEnginesNumber())
{
DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(index);
DeviceSampleSource *deviceSource = deviceSourceEngine->getSource();
if (deviceSource->getDeviceDescription() == "LocalInput")
{
if (!getDeviceAPI()) {
qDebug("FileSink::getLocalDevice: the parent device is unset");
} else if (getDeviceAPI()->getDeviceUID() == deviceSourceEngine->getUID()) {
qDebug("FileSink::getLocalDevice: source device at index %u is the parent device", index);
} else {
return deviceSource;
}
}
else
{
qDebug("FileSink::getLocalDevice: source device at index %u is not a SigMF File sink", index);
}
}
else
{
qDebug("FileSink::getLocalDevice: non existent source device index: %u", index);
}
return nullptr;
}
void FileSink::applySettings(const FileSinkSettings& settings, bool force)
{
qDebug() << "FileSink::applySettings:"
<< "m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< "m_log2Decim: " << settings.m_log2Decim
<< "m_fileRecordName: " << settings.m_fileRecordName
<< "force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_fileRecordName != m_settings.m_fileRecordName) || force) {
reverseAPIKeys.append("fileRecordName");
}
if ((settings.m_rgbColor != m_settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
if ((settings.m_title != m_settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((settings.m_log2Decim != m_settings.m_log2Decim) || force) {
reverseAPIKeys.append("log2Decim");
}
if ((settings.m_spectrumSquelchMode != m_settings.m_spectrumSquelchMode) || force) {
reverseAPIKeys.append("spectrumSquelchMode");
}
if ((settings.m_spectrumSquelch != m_settings.m_spectrumSquelch) || force) {
reverseAPIKeys.append("spectrumSquelch");
}
if ((settings.m_preRecordTime != m_settings.m_preRecordTime) || force) {
reverseAPIKeys.append("preRecordTime");
}
if ((settings.m_squelchPostRecordTime != m_settings.m_squelchPostRecordTime) || force) {
reverseAPIKeys.append("squelchPostRecordTime");
}
if ((settings.m_squelchRecordingEnable != m_settings.m_squelchRecordingEnable) || force) {
reverseAPIKeys.append("squelchRecordingEnable");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
FileSinkBaseband::MsgConfigureFileSinkBaseband *msg = FileSinkBaseband::MsgConfigureFileSinkBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0))
{
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 FileSink::record(bool record)
{
FileSinkBaseband::MsgConfigureFileSinkWork *msg = FileSinkBaseband::MsgConfigureFileSinkWork::create(record);
m_basebandSink->getInputMessageQueue()->push(msg);
}
uint64_t FileSink::getMsCount() const
{
if (m_basebandSink) {
return m_basebandSink->getMsCount();
} else {
return 0;
}
}
uint64_t FileSink::getByteCount() const
{
if (m_basebandSink) {
return m_basebandSink->getByteCount();
} else {
return 0;
}
}
unsigned int FileSink::getNbTracks() const
{
if (m_basebandSink) {
return m_basebandSink->getNbTracks();
} else {
return 0;
}
}
int FileSink::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSigMfFileSinkSettings(new SWGSDRangel::SWGSigMFFileSinkSettings());
response.getSigMfFileSinkSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int FileSink::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
FileSinkSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureFileSink *msg = MsgConfigureFileSink::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("FileSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureFileSink *msgToGUI = MsgConfigureFileSink::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
int FileSink::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSigMfFileSinkReport(new SWGSDRangel::SWGSigMFFileSinkReport());
response.getSigMfFileSinkReport()->init();
webapiFormatChannelReport(response);
return 200;
}
int FileSink::webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage)
{
SWGSDRangel::SWGSigMFFileSinkActions *swgSigMFFileSinkActions = query.getSigMfFileSinkActions();
if (swgSigMFFileSinkActions)
{
if (channelActionsKeys.contains("record"))
{
bool record = swgSigMFFileSinkActions->getRecord() != 0;
if (!m_settings.m_squelchRecordingEnable)
{
FileSinkBaseband::MsgConfigureFileSinkWork *msg = FileSinkBaseband::MsgConfigureFileSinkWork::create(record);
m_basebandSink->getInputMessageQueue()->push(msg);
if (getMessageQueueToGUI())
{
FileSinkMessages::MsgReportRecording *msgToGUI = FileSinkMessages::MsgReportRecording::create(record);
getMessageQueueToGUI()->push(msgToGUI);
}
}
}
return 202;
}
else
{
errorMessage = "Missing SigMFFileSinkActions in query";
return 400;
}
}
void FileSink::webapiUpdateChannelSettings(
FileSinkSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getSigMfFileSinkSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("fileRecordName")) {
settings.m_fileRecordName = *response.getSigMfFileSinkSettings()->getFileRecordName();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getSigMfFileSinkSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getSigMfFileSinkSettings()->getTitle();
}
if (channelSettingsKeys.contains("log2Decim")) {
settings.m_log2Decim = response.getSigMfFileSinkSettings()->getLog2Decim();
}
if (channelSettingsKeys.contains("spectrumSquelchMode")) {
settings.m_spectrumSquelchMode = response.getSigMfFileSinkSettings()->getSpectrumSquelchMode() != 0;
}
if (channelSettingsKeys.contains("spectrumSquelch")) {
settings.m_spectrumSquelch = response.getSigMfFileSinkSettings()->getSpectrumSquelch();
}
if (channelSettingsKeys.contains("preRecordTime")) {
settings.m_preRecordTime = response.getSigMfFileSinkSettings()->getPreRecordTime();
}
if (channelSettingsKeys.contains("squelchPostRecordTime")) {
settings.m_squelchPostRecordTime = response.getSigMfFileSinkSettings()->getSquelchPostRecordTime();
}
if (channelSettingsKeys.contains("squelchRecordingEnable")) {
settings.m_squelchRecordingEnable = response.getSigMfFileSinkSettings()->getSquelchRecordingEnable() != 0;
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getSigMfFileSinkSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getSigMfFileSinkSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getSigMfFileSinkSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getSigMfFileSinkSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getSigMfFileSinkSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getSigMfFileSinkSettings()->getReverseApiChannelIndex();
}
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_reverseAPIChannelIndex = response.getSigMfFileSinkSettings()->getInputFrequencyOffset();
}
}
void FileSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FileSinkSettings& settings)
{
response.getSigMfFileSinkSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
if (response.getSigMfFileSinkSettings()->getFileRecordName()) {
*response.getSigMfFileSinkSettings()->getFileRecordName() = settings.m_fileRecordName;
} else {
response.getSigMfFileSinkSettings()->setFileRecordName(new QString(settings.m_fileRecordName));
}
response.getSigMfFileSinkSettings()->setRgbColor(settings.m_rgbColor);
if (response.getSigMfFileSinkSettings()->getTitle()) {
*response.getSigMfFileSinkSettings()->getTitle() = settings.m_title;
} else {
response.getSigMfFileSinkSettings()->setTitle(new QString(settings.m_title));
}
response.getSigMfFileSinkSettings()->setLog2Decim(settings.m_log2Decim);
response.getSigMfFileSinkSettings()->setSpectrumSquelchMode(settings.m_spectrumSquelchMode ? 1 : 0);
response.getSigMfFileSinkSettings()->setSpectrumSquelch(settings.m_spectrumSquelch);
response.getSigMfFileSinkSettings()->setPreRecordTime(settings.m_preRecordTime);
response.getSigMfFileSinkSettings()->setSquelchPostRecordTime(settings.m_squelchPostRecordTime);
response.getSigMfFileSinkSettings()->setSquelchRecordingEnable(settings.m_squelchRecordingEnable ? 1 : 0);
response.getSigMfFileSinkSettings()->setStreamIndex(settings.m_streamIndex);
response.getSigMfFileSinkSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getSigMfFileSinkSettings()->getReverseApiAddress()) {
*response.getSigMfFileSinkSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getSigMfFileSinkSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getSigMfFileSinkSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getSigMfFileSinkSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getSigMfFileSinkSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void FileSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getSigMfFileSinkReport()->setSpectrumSquelch(m_basebandSink->isSquelchOpen() ? 1 : 0);
response.getSigMfFileSinkReport()->setSpectrumMax(m_basebandSink->getSpecMax());
response.getSigMfFileSinkReport()->setSinkSampleRate(m_basebandSink->getSinkSampleRate());
response.getSigMfFileSinkReport()->setRecordTimeMs(getMsCount());
response.getSigMfFileSinkReport()->setRecordSize(getByteCount());
response.getSigMfFileSinkReport()->setRecording(m_basebandSink->isRecording() ? 1 : 0);
response.getSigMfFileSinkReport()->setRecordCaptures(getNbTracks());
response.getSigMfFileSinkReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
void FileSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FileSinkSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
swgChannelSettings->setDirection(0); // single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("FileSink"));
swgChannelSettings->setSigMfFileSinkSettings(new SWGSDRangel::SWGSigMFFileSinkSettings());
SWGSDRangel::SWGSigMFFileSinkSettings *swgSigMFFileSinkSettings = swgChannelSettings->getSigMfFileSinkSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
swgSigMFFileSinkSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("fileRecordName")) {
swgSigMFFileSinkSettings->setTitle(new QString(settings.m_fileRecordName));
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgSigMFFileSinkSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgSigMFFileSinkSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("log2Decim") || force) {
swgSigMFFileSinkSettings->setLog2Decim(settings.m_log2Decim);
}
if (channelSettingsKeys.contains("spectrumSquelchMode")) {
swgSigMFFileSinkSettings->setSpectrumSquelchMode(settings.m_spectrumSquelchMode ? 1 : 0);
}
if (channelSettingsKeys.contains("spectrumSquelch")) {
swgSigMFFileSinkSettings->setSpectrumSquelch(settings.m_spectrumSquelch);
}
if (channelSettingsKeys.contains("preRecordTime")) {
swgSigMFFileSinkSettings->setPreRecordTime(settings.m_preRecordTime);
}
if (channelSettingsKeys.contains("squelchPostRecordTime")) {
swgSigMFFileSinkSettings->setSquelchPostRecordTime(settings.m_squelchPostRecordTime);
}
if (channelSettingsKeys.contains("squelchRecordingEnable")) {
swgSigMFFileSinkSettings->setSquelchRecordingEnable(settings.m_squelchRecordingEnable ? 1 : 0);
}
if (channelSettingsKeys.contains("streamIndex")) {
swgSigMFFileSinkSettings->setStreamIndex(settings.m_streamIndex);
}
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
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void FileSink::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "FileSink::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("FileSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,155 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINK_H_
#define INCLUDE_FILESINK_H_
#include <QObject>
#include <QThread>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesink.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h"
#include "filesinksettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class DeviceAPI;
class DeviceSampleSource;
class FileSinkBaseband;
class FileSink : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureFileSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
const FileSinkSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureFileSink* create(const FileSinkSettings& settings, bool force)
{
return new MsgConfigureFileSink(settings, force);
}
private:
FileSinkSettings m_settings;
bool m_force;
MsgConfigureFileSink(const FileSinkSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
FileSink(DeviceAPI *deviceAPI);
virtual ~FileSink();
virtual void destroy() { delete this; }
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
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 = "File Sink"; }
virtual qint64 getCenterFrequency() const { return m_frequencyOffset; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_frequencyOffset;
}
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);
virtual int webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const FileSinkSettings& settings);
static void webapiUpdateChannelSettings(
FileSinkSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
void getLocalDevices(std::vector<uint32_t>& indexes);
uint32_t getNumberOfDeviceStreams() const;
SpectrumVis *getSpectrumVis() { return &m_spectrumVis; }
void record(bool record);
uint64_t getMsCount() const;
uint64_t getByteCount() const;
unsigned int getNbTracks() const;
static const QString m_channelIdURI;
static const QString m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
FileSinkBaseband *m_basebandSink;
FileSinkSettings m_settings;
SpectrumVis m_spectrumVis;
uint64_t m_centerFrequency;
int64_t m_frequencyOffset;
uint32_t m_basebandSampleRate;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const FileSinkSettings& settings, bool force = false);
void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim);
DeviceSampleSource *getLocalDevice(uint32_t index);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FileSinkSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif /* INCLUDE_FILESINK_H_ */

View File

@ -0,0 +1,248 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <QDebug>
#include "dsp/downchannelizer.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/spectrumvis.h"
#include "util/db.h"
#include "filesinkmessages.h"
#include "filesinkbaseband.h"
MESSAGE_CLASS_DEFINITION(FileSinkBaseband::MsgConfigureFileSinkBaseband, Message)
MESSAGE_CLASS_DEFINITION(FileSinkBaseband::MsgConfigureFileSinkWork, Message)
FileSinkBaseband::FileSinkBaseband() :
m_running(false),
m_specMax(0),
m_squelchLevel(0),
m_squelchOpen(false),
m_mutex(QMutex::Recursive)
{
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
qDebug("FileSinkBaseband::FileSinkBaseband");
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(200);
}
FileSinkBaseband::~FileSinkBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void FileSinkBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void FileSinkBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&FileSinkBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void FileSinkBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&FileSinkBaseband::handleData
);
m_running = false;
}
void FileSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void FileSinkBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void FileSinkBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool FileSinkBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureFileSinkBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureFileSinkBaseband& cfg = (MsgConfigureFileSinkBaseband&) cmd;
qDebug() << "FileSinkBaseband::handleMessage: MsgConfigureFileSinkBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "FileSinkBaseband::handleMessage: DSPSignalNotification:"
<< " basebandSampleRate: " << notif.getSampleRate()
<< " cnterFrequency: " << notif.getCenterFrequency();
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
m_centerFrequency = notif.getCenterFrequency();
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
int desiredSampleRate = m_channelizer->getBasebandSampleRate() / (1<<m_settings.m_log2Decim);
m_channelizer->setChannelization(desiredSampleRate, m_settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(
m_channelizer->getChannelSampleRate(),
desiredSampleRate,
m_channelizer->getChannelFrequencyOffset(),
m_centerFrequency + m_settings.m_inputFrequencyOffset);
return true;
}
else if (MsgConfigureFileSinkWork::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureFileSinkWork& conf = (MsgConfigureFileSinkWork&) cmd;
qDebug() << "FileSinkBaseband::handleMessage: MsgConfigureFileSinkWork: " << conf.isWorking();
if (!m_settings.m_squelchRecordingEnable)
{
if (conf.isWorking()) {
m_sink.startRecording();
} else {
m_sink.stopRecording();
}
}
return true;
}
else
{
return false;
}
}
void FileSinkBaseband::applySettings(const FileSinkSettings& settings, bool force)
{
qDebug() << "FileSinkBaseband::applySettings:"
<< "m_log2Decim:" << settings.m_log2Decim
<< "m_inputFrequencyOffset:" << settings.m_inputFrequencyOffset
<< "m_fileRecordName: " << settings.m_fileRecordName
<< "m_centerFrequency: " << m_centerFrequency
<< "force: " << force;
if ((settings.m_log2Decim != m_settings.m_log2Decim)
|| (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
int desiredSampleRate = m_channelizer->getBasebandSampleRate() / (1<<settings.m_log2Decim);
m_channelizer->setChannelization(desiredSampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(
m_channelizer->getChannelSampleRate(),
desiredSampleRate,
m_channelizer->getChannelFrequencyOffset(),
m_centerFrequency + settings.m_inputFrequencyOffset);
}
if ((settings.m_spectrumSquelchMode != m_settings.m_spectrumSquelchMode) || force) {
if (!settings.m_spectrumSquelchMode) {
m_squelchOpen = false;
}
}
if ((settings.m_spectrumSquelch != m_settings.m_spectrumSquelch) || force) {
m_squelchLevel = CalcDb::powerFromdB(settings.m_spectrumSquelch);
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int FileSinkBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void FileSinkBaseband::tick()
{
if (m_spectrumSink && m_settings.m_spectrumSquelchMode)
{
m_specMax = m_spectrumSink->getSpecMax();
bool squelchOpen = m_specMax > m_squelchLevel;
if (squelchOpen != m_squelchOpen)
{
if (m_messageQueueToGUI)
{
FileSinkMessages::MsgReportSquelch *msg = FileSinkMessages::MsgReportSquelch::create(squelchOpen);
m_messageQueueToGUI->push(msg);
}
if (m_settings.m_squelchRecordingEnable) {
m_sink.squelchRecording(squelchOpen);
}
}
m_squelchOpen = squelchOpen;
}
}

View File

@ -0,0 +1,131 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINKBASEBAND_H_
#define INCLUDE_FILESINKBASEBAND_H_
#include <QObject>
#include <QMutex>
#include <QTimer>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "filesinksink.h"
#include "filesinksettings.h"
class DownChannelizer;
class SpectrumVis;
class FileSinkBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureFileSinkBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const FileSinkSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureFileSinkBaseband* create(const FileSinkSettings& settings, bool force)
{
return new MsgConfigureFileSinkBaseband(settings, force);
}
private:
FileSinkSettings m_settings;
bool m_force;
MsgConfigureFileSinkBaseband(const FileSinkSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureFileSinkWork : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool isWorking() const { return m_working; }
static MsgConfigureFileSinkWork* create(bool working)
{
return new MsgConfigureFileSinkWork(working);
}
private:
bool m_working;
MsgConfigureFileSinkWork(bool working) :
Message(),
m_working(working)
{ }
};
FileSinkBaseband();
~FileSinkBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;
void setBasebandSampleRate(int sampleRate);
bool isRunning() const { return m_running; }
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; m_sink.setSpectrumSink(spectrumSink); }
uint64_t getMsCount() const { return m_sink.getMsCount(); }
uint64_t getByteCount() const { return m_sink.getByteCount(); }
unsigned int getNbTracks() const { return m_sink.getNbTracks(); }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; m_sink.setMessageQueueToGUI(messageQueue); }
void setDeviceHwId(const QString& hwId) { m_sink.setDeviceHwId(hwId); }
void setDeviceUId(int uid) { m_sink.setDeviceUId(uid); }
bool isSquelchOpen() const { return m_squelchOpen; }
bool isRecording() const { return m_sink.isRecording(); }
float getSpecMax() const { return m_specMax; }
int getSinkSampleRate() const { return m_sink.getSampleRate(); }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
FileSinkSink m_sink;
SpectrumVis *m_spectrumSink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
FileSinkSettings m_settings;
float m_specMax; //!< Last max used for comparison
float m_squelchLevel;
bool m_squelchOpen;
int64_t m_centerFrequency;
bool m_running;
QMutex m_mutex;
QTimer m_timer;
bool handleMessage(const Message& cmd);
void applySettings(const FileSinkSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
void tick();
};
#endif // INCLUDE_FILESINKBASEBAND_H_

View File

@ -0,0 +1,590 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <QLocale>
#include <QFileDialog>
#include <QTime>
#include "device/deviceuiset.h"
#include "device/deviceapi.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/dspcommands.h"
#include "mainwindow.h"
#include "filesinkmessages.h"
#include "filesink.h"
#include "filesinkgui.h"
#include "ui_filesinkgui.h"
FileSinkGUI* FileSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx)
{
FileSinkGUI* gui = new FileSinkGUI(pluginAPI, deviceUISet, channelRx);
return gui;
}
void FileSinkGUI::destroy()
{
delete this;
}
void FileSinkGUI::setName(const QString& name)
{
setObjectName(name);
}
QString FileSinkGUI::getName() const
{
return objectName();
}
qint64 FileSinkGUI::getCenterFrequency() const {
return 0;
}
void FileSinkGUI::setCenterFrequency(qint64 centerFrequency)
{
(void) centerFrequency;
}
void FileSinkGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray FileSinkGUI::serialize() const
{
return m_settings.serialize();
}
bool FileSinkGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool FileSinkGUI::handleMessage(const Message& message)
{
if (DSPSignalNotification::match(message))
{
DSPSignalNotification notif = (const DSPSignalNotification&) message;
m_basebandSampleRate = notif.getSampleRate();
displayRate();
if (m_fixedPosition)
{
setFrequencyFromPos();
applySettings();
}
else
{
setPosFromFrequency();
}
return true;
}
else if (FileSink::MsgConfigureFileSink::match(message))
{
const FileSink::MsgConfigureFileSink& cfg = (FileSink::MsgConfigureFileSink&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (FileSinkMessages::MsgConfigureSpectrum::match(message))
{
const FileSinkMessages::MsgConfigureSpectrum& cfg = (FileSinkMessages::MsgConfigureSpectrum&) message;
ui->glSpectrum->setSampleRate(cfg.getSampleRate());
ui->glSpectrum->setCenterFrequency(cfg.getCenterFrequency());
return true;
}
else if (FileSinkMessages::MsgReportSquelch::match(message))
{
const FileSinkMessages::MsgReportSquelch& report = (FileSinkMessages::MsgReportSquelch&) message;
if (report.getOpen()) {
ui->squelchLevel->setStyleSheet("QDial { background-color : green; }");
} else {
ui->squelchLevel->setStyleSheet("QDial { background:rgb(79,79,79); }");
}
return true;
}
else if (FileSinkMessages::MsgReportRecording::match(message))
{
const FileSinkMessages::MsgReportSquelch& report = (FileSinkMessages::MsgReportSquelch&) message;
if (report.getOpen())
{
ui->record->setStyleSheet("QToolButton { background-color : red; }");
ui->squelchedRecording->setEnabled(false);
}
else
{
ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
ui->squelchedRecording->setEnabled(true);
}
return true;
}
else if (FileSinkMessages::MsgReportRecordFileName::match(message))
{
const FileSinkMessages::MsgReportRecordFileName& report = (FileSinkMessages::MsgReportRecordFileName&) message;
ui->fileNameText->setText(report.getFileName());
return true;
}
else
{
return false;
}
}
FileSinkGUI::FileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) :
RollupWidget(parent),
ui(new Ui::FileSinkGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_fixedShiftIndex(0),
m_basebandSampleRate(0),
m_fixedPosition(false),
m_tickCount(0)
{
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_fileSink = (FileSink*) channelrx;
m_spectrumVis = m_fileSink->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum);
m_fileSink->setMessageQueueToGUI(getInputMessageQueue());
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 8, -99999999, 99999999);
ui->position->setEnabled(m_fixedPosition);
ui->glSpectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(m_settings.m_rgbColor);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setBandwidth(m_basebandSampleRate);
m_channelMarker.setTitle("File Sink");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_settings.setSpectrumGUI(ui->glSpectrumGUI);
m_deviceUISet->registerRxChannelInstance(FileSink::m_channelIdURI, this);
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
displaySettings();
applySettings(true);
}
FileSinkGUI::~FileSinkGUI()
{
m_deviceUISet->removeRxChannelInstance(this);
delete m_fileSink; // TODO: check this: when the GUI closes it has to delete the demodulator
delete ui;
}
void FileSinkGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void FileSinkGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
setTitleColor(m_channelMarker.getColor());
FileSink::MsgConfigureFileSink* message = FileSink::MsgConfigureFileSink::create(m_settings, force);
m_fileSink->getInputMessageQueue()->push(message);
}
}
void FileSinkGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setBandwidth(m_basebandSampleRate / (1<<m_settings.m_log2Decim));
m_channelMarker.setTitle(m_settings.m_title);
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->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->fileNameText->setText(m_settings.m_fileRecordName);
ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
ui->spectrumSquelch->setChecked(m_settings.m_spectrumSquelchMode);
ui->squelchLevel->setValue(m_settings.m_spectrumSquelch);
ui->squelchLevelText->setText(tr("%1").arg(m_settings.m_spectrumSquelch));
ui->preRecordTime->setValue(m_settings.m_preRecordTime);
ui->preRecordTimeText->setText(tr("%1").arg(m_settings.m_preRecordTime));
ui->postSquelchTime->setValue(m_settings.m_squelchPostRecordTime);
ui->postSquelchTimeText->setText(tr("%1").arg(m_settings.m_squelchPostRecordTime));
ui->squelchedRecording->setChecked(m_settings.m_squelchRecordingEnable);
ui->record->setEnabled(!m_settings.m_squelchRecordingEnable);
if (!m_settings.m_spectrumSquelchMode) {
ui->squelchLevel->setStyleSheet("QDial { background:rgb(79,79,79); }");
}
displayStreamIndex();
setPosFromFrequency();
blockApplySettings(false);
}
void FileSinkGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void FileSinkGUI::displayRate()
{
double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Decim);
ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
m_channelMarker.setBandwidth(channelSampleRate);
}
void FileSinkGUI::displayPos()
{
ui->position->setValue(m_fixedShiftIndex);
ui->filterChainIndex->setText(tr("%1").arg(m_fixedShiftIndex));
}
void FileSinkGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void FileSinkGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void FileSinkGUI::channelMarkerChangedByCursor()
{
if (m_fixedPosition) {
return;
}
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
setPosFromFrequency();
applySettings();
}
void FileSinkGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void FileSinkGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void FileSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void FileSinkGUI::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();
}
else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine))
{
DeviceStreamSelectionDialog dialog(this);
dialog.setNumberOfStreams(m_fileSink->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
dialog.move(p);
dialog.exec();
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
displayStreamIndex();
applySettings();
}
resetContextMenuType();
}
void FileSinkGUI::on_deltaFrequency_changed(qint64 value)
{
if (!m_fixedPosition)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
setPosFromFrequency();
applySettings();
}
}
void FileSinkGUI::on_decimationFactor_currentIndexChanged(int index)
{
m_settings.m_log2Decim = index;
applyDecimation();
displayRate();
displayPos();
applySettings();
if (m_fixedPosition) {
setFrequencyFromPos();
} else {
setPosFromFrequency();
}
}
void FileSinkGUI::on_fixedPosition_toggled(bool checked)
{
m_fixedPosition = checked;
m_channelMarker.setMovable(!checked);
ui->deltaFrequency->setEnabled(!checked);
ui->position->setEnabled(checked);
if (m_fixedPosition)
{
setFrequencyFromPos();
applySettings();
}
}
void FileSinkGUI::on_position_valueChanged(int value)
{
m_fixedShiftIndex = value;
displayPos();
if (m_fixedPosition)
{
setFrequencyFromPos();
applySettings();
}
}
void FileSinkGUI::on_spectrumSquelch_toggled(bool checked)
{
m_settings.m_spectrumSquelchMode = checked;
if (!m_settings.m_spectrumSquelchMode)
{
m_settings.m_squelchRecordingEnable = false;
ui->squelchLevel->setStyleSheet("QDial { background:rgb(79,79,79); }");
ui->squelchedRecording->blockSignals(true);
ui->squelchedRecording->setChecked(false);
ui->squelchedRecording->blockSignals(false);
ui->record->setEnabled(true);
}
ui->squelchedRecording->setEnabled(checked);
applySettings();
}
void FileSinkGUI::on_squelchLevel_valueChanged(int value)
{
m_settings.m_spectrumSquelch = value;
ui->squelchLevelText->setText(tr("%1").arg(m_settings.m_spectrumSquelch));
applySettings();
}
void FileSinkGUI::on_preRecordTime_valueChanged(int value)
{
m_settings.m_preRecordTime = value;
ui->preRecordTimeText->setText(tr("%1").arg(m_settings.m_preRecordTime));
applySettings();
}
void FileSinkGUI::on_postSquelchTime_valueChanged(int value)
{
m_settings.m_squelchPostRecordTime = value;
ui->postSquelchTimeText->setText(tr("%1").arg(m_settings.m_squelchPostRecordTime));
applySettings();
}
void FileSinkGUI::on_squelchedRecording_toggled(bool checked)
{
ui->record->setEnabled(!checked);
m_settings.m_squelchRecordingEnable = checked;
applySettings();
}
void FileSinkGUI::on_record_toggled(bool checked)
{
ui->squelchedRecording->setEnabled(!checked);
if (checked) {
ui->record->setStyleSheet("QToolButton { background-color : red; }");
} else {
ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
m_fileSink->record(checked);
}
void FileSinkGUI::on_showFileDialog_clicked(bool checked)
{
(void) checked;
QFileDialog fileDialog(
this,
tr("Save record file"),
m_settings.m_fileRecordName,
tr("SDR I/Q Files (*.sdriq)")
);
fileDialog.setOptions(QFileDialog::DontUseNativeDialog);
fileDialog.setFileMode(QFileDialog::AnyFile);
QStringList fileNames;
if (fileDialog.exec())
{
fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
m_settings.m_fileRecordName = fileNames.at(0);
ui->fileNameText->setText(m_settings.m_fileRecordName);
applySettings();
}
}
}
void FileSinkGUI::setFrequencyFromPos()
{
int inputFrequencyOffset = FileSinkSettings::getOffsetFromFixedShiftIndex(
m_basebandSampleRate,
m_settings.m_log2Decim,
m_fixedShiftIndex);
m_channelMarker.setCenterFrequency(inputFrequencyOffset);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
}
void FileSinkGUI::setPosFromFrequency()
{
int fshift = FileSinkSettings::getHalfBand(m_basebandSampleRate, m_settings.m_log2Decim + 1);
m_fixedShiftIndex = FileSinkSettings::getFixedShiftIndexFromOffset(
m_basebandSampleRate,
m_settings.m_log2Decim,
m_settings.m_inputFrequencyOffset + (m_settings.m_inputFrequencyOffset < 0 ? -fshift : fshift)
);
displayPos();
}
void FileSinkGUI::applyDecimation()
{
ui->position->setMaximum(FileSinkSettings::getNbFixedShiftIndexes(m_settings.m_log2Decim)-1);
ui->position->setValue(m_fixedShiftIndex);
m_fixedShiftIndex = ui->position->value();
}
void FileSinkGUI::tick()
{
if (++m_tickCount == 20) // once per second
{
uint64_t msTime = m_fileSink->getMsCount();
uint64_t bytes = m_fileSink->getByteCount();
unsigned int nbTracks = m_fileSink->getNbTracks();
QTime recordLength(0, 0, 0, 0);
recordLength = recordLength.addSecs(msTime / 1000);
recordLength = recordLength.addMSecs(msTime % 1000);
QString s_time = recordLength.toString("HH:mm:ss");
ui->recordTimeText->setText(s_time);
ui->recordSizeText->setText(displayScaled(bytes, 2));
ui->recordNbTracks->setText(tr("#%1").arg(nbTracks));
m_tickCount = 0;
}
}
QString FileSinkGUI::displayScaled(uint64_t value, int precision)
{
if (value < 1000) {
return tr("%1").arg(QString::number(value, 'f', precision));
} else if (value < 1000000) {
return tr("%1k").arg(QString::number(value / 1000.0, 'f', precision));
} else if (value < 1000000000) {
return tr("%1M").arg(QString::number(value / 1000000.0, 'f', precision));
} else if (value < 1000000000000) {
return tr("%1G").arg(QString::number(value / 1000000000.0, 'f', precision));
}
}

View File

@ -0,0 +1,118 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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_CHANNELRX_FILESINK_FILESINKGUI_H_
#define PLUGINS_CHANNELRX_FILESINK_FILESINKGUI_H_
#include <stdint.h>
#include <QObject>
#include "plugin/plugininstancegui.h"
#include "dsp/channelmarker.h"
#include "gui/rollupwidget.h"
#include "util/messagequeue.h"
#include "filesinksettings.h"
class PluginAPI;
class DeviceUISet;
class FileSink;
class BasebandSampleSink;
class SpectrumVis;
namespace Ui {
class FileSinkGUI;
}
class FileSinkGUI : public RollupWidget, public PluginInstanceGUI {
Q_OBJECT
public:
static FileSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
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();
void channelMarkerHighlightedByCursor();
private:
Ui::FileSinkGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
FileSinkSettings m_settings;
int m_fixedShiftIndex;
int m_basebandSampleRate;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
bool m_fixedPosition;
bool m_doApplySettings;
FileSink* m_fileSink;
SpectrumVis* m_spectrumVis;
MessageQueue m_inputMessageQueue;
uint32_t m_tickCount;
explicit FileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = nullptr);
virtual ~FileSinkGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void applyDecimation();
void displaySettings();
void displayStreamIndex();
void displayRate();
void displayPos();
void setFrequencyFromPos();
void setPosFromFrequency();
QString displayScaled(uint64_t value, int precision);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void handleSourceMessages();
void on_deltaFrequency_changed(qint64 value);
void on_decimationFactor_currentIndexChanged(int index);
void on_fixedPosition_toggled(bool checked);
void on_position_valueChanged(int value);
void on_spectrumSquelch_toggled(bool checked);
void on_squelchLevel_valueChanged(int value);
void on_preRecordTime_valueChanged(int value);
void on_postSquelchTime_valueChanged(int value);
void on_squelchedRecording_toggled(bool checked);
void on_record_toggled(bool checked);
void on_showFileDialog_clicked(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void tick();
};
#endif /* PLUGINS_CHANNELRX_FILESINK_FILESINKGUI_H_ */

View File

@ -0,0 +1,584 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileSinkGUI</class>
<widget class="RollupWidget" name="FileSinkGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>552</width>
<height>458</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>552</width>
<height>102</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>File Sink</string>
</property>
<property name="statusTip">
<string>File Sink</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>550</width>
<height>100</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>550</width>
<height>100</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<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="QHBoxLayout" name="deltaFrequencyLayout">
<property name="rightMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="decimationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="decimationFactor">
<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>
<spacer name="horizontalSpacer_4">
<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="channelRateText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Sink 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="recordNbTracks">
<property name="minimumSize">
<size>
<width>32</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Number of captures (files) in recording session updated at end of file</string>
</property>
<property name="text">
<string>#000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="recordTimeText">
<property name="minimumSize">
<size>
<width>52</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Total recording time (HH:MM:SS)</string>
</property>
<property name="text">
<string>00:00:00</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="recordSizeText">
<property name="minimumSize">
<size>
<width>52</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Total recording size (k: kB, M: MB, G: GB)</string>
</property>
<property name="text">
<string>999.99M</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="decimationShiftLayout">
<property name="rightMargin">
<number>10</number>
</property>
<item>
<widget class="QCheckBox" name="fixedPosition">
<property name="toolTip">
<string>Use fixed frequency shift positions for little performance improvement</string>
</property>
<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>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="spectrumSquelch">
<property name="toolTip">
<string>Toggle spectrum squelch recording</string>
</property>
<property name="text">
<string>SQ</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="squelchLevel">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Spectrum squelch level (dB)</string>
</property>
<property name="minimum">
<number>-120</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>-50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="squelchLevelText">
<property name="toolTip">
<string>Spectrum squelch level (dB)</string>
</property>
<property name="text">
<string>-100</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="preRecordTime">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Pre-recording time (s)</string>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="preRecordTimeText">
<property name="toolTip">
<string>Squelched recoding pre-recording time (s)</string>
</property>
<property name="text">
<string>10</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="postSquelchTime">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Squelched recording post-recording time (s) </string>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="postSquelchTimeText">
<property name="toolTip">
<string>Squelched recording post-recording time (s)</string>
</property>
<property name="text">
<string>10</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="squelchedRecording">
<property name="toolTip">
<string>Squelched recording enable</string>
</property>
<property name="text">
<string>REC</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fileNameLayout">
<item>
<widget class="ButtonSwitch" name="record">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/record_off.png</normaloff>:/record_off.png</iconset>
</property>
</widget>
</item>
<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="toolTip">
<string>Open file</string>
</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="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Current recording file</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>100</y>
<width>541</width>
<height>351</height>
</rect>
</property>
<property name="windowTitle">
<string>Channel Spectrum</string>
</property>
<layout class="QVBoxLayout" name="SpectrumLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="GLSpectrum" name="glSpectrum" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLSpectrumGUI" name="glSpectrumGUI" native="true"/>
</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>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrum</class>
<extends>QWidget</extends>
<header>gui/glspectrum.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrumGUI</class>
<extends>QWidget</extends>
<header>gui/glspectrumgui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,23 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "filesinkmessages.h"
MESSAGE_CLASS_DEFINITION(FileSinkMessages::MsgConfigureSpectrum, Message)
MESSAGE_CLASS_DEFINITION(FileSinkMessages::MsgReportSquelch, Message)
MESSAGE_CLASS_DEFINITION(FileSinkMessages::MsgReportRecording, Message)
MESSAGE_CLASS_DEFINITION(FileSinkMessages::MsgReportRecordFileName, Message)

View File

@ -0,0 +1,108 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINKMESSAGES_H_
#define INCLUDE_FILESINKMESSAGES_H_
#include <QObject>
#include "util/message.h"
class FileSinkMessages : public QObject {
Q_OBJECT
public:
class MsgConfigureSpectrum : public Message {
MESSAGE_CLASS_DECLARATION
public:
int64_t getCenterFrequency() const { return m_centerFrequency; }
int getSampleRate() const { return m_sampleRate; }
static MsgConfigureSpectrum* create(int64_t centerFrequency, int sampleRate) {
return new MsgConfigureSpectrum(centerFrequency, sampleRate);
}
private:
int64_t m_centerFrequency;
int m_sampleRate;
MsgConfigureSpectrum(int64_t centerFrequency, int sampleRate) :
Message(),
m_centerFrequency(centerFrequency),
m_sampleRate(sampleRate)
{ }
};
class MsgReportSquelch : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getOpen() const { return m_open; }
static MsgReportSquelch* create(bool open) {
return new MsgReportSquelch(open);
}
private:
bool m_open;
MsgReportSquelch(bool open) :
Message(),
m_open(open)
{ }
};
class MsgReportRecording : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getRecording() const { return m_recording; }
static MsgReportRecording* create(bool recording) {
return new MsgReportRecording(recording);
}
private:
bool m_recording;
MsgReportRecording(bool recording) :
Message(),
m_recording(recording)
{ }
};
class MsgReportRecordFileName : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getFileName() const { return m_fileName; }
static MsgReportRecordFileName* create(const QString& fileName) {
return new MsgReportRecordFileName(fileName);
}
private:
QString m_fileName;
MsgReportRecordFileName(const QString& fileName) :
Message(),
m_fileName(fileName)
{ }
};
};
#endif // INCLUDE_FILESINKMESSAGES_H_

View File

@ -0,0 +1,85 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 "filesinkgui.h"
#endif
#include "filesink.h"
#include "filesinkwebapiadapter.h"
#include "filesinkplugin.h"
const PluginDescriptor FileSinkPlugin::m_pluginDescriptor = {
FileSink::m_channelId,
QString("File Sink"),
QString("4.15.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
FileSinkPlugin::FileSinkPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& FileSinkPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void FileSinkPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register channel Source
m_pluginAPI->registerRxChannel(FileSink::m_channelIdURI, FileSink::m_channelId, this);
}
#ifdef SERVER_MODE
PluginInstanceGUI* FileSinkPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
return nullptr;
}
#else
PluginInstanceGUI* FileSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return FileSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
BasebandSampleSink* FileSinkPlugin::createRxChannelBS(DeviceAPI *deviceAPI) const
{
return new FileSink(deviceAPI);
}
ChannelAPI* FileSinkPlugin::createRxChannelCS(DeviceAPI *deviceAPI) const
{
return new FileSink(deviceAPI);
}
ChannelWebAPIAdapter* FileSinkPlugin::createChannelWebAPIAdapter() const
{
return new FileSinkWebAPIAdapter();
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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_CHANNELRX_FILESINK_FILESINKPLUGIN_H_
#define PLUGINS_CHANNELRX_FILESINK_FILESINKPLUGIN_H_
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class FileSinkPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.demod.filesink")
public:
explicit FileSinkPlugin(QObject* parent = 0);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual BasebandSampleSink* createRxChannelBS(DeviceAPI *deviceAPI) const;
virtual ChannelAPI* createRxChannelCS(DeviceAPI *deviceAPI) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif /* PLUGINS_CHANNELRX_FILESINK_FILESINKPLUGIN_H_ */

View File

@ -0,0 +1,173 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <QColor>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "filesinksettings.h"
FileSinkSettings::FileSinkSettings()
{
resetToDefaults();
}
void FileSinkSettings::resetToDefaults()
{
m_ncoMode = false;
m_inputFrequencyOffset = 0;
m_fileRecordName = "";
m_rgbColor = QColor(140, 4, 4).rgb();
m_title = "File Sink";
m_log2Decim = 0;
m_spectrumGUI = nullptr;
m_spectrumSquelchMode = false;
m_spectrumSquelch = -50;
m_preRecordTime = 0;
m_squelchPostRecordTime = 0;
m_squelchRecordingEnable = false;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
}
QByteArray FileSinkSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeBool(2, m_ncoMode);
s.writeString(3, m_fileRecordName);
s.writeS32(4, m_streamIndex);
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);
s.writeU32(12, m_log2Decim);
if (m_spectrumGUI) {
s.writeBlob(13, m_spectrumGUI->serialize());
}
s.writeBool(14, m_spectrumSquelchMode);
s.writeS32(15, m_spectrumSquelch);
s.writeS32(16, m_preRecordTime);
s.writeS32(17, m_squelchPostRecordTime);
s.writeBool(18, m_squelchRecordingEnable);
return s.final();
}
bool FileSinkSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
uint32_t tmp;
int stmp;
QString strtmp;
QByteArray bytetmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readBool(2, &m_ncoMode, false);
d.readString(3, &m_fileRecordName, "");
d.readS32(4, &m_streamIndex, 0);
d.readU32(5, &m_rgbColor, QColor(0, 255, 255).rgb());
d.readString(6, &m_title, "File Sink");
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;
d.readU32(12, &tmp, 0);
m_log2Decim = tmp > 6 ? 6 : tmp;
if (m_spectrumGUI)
{
d.readBlob(13, &bytetmp);
m_spectrumGUI->deserialize(bytetmp);
}
d.readBool(14, &m_spectrumSquelchMode, false);
d.readS32(15, &stmp, -50);
m_spectrumSquelch = stmp;
d.readS32(16, &m_preRecordTime, 0);
d.readS32(17, &m_squelchPostRecordTime, 0);
d.readBool(18, &m_squelchRecordingEnable, false);
return true;
}
else
{
resetToDefaults();
return false;
}
}
unsigned int FileSinkSettings::getNbFixedShiftIndexes(int log2Decim)
{
int decim = (1<<log2Decim);
return 2*decim - 1;
}
int FileSinkSettings::getHalfBand(int sampleRate, int log2Decim)
{
int decim = (1<<log2Decim);
return sampleRate / (2*decim);
}
unsigned int FileSinkSettings::getFixedShiftIndexFromOffset(int sampleRate, int log2Decim, int frequencyOffset)
{
if (sampleRate == 0) {
return 0;
}
int decim = (1<<log2Decim);
int mid = decim - 1;
return ((frequencyOffset*2*decim) / sampleRate) + mid;
}
int FileSinkSettings::getOffsetFromFixedShiftIndex(int sampleRate, int log2Decim, int shiftIndex)
{
int decim = (1<<log2Decim);
int mid = decim - 1;
return ((shiftIndex - mid) * sampleRate) / (2*decim);
}

View File

@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINKSETTINGS_H_
#define INCLUDE_FILESINKSETTINGS_H_
#include <QByteArray>
#include <QString>
class Serializable;
struct FileSinkSettings
{
bool m_ncoMode;
qint32 m_inputFrequencyOffset;
QString m_fileRecordName;
quint32 m_rgbColor;
QString m_title;
uint32_t m_log2Decim;
bool m_spectrumSquelchMode;
float m_spectrumSquelch;
int m_preRecordTime;
int m_squelchPostRecordTime;
bool m_squelchRecordingEnable;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
Serializable *m_spectrumGUI;
FileSinkSettings();
void resetToDefaults();
void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static unsigned int getNbFixedShiftIndexes(int log2Decim);
static int getHalfBand(int sampleRate, int log2Decim);
static unsigned int getFixedShiftIndexFromOffset(int sampleRate, int log2Decim, int frequencyOffset);
static int getOffsetFromFixedShiftIndex(int sampleRate, int log2Decim, int shiftIndex);
};
#endif /* INCLUDE_FILESINKSETTINGS_H_ */

View File

@ -0,0 +1,305 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 <QDebug>
#include "dsp/dspcommands.h"
#include "dsp/sigmffilerecord.h"
#include "dsp/spectrumvis.h"
#include "filesinkmessages.h"
#include "filesinksink.h"
FileSinkSink::FileSinkSink() :
m_spectrumSink(nullptr),
m_msgQueueToGUI(nullptr),
m_nbCaptures(0),
m_preRecordBuffer(48000),
m_preRecordFill(0),
m_recordEnabled(false),
m_record(false),
m_squelchOpen(false),
m_postSquelchCounter(0),
m_msCount(0),
m_byteCount(0)
{}
FileSinkSink::~FileSinkSink()
{}
void FileSinkSink::startRecording()
{
if (m_recordEnabled) // File is open for writing and valid
{
// set the length of pre record time
qint64 mSShift = (m_preRecordFill * 1000) / m_sinkSampleRate;
m_fileSink.setMsShift(-mSShift);
// notify capture start
m_fileSink.startRecording();
m_record = true;
m_nbCaptures++;
if (m_msgQueueToGUI)
{
FileSinkMessages::MsgReportRecordFileName *msg
= FileSinkMessages::MsgReportRecordFileName::create(m_fileSink.getCurrentFileName());
m_msgQueueToGUI->push(msg);
}
// copy pre record samples
SampleVector::iterator p1Begin, p1End, p2Begin, p2End;
m_preRecordBuffer.readBegin(m_preRecordFill, &p1Begin, &p1End, &p2Begin, &p2End);
if (p1Begin != p1End) {
m_fileSink.feed(p1Begin, p1End, false);
}
if (p2Begin != p2End) {
m_fileSink.feed(p2Begin, p2End, false);
}
m_byteCount += m_preRecordFill * sizeof(Sample);
if (m_sinkSampleRate > 0) {
m_msCount += (m_preRecordFill * 1000) / m_sinkSampleRate;
}
}
}
void FileSinkSink::stopRecording()
{
if (m_record)
{
m_preRecordBuffer.reset();
m_fileSink.stopRecording();
m_record = false;
}
}
void FileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
SampleVector::const_iterator beginw = begin;
SampleVector::const_iterator endw = end;
if (m_decimator.getDecim() != 1)
{
for (SampleVector::const_iterator it = begin; it < end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
Complex ci;
if (m_decimator.decimate(c, ci)) {
m_sampleBuffer.push_back(Sample(ci.real(), ci.imag()));
}
}
beginw = m_sampleBuffer.begin();
endw = m_sampleBuffer.end();
}
if (!m_record && (m_settings.m_preRecordTime != 0)) {
m_preRecordFill = m_preRecordBuffer.write(beginw, endw);
}
if (m_settings.m_squelchRecordingEnable)
{
int nbToWrite = endw - beginw;
if (m_squelchOpen)
{
m_fileSink.feed(beginw, endw, true);
}
else
{
if (nbToWrite < m_postSquelchCounter)
{
m_fileSink.feed(beginw, endw, true);
m_postSquelchCounter -= nbToWrite;
}
else
{
if (m_msgQueueToGUI)
{
FileSinkMessages::MsgReportRecording *msg = FileSinkMessages::MsgReportRecording::create(false);
m_msgQueueToGUI->push(msg);
}
m_fileSink.feed(beginw, endw + m_postSquelchCounter, true);
nbToWrite = m_postSquelchCounter;
m_postSquelchCounter = 0;
stopRecording();
}
}
m_byteCount += nbToWrite * sizeof(Sample);
if (m_sinkSampleRate > 0) {
m_msCount += (nbToWrite * 1000) / m_sinkSampleRate;
}
}
else if (m_record)
{
m_fileSink.feed(beginw, endw, true);
int nbSamples = endw - beginw;
m_byteCount += nbSamples * sizeof(Sample);
if (m_sinkSampleRate > 0) {
m_msCount += (nbSamples * 1000) / m_sinkSampleRate;
}
}
if (m_spectrumSink) {
m_spectrumSink->feed(beginw, endw, false);
}
if (m_decimator.getDecim() != 1) {
m_sampleBuffer.clear();
}
}
void FileSinkSink::applyChannelSettings(
int channelSampleRate,
int sinkSampleRate,
int channelFrequencyOffset,
int64_t centerFrequency,
bool force)
{
qDebug() << "FileSinkSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " sinkSampleRate: " << sinkSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset
<< " centerFrequency: " << centerFrequency
<< " force: " << force;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate)
|| (m_sinkSampleRate != sinkSampleRate) || force)
{
int decim = channelSampleRate / sinkSampleRate;
for (int i = 0; i < 7; i++) // find log2 beween 0 and 6
{
if (decim & 1 == 1)
{
qDebug() << "FileSinkSink::applyChannelSettings: log2decim: " << i;
m_decimator.setLog2Decim(i);
break;
}
decim >>= 1;
}
}
if ((m_centerFrequency != centerFrequency)
|| (m_channelFrequencyOffset != channelFrequencyOffset)
|| (m_sinkSampleRate != sinkSampleRate) || force)
{
DSPSignalNotification *notif = new DSPSignalNotification(sinkSampleRate, centerFrequency);
DSPSignalNotification *notifToSpectrum = new DSPSignalNotification(*notif);
m_fileSink.getInputMessageQueue()->push(notif);
m_spectrumSink->getInputMessageQueue()->push(notifToSpectrum);
if (m_msgQueueToGUI)
{
FileSinkMessages::MsgConfigureSpectrum *msg = FileSinkMessages::MsgConfigureSpectrum::create(
centerFrequency, sinkSampleRate);
m_msgQueueToGUI->push(msg);
}
}
if ((m_sinkSampleRate != sinkSampleRate) || force) {
m_preRecordBuffer.setSize(m_settings.m_preRecordTime * sinkSampleRate);
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_sinkSampleRate = sinkSampleRate;
m_centerFrequency = centerFrequency;
m_preRecordBuffer.reset();
}
void FileSinkSink::applySettings(const FileSinkSettings& settings, bool force)
{
qDebug() << "FileSinkSink::applySettings:"
<< "m_fileRecordName: " << settings.m_fileRecordName
<< "force: " << force;
if ((settings.m_fileRecordName != m_settings.m_fileRecordName) || force)
{
QString fileBase;
FileRecordInterface::RecordType recordType = FileRecordInterface::guessTypeFromFileName(settings.m_fileRecordName, fileBase);
if (recordType == FileRecordInterface::RecordTypeSdrIQ)
{
m_fileSink.setFileName(fileBase);
m_msCount = 0;
m_byteCount = 0;
m_nbCaptures = 0;
m_recordEnabled = true;
}
else
{
m_recordEnabled = false;
}
}
if ((settings.m_preRecordTime != m_settings.m_squelchPostRecordTime) || force)
{
m_preRecordBuffer.setSize(settings.m_preRecordTime * m_sinkSampleRate);
if (settings.m_preRecordTime == 0) {
m_preRecordFill = 0;
}
}
m_settings = settings;
}
void FileSinkSink::squelchRecording(bool squelchOpen)
{
if (!m_recordEnabled || !m_settings.m_squelchRecordingEnable) {
return;
}
if (squelchOpen)
{
if (!m_record)
{
startRecording();
if (m_msgQueueToGUI)
{
FileSinkMessages::MsgReportRecording *msg = FileSinkMessages::MsgReportRecording::create(true);
m_msgQueueToGUI->push(msg);
}
}
}
else
{
m_postSquelchCounter = m_settings.m_squelchPostRecordTime * m_sinkSampleRate;
}
m_squelchOpen = squelchOpen;
}

View File

@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINKSINK_H_
#define INCLUDE_FILESINKSINK_H_
#include "dsp/channelsamplesink.h"
#include "dsp/filerecord.h"
#include "dsp/decimatorc.h"
#include "dsp/samplesimplefifo.h"
#include "dsp/ncof.h"
#include "filesinksettings.h"
class FileRecordInterface;
class SpectrumVis;
class FileSinkSink : public ChannelSampleSink {
public:
FileSinkSink();
~FileSinkSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
FileRecord *getFileSink() { return &m_fileSink; }
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; }
void startRecording();
void stopRecording();
void setDeviceHwId(const QString& hwId) { m_deviceHwId = hwId; }
void setDeviceUId(int uid) { m_deviceUId = uid; }
void applyChannelSettings(
int channelSampleRate,
int sinkSampleRate,
int channelFrequencyOffset,
int64_t centerFrequency,
bool force = false);
void applySettings(const FileSinkSettings& settings, bool force = false);
uint64_t getMsCount() const { return m_msCount; }
uint64_t getByteCount() const { return m_byteCount; }
unsigned int getNbTracks() const { return m_nbCaptures; }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; }
void squelchRecording(bool squelchOpen);
int getSampleRate() const { return m_sinkSampleRate; }
bool isRecording() const { return m_record; }
private:
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_sinkSampleRate;
int64_t m_centerFrequency;
NCOF m_nco;
DecimatorC m_decimator;
SampleVector m_sampleBuffer;
FileSinkSettings m_settings;
FileRecord m_fileSink;
unsigned int m_nbCaptures;
SampleSimpleFifo m_preRecordBuffer;
unsigned int m_preRecordFill;
float m_squelchLevel;
SpectrumVis* m_spectrumSink;
MessageQueue *m_msgQueueToGUI;
bool m_recordEnabled;
bool m_record;
bool m_squelchOpen;
int m_postSquelchCounter;
QString m_deviceHwId;
int m_deviceUId;
uint64_t m_msCount;
uint64_t m_byteCount;
};
#endif // INCLUDE_FILESINKSINK_H_

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "SWGChannelSettings.h"
#include "filesink.h"
#include "filesinkwebapiadapter.h"
FileSinkWebAPIAdapter::FileSinkWebAPIAdapter()
{}
FileSinkWebAPIAdapter::~FileSinkWebAPIAdapter()
{}
int FileSinkWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
(void) response;
response.setSigMfFileSinkSettings(new SWGSDRangel::SWGSigMFFileSinkSettings());
response.getSigMfFileSinkSettings()->init();
FileSink::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int FileSinkWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
FileSink::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 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 INCLUDE_FILESINK_WEBAPIADAPTER_H
#define INCLUDE_FILESINK_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "filesinksettings.h"
/**
* Standalone API adapter only for the settings
*/
class FileSinkWebAPIAdapter : public ChannelWebAPIAdapter {
public:
FileSinkWebAPIAdapter();
virtual ~FileSinkWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
FileSinkSettings m_settings;
};
#endif // INCLUDE_FILESINK_WEBAPIADAPTER_H

View File

@ -0,0 +1,99 @@
<h1>File Recorder</h1>
<h2>Introduction</h2>
Use this plugin to record its channel IQ data in [sdriq](../../samplesource/fileinput/readme.md#introduction) format. The baseband sample rate can be decimated by a factor of two and its center shifted to accomodate different requirements than recording the full baseband. More than one such plugin can be used in the same baseband to record different parts of the baseband spectrum. Of course in this case file output collision should be avoided.
Such files can be read in SDRangel using the [File input plugin](../../samplesource/fileinput/readme.md).
Each recording is written in a new file with the starting timestamp before the `.sdriq` extension in `yyyy-MM-ddTHH_mm_ss_zzz` format. It keeps the first dot limted groups of the filename before the `.sdriq` extension if there are two such groups or before the two last groups if there are more than two groups. Examples:
- Given file name: `test.sdriq` then a recording file will be like: `test.2020-08-05T21_39_07_974.sdriq`
- Given file name: `test.2020-08-05T20_36_15_974.sdriq` then a recording file will be like (with timestamp updated): `test.2020-08-05T21_41_21_173.sdriq`
- Given file name: `test.first.sdriq` then a recording file will be like: `test.2020-08-05T22_00_07_974.sdriq`
- Given file name: `record.test.first.sdriq` then a recording file will be like: `reocrd.test.2020-08-05T21_39_52_974.sdriq`
<h2>Interface</h2>
![File Sink plugin GUI](../../../doc/img/FileSink_plugin.png)
<h3>1: Frequency shift from center frequency of reception</h3>
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
If the fixed position slider is engaged (7) this control is disabled.
<h3>2: Decimation factor</h3>
Use this control to decimate the baseband samples by a power of two. Consequently the baseband sample rate is reduced by this factor in the channel.
<h3>3: Channel (sink) sample rate</h3>
Shows the channel sink sample rate in kS/s. The recod capture is effectively recorded at this rate.
<h3>4: Number of recordings in session</h3>
Adding a new File Sink plugin instance or changing the file name starts a new session. This is the number of files recorded in the session (not counting the current one if recording).
<h3>5: Recording time</h3>
This is the total recording time for the session.
<h3>6: Record size</h3>
This is the total number of bytes recorded for the session.The number is possibly suffixed by a multiplier character:
- **k**: _kilo_ for kilobytes
- **M**: _mega_ for meabytes
- **G**: _giga_ for gigabytes
<h3>7: Fixed frequency shift positions</h3>
Use the checkbox to move the shift frequency at definite positions where the chain of half band decimation filters match an exact bandwidth and shift. The effect is to bypass the last interpolator and NCO and thus can save CPU cycles. This may be useful at high sample rates at the expense of not getting exactly on the desired spot.
Use the slider to move position from lower to higher frequency. The position index appears on the right. The higher the decimation factor the more positions are available as the decimated bandwidth gets smaller.
When this is engaged the frequency control (1) is disabled.
This is a GUI only feature when using API it is up to the API client to calculate the desired position. Starting from the center any position lower or higher at the bandwidth divided by two times the decimation factor yields the desired property.
<h3>8: Spectrum squelch</h3>
Recording can be triggered by specifying a power level. If any peak in the spectrum exceeds this level then recording is triggered. This button only activates the detection system. When squelch is open the level button (9) lits up in green (as on the screenshot). To activate triggered recording you have to use (12).
You can try to see for which squelch level you obtain the desired triggering before actually applying it to control the record process with button (12).
Note that spectrum polling is done every 200 ms. If the signal of interest is shorter you may want to tweak the spectrum controls using the "Maximum" averaging type and a number of averaging samples making the averaging period (appearing in the tooltip) larger than 200 ms. Please refer to spectrum controls documentation in the main window readme (link in 15) for more details.
<h3>9: Squelch level</h3>
This is the squelch level as discussed above. To try to find the correct value you can use the spectrum display (15).
<h3>10: Pre recording period<h3>
This is the number of seconds of data that will be prepended before the start of recording point. Thus you can make sure that the signal of interest will be fully recorded. Works in both spectrum squelch triggered and manual mode.
<h3>11: Post recording period</h3>
This applies to spectrum squelch triggered recording only. This is the number of seconds recorded after the squelch closes. If the squelch opens again during this period then the counter is reset and recording will stop only after this period of time is elapsed without the squelch re-opening.
This is useful if you want to record a bunch of transient bursts or just make sure that the recording does not stop too abruptly.
<h3>12: Enable/disble spectrum squelch triggered recording</h3>
Use this button to effectively apply spectrum squelch to recording. In this mode recording on and off will be under the control of the squelch system. Thus when active the normal record button (13) is disabled. However its color changes to reflect the recording status as described next.
<h3>13: Record button</h3>
Use this button to start/stop recording unconditionnaly. Note that start/stop recording is opening/closing a new file in the same session. Until the file is changed with (14) the same file root will be used until the device is stopped or channel plugin is dismissed.
The button turns red if recording is active.
<h3>14: Select output file</h3>
Use this button to open a file dialog that lets you specify the location and name of the output files. Please refer to the indtroduction at the top of this page for details on how the recording file name is composed from the given file name.
The file path currently being written (or last closed) appears at the right of the button.
<h3>15: Channel spectrum</h3>
This is the spectrum display of the IQ stream seen by the channel. It is the same as all spectrum displays in the program and is identical to the [main window](../../../sdrgui/readme.md#) spectrum display.

View File

@ -100,6 +100,7 @@ set(sdrbase_SOURCES
dsp/filterrc.cpp
dsp/filtermbe.cpp
dsp/filerecord.cpp
dsp/filerecordinterface.cpp
dsp/freqlockcomplex.cpp
dsp/interpolator.cpp
dsp/glscopesettings.cpp
@ -230,6 +231,7 @@ set(sdrbase_HEADERS
dsp/filterrc.h
dsp/filtermbe.h
dsp/filerecord.h
dsp/filerecordinterface.h
dsp/freqlockcomplex.h
dsp/gfft.h
dsp/glscopesettings.h

View File

@ -28,20 +28,21 @@
#include "filerecord.h"
FileRecord::FileRecord() :
BasebandSampleSink(),
m_fileName("test.sdriq"),
FileRecordInterface(),
m_fileBase("test"),
m_sampleRate(0),
m_centerFrequency(0),
m_recordOn(false),
m_recordStart(false),
m_byteCount(0)
m_byteCount(0),
m_msShift(0)
{
setObjectName("FileRecord");
}
FileRecord::FileRecord(const QString& filename) :
BasebandSampleSink(),
m_fileName(filename),
FileRecord::FileRecord(const QString& fileBase) :
FileRecordInterface(),
m_fileBase(fileBase),
m_sampleRate(0),
m_centerFrequency(0),
m_recordOn(false),
@ -56,11 +57,11 @@ FileRecord::~FileRecord()
stopRecording();
}
void FileRecord::setFileName(const QString& filename)
void FileRecord::setFileName(const QString& fileBase)
{
if (!m_recordOn)
{
m_fileName = filename;
m_fileBase = fileBase;
}
}
@ -104,13 +105,19 @@ void FileRecord::stop()
void FileRecord::startRecording()
{
if (m_recordOn) {
stopRecording();
}
if (!m_sampleFile.is_open())
{
qDebug() << "FileRecord::startRecording";
m_sampleFile.open(m_fileName.toStdString().c_str(), std::ios::binary);
m_curentFileName = QString("%1.%2.sdriq").arg(m_fileBase).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"));
m_sampleFile.open(m_curentFileName.toStdString().c_str(), std::ios::binary);
m_recordOn = true;
m_recordStart = true;
m_byteCount = 0;
writeHeader();
}
}
@ -134,6 +141,11 @@ bool FileRecord::handleMessage(const Message& message)
m_centerFrequency = notif.getCenterFrequency();
qDebug() << "FileRecord::handleMessage: DSPSignalNotification: m_inputSampleRate: " << m_sampleRate
<< " m_centerFrequency: " << m_centerFrequency;
if (m_recordOn) {
startRecording();
}
return true;
}
else
@ -142,23 +154,13 @@ bool FileRecord::handleMessage(const Message& message)
}
}
void FileRecord::handleConfigure(const QString& fileName)
{
if (fileName != m_fileName)
{
stopRecording();
}
m_fileName = fileName;
}
void FileRecord::writeHeader()
{
Header header;
header.sampleRate = m_sampleRate;
header.centerFrequency = m_centerFrequency;
std::time_t ts = time(0);
header.startTimeStamp = ts;
header.startTimeStamp = ts + (m_msShift / 1000);
header.sampleSize = SDR_RX_SAMP_SZ;
header.filler = 0;

View File

@ -22,13 +22,14 @@
#include <string>
#include <iostream>
#include <fstream>
#include <ctime>
#include "dsp/filerecordinterface.h"
#include "export.h"
class Message;
class SDRBASE_API FileRecord : public BasebandSampleSink {
class SDRBASE_API FileRecord : public FileRecordInterface {
public:
#pragma pack(push, 1)
@ -44,34 +45,39 @@ public:
#pragma pack(pop)
FileRecord();
FileRecord(const QString& filename);
FileRecord(const QString& fileBase);
virtual ~FileRecord();
quint64 getByteCount() const { return m_byteCount; }
void setMsShift(int shift) { m_msShift = shift; }
const QString& getCurrentFileName() { return m_curentFileName; }
void setFileName(const QString& filename);
void genUniqueFileName(uint deviceUID, int istream = -1);
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& message);
void startRecording();
void stopRecording();
bool isRecording() const { return m_recordOn; }
virtual void setFileName(const QString& fileBase);
virtual void startRecording();
virtual void stopRecording();
virtual bool isRecording() const { return m_recordOn; }
static bool readHeader(std::ifstream& samplefile, Header& header); //!< returns true if CRC checksum is correct else false
static void writeHeader(std::ofstream& samplefile, Header& header);
private:
QString m_fileName;
QString m_fileBase;
quint32 m_sampleRate;
quint64 m_centerFrequency;
bool m_recordOn;
bool m_recordStart;
std::ofstream m_sampleFile;
QString m_curentFileName;
quint64 m_byteCount;
int m_msShift;
void handleConfigure(const QString& fileName);
void writeHeader();
};

View File

@ -0,0 +1,74 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// File recorder in SigMF format single channel for SI plugins //
// //
// 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 <QDateTime>
#include "filerecordinterface.h"
FileRecordInterface::FileRecordInterface() :
BasebandSampleSink()
{}
FileRecordInterface::~FileRecordInterface()
{}
QString FileRecordInterface::genUniqueFileName(unsigned int deviceUID, int istream)
{
if (istream < 0) {
return QString("rec%1.%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"));
} else {
return QString("rec%1_%2.%3.sdriq").arg(deviceUID).arg(istream).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"));
}
}
FileRecordInterface::RecordType FileRecordInterface::guessTypeFromFileName(const QString& fileName, QString& fileBase)
{
QStringList dotBreakout = fileName.split(QLatin1Char('.'));
if (dotBreakout.length() > 1)
{
QString extension = dotBreakout.last();
dotBreakout.removeLast();
if (extension == "sdriq")
{
if (dotBreakout.length() > 1) {
dotBreakout.removeLast();
}
fileBase = dotBreakout.join(QLatin1Char('.'));
return RecordTypeSdrIQ;
}
else if (extension == "sigmf-meta")
{
fileBase = dotBreakout.join(QLatin1Char('.'));
return RecordTypeSigMF;
}
else
{
fileBase = fileName;
return RecordTypeUndefined;
}
}
else
{
fileBase = fileName;
return RecordTypeUndefined;
}
}

View File

@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// File recorder in SigMF format single channel for SI plugins //
// //
// 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 INCLUDE_FILERECORD_INTERFACE_H
#define INCLUDE_FILERECORD_INTERFACE_H
#include <QString>
#include "dsp/basebandsamplesink.h"
#include "export.h"
class SDRBASE_API FileRecordInterface : public BasebandSampleSink {
public:
enum RecordType
{
RecordTypeUndefined = 0,
RecordTypeSdrIQ,
RecordTypeSigMF
};
FileRecordInterface();
virtual ~FileRecordInterface();
virtual void start() = 0;
virtual void stop() = 0;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) = 0;
virtual bool handleMessage(const Message& cmd) = 0; //!< Processing of a message. Returns true if message has actually been processed
virtual void setFileName(const QString &filename) = 0;
virtual void startRecording() = 0;
virtual void stopRecording() = 0;
virtual bool isRecording() const = 0;
static QString genUniqueFileName(unsigned int deviceUID, int istream = -1);
static RecordType guessTypeFromFileName(const QString& fileName, QString& fileBase);
};
#endif // INCLUDE_FILERECORD_INTERFACE_H