1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-29 19:28:47 -05:00

SigMF file sink

This commit is contained in:
f4exb 2020-11-10 11:06:39 +01:00
parent a1c2697657
commit c353c85bdd
20 changed files with 3581 additions and 0 deletions

View File

@ -26,6 +26,10 @@ if (CODEC2_FOUND)
add_subdirectory(demodfreedv)
endif(CODEC2_FOUND)
if (LIBSIGMF_FOUND)
add_subdirectory(sigmffilesink)
endif(LIBSIGMF_FOUND)
if(NOT SERVER_MODE)
add_subdirectory(chanalyzer)
add_subdirectory(demodatv)

View File

@ -0,0 +1,61 @@
project(sigmffilesink)
set(sigmffilesink_SOURCES
sigmffilesink.cpp
sigmffilesinkbaseband.cpp
sigmffilesinksink.cpp
sigmffilesinksettings.cpp
sigmffilesinkmessages.cpp
sigmffilesinkwebapiadapter.cpp
sigmffilesinkplugin.cpp
)
set(sigmffilesink_HEADERS
sigmffilesink.h
sigmffilesinkbaseband.h
sigmffilesinksink.h
sigmffilesinksettings.h
sigmffilesinkmessages.h
sigmffilesinkwebapiadapter.h
sigmffilesinkplugin.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(sigmffilesink_SOURCES
${sigmffilesink_SOURCES}
sigmffilesinkgui.cpp
sigmffilesinkgui.ui
)
set(sigmffilesink_HEADERS
${sigmffilesink_HEADERS}
sigmffilesinkgui.h
)
set(TARGET_NAME sigmffilesink)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME sigmffilesinksrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${sigmffilesink_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,98 @@
<h1>SigMF File Recorder</h1>
<h2>Introduction</h2>
Use this plugin to record its channel IQ data in [SigMF](https://github.com/gnuradio/SigMF/blob/master/sigmf-spec.md) 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 [SigMF file input plugin](../../samplesource/sigmffileinput/readme.md). This plugin will use extensions to the basic SigMF specification that are specific to SDRangel. However any other software if correctly implemented should ignore these extensions and still be able to read the file possibily with a loss in functionnality.
As per SigMF specifications two files are created in fact.
- One with `.sigmf-meta` extension contains meta data and details to find the different captures in the data file blob. It is written in JSON format and is human readable. You can refer to SigMF documentation in the link at top to read about the details.
- Another with `.sigmf-data` contains the IQ data as a blob indexed by structures in the `.sigmf-meta` file. Thus to the SigMF file reader data appears as a sequence of captures having idependent start time and length, center frequency and with SDRangel specific extensions independent sample rates.
It adds a dependency to the [libsigmf library](https://github.com/f4exb/libsigmf) more specifically the `f4exb` fork that supports `multirecordings` and `sdrangel` extensions.
<h2>Interface</h2>
![SigMF File Sink plugin GUI](../../../doc/img/SigMFFileSink_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 record captures</h3>
SigMF can collate many sequences or captures in the same data file (it uses an index in its meta data). This is the number of captures already recorded (not counting the current one if recording).
<h3>5: Recording time</h3>
This is the current recording time of the whole file (all captures)
<h3>6: Record size</h3>
This is the total number of bytes including all captures. This corresponds to the size of the `.sigmf-data` file. 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. Note that with SigMF start/stop recording is starting/stopping a new capture in the same file. Until the file is changed with (14) the same file 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. SigMF creates two files actually one meta file with extension `.sigmf-meta` and one data file with extension `.sigmf-data` that contains the actual IQ data. There you specify the name of the meta file (`.sigmf-meta` extension) and a data file with `.sigmf-data` extension will be created using the same root name as the meta file.
The path of the selected meta file appears at the right of the button. If it is empty or invalid recording will not be effective.
<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

@ -0,0 +1,651 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "feature/feature.h"
#include "maincore.h"
#include "sigmffilesinkmessages.h"
#include "sigmffilesinkbaseband.h"
#include "sigmffilesink.h"
MESSAGE_CLASS_DEFINITION(SigMFFileSink::MsgConfigureSigMFFileSink, Message)
const QString SigMFFileSink::m_channelIdURI = "sdrangel.channel.sigmffilesink";
const QString SigMFFileSink::m_channelId = "SigMFFileSink";
SigMFFileSink::SigMFFileSink(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 SigMFFileSinkBaseband();
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*)));
}
SigMFFileSink::~SigMFFileSink()
{
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 SigMFFileSink::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void SigMFFileSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void SigMFFileSink::start()
{
qDebug("SigMFFileSink::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);
SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void SigMFFileSink::stop()
{
qDebug("SigMFFileSink::stop");
m_basebandSink->stopWork();
m_thread.exit();
m_thread.wait();
}
bool SigMFFileSink::handleMessage(const Message& cmd)
{
if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& cfg = (DSPSignalNotification&) cmd;
qDebug() << "SigMFFileSink::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 (MsgConfigureSigMFFileSink::match(cmd))
{
MsgConfigureSigMFFileSink& cfg = (MsgConfigureSigMFFileSink&) cmd;
qDebug() << "SigMFFileSink::handleMessage: MsgConfigureSigMFFileSink";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else
{
return false;
}
}
QByteArray SigMFFileSink::serialize() const
{
return m_settings.serialize();
}
bool SigMFFileSink::deserialize(const QByteArray& data)
{
(void) data;
if (m_settings.deserialize(data))
{
MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void SigMFFileSink::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 *SigMFFileSink::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("SigMFFileSink::getLocalDevice: the parent device is unset");
} else if (getDeviceAPI()->getDeviceUID() == deviceSourceEngine->getUID()) {
qDebug("SigMFFileSink::getLocalDevice: source device at index %u is the parent device", index);
} else {
return deviceSource;
}
}
else
{
qDebug("SigMFFileSink::getLocalDevice: source device at index %u is not a SigMF File sink", index);
}
}
else
{
qDebug("SigMFFileSink::getLocalDevice: non existent source device index: %u", index);
}
return nullptr;
}
void SigMFFileSink::applySettings(const SigMFFileSinkSettings& settings, bool force)
{
qDebug() << "SigMFFileSink::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");
}
SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband::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);
}
if (m_featuresSettingsFeedback.size() > 0) {
featuresSendSettings(reverseAPIKeys, settings, force);
}
m_settings = settings;
}
void SigMFFileSink::record(bool record)
{
SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork::create(record);
m_basebandSink->getInputMessageQueue()->push(msg);
}
uint64_t SigMFFileSink::getMsCount() const
{
if (m_basebandSink) {
return m_basebandSink->getMsCount();
} else {
return 0;
}
}
uint64_t SigMFFileSink::getByteCount() const
{
if (m_basebandSink) {
return m_basebandSink->getByteCount();
} else {
return 0;
}
}
unsigned int SigMFFileSink::getNbTracks() const
{
if (m_basebandSink) {
return m_basebandSink->getNbTracks();
} else {
return 0;
}
}
int SigMFFileSink::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSigMfFileSinkSettings(new SWGSDRangel::SWGSigMFFileSinkSettings());
response.getSigMfFileSinkSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int SigMFFileSink::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
SigMFFileSinkSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("SigMFFileSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureSigMFFileSink *msgToGUI = MsgConfigureSigMFFileSink::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
int SigMFFileSink::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSigMfFileSinkReport(new SWGSDRangel::SWGSigMFFileSinkReport());
response.getSigMfFileSinkReport()->init();
webapiFormatChannelReport(response);
return 200;
}
int SigMFFileSink::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)
{
SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork::create(record);
m_basebandSink->getInputMessageQueue()->push(msg);
if (getMessageQueueToGUI())
{
SigMFFileSinkMessages::MsgReportRecording *msgToGUI = SigMFFileSinkMessages::MsgReportRecording::create(record);
getMessageQueueToGUI()->push(msgToGUI);
}
}
}
return 202;
}
else
{
errorMessage = "Missing SigMFFileSinkActions in query";
return 400;
}
}
void SigMFFileSink::webapiUpdateChannelSettings(
SigMFFileSinkSettings& 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 SigMFFileSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SigMFFileSinkSettings& 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 SigMFFileSink::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 SigMFFileSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SigMFFileSinkSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
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 SigMFFileSink::featuresSendSettings(QList<QString>& channelSettingsKeys, const SigMFFileSinkSettings& settings, bool force)
{
QList<Feature*>::iterator it = m_featuresSettingsFeedback.begin();
MainCore *mainCore = MainCore::instance();
for (; it != m_featuresSettingsFeedback.end(); ++it)
{
if (mainCore->existsFeature(*it))
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
Feature::MsgChannelSettings *msg = Feature::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
(*it)->getInputMessageQueue()->push(msg);
}
else
{
m_featuresSettingsFeedback.removeOne(*it);
}
}
}
void SigMFFileSink::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const SigMFFileSinkSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString(m_channelId));
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);
}
}
void SigMFFileSink::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "SigMFFileSink::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("SigMFFileSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SIGMFFILESINK_H_
#define INCLUDE_SIGMFFILESINK_H_
#include <QObject>
#include <QThread>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesink.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h"
#include "sigmffilesinksettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class DeviceAPI;
class DeviceSampleSource;
class SigMFFileSinkBaseband;
class SigMFFileSink : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureSigMFFileSink : public Message {
MESSAGE_CLASS_DECLARATION
public:
const SigMFFileSinkSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureSigMFFileSink* create(const SigMFFileSinkSettings& settings, bool force)
{
return new MsgConfigureSigMFFileSink(settings, force);
}
private:
SigMFFileSinkSettings m_settings;
bool m_force;
MsgConfigureSigMFFileSink(const SigMFFileSinkSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
SigMFFileSink(DeviceAPI *deviceAPI);
virtual ~SigMFFileSink();
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 = m_channelId; }
virtual const QString& getURI() const { return m_channelIdURI; }
virtual void getTitle(QString& title) { title = "SigMF 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 SigMFFileSinkSettings& settings);
static void webapiUpdateChannelSettings(
SigMFFileSinkSettings& 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;
SigMFFileSinkBaseband *m_basebandSink;
SigMFFileSinkSettings 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 SigMFFileSinkSettings& 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 SigMFFileSinkSettings& settings, bool force);
void featuresSendSettings(QList<QString>& channelSettingsKeys, const SigMFFileSinkSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const SigMFFileSinkSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif /* INCLUDE_SIGMFFILESINK_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 "sigmffilesinkmessages.h"
#include "sigmffilesinkbaseband.h"
MESSAGE_CLASS_DEFINITION(SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband, Message)
MESSAGE_CLASS_DEFINITION(SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork, Message)
SigMFFileSinkBaseband::SigMFFileSinkBaseband() :
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("SigMFFileSinkBaseband::SigMFFileSinkBaseband");
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(200);
}
SigMFFileSinkBaseband::~SigMFFileSinkBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void SigMFFileSinkBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void SigMFFileSinkBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&SigMFFileSinkBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void SigMFFileSinkBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&SigMFFileSinkBaseband::handleData
);
m_running = false;
}
void SigMFFileSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void SigMFFileSinkBaseband::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 SigMFFileSinkBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool SigMFFileSinkBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureSigMFFileSinkBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureSigMFFileSinkBaseband& cfg = (MsgConfigureSigMFFileSinkBaseband&) cmd;
qDebug() << "SigMFFileSinkBaseband::handleMessage: MsgConfigureSigMFFileSinkBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "SigMFFileSinkBaseband::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 (MsgConfigureSigMFFileSinkWork::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureSigMFFileSinkWork& conf = (MsgConfigureSigMFFileSinkWork&) cmd;
qDebug() << "SigMFFileSinkBaseband::handleMessage: MsgConfigureSigMFFileSinkWork: " << conf.isWorking();
if (!m_settings.m_squelchRecordingEnable)
{
if (conf.isWorking()) {
m_sink.startRecording();
} else {
m_sink.stopRecording();
}
}
return true;
}
else
{
return false;
}
}
void SigMFFileSinkBaseband::applySettings(const SigMFFileSinkSettings& settings, bool force)
{
qDebug() << "SigMFFileSinkBaseband::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 SigMFFileSinkBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void SigMFFileSinkBaseband::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)
{
SigMFFileSinkMessages::MsgReportSquelch *msg = SigMFFileSinkMessages::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_SIFMFFILESINKBASEBAND_H_
#define INCLUDE_SIFMFFILESINKBASEBAND_H_
#include <QObject>
#include <QMutex>
#include <QTimer>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "sigmffilesinksink.h"
#include "sigmffilesinksettings.h"
class DownChannelizer;
class SpectrumVis;
class SigMFFileSinkBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureSigMFFileSinkBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const SigMFFileSinkSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureSigMFFileSinkBaseband* create(const SigMFFileSinkSettings& settings, bool force)
{
return new MsgConfigureSigMFFileSinkBaseband(settings, force);
}
private:
SigMFFileSinkSettings m_settings;
bool m_force;
MsgConfigureSigMFFileSinkBaseband(const SigMFFileSinkSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureSigMFFileSinkWork : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool isWorking() const { return m_working; }
static MsgConfigureSigMFFileSinkWork* create(bool working)
{
return new MsgConfigureSigMFFileSinkWork(working);
}
private:
bool m_working;
MsgConfigureSigMFFileSinkWork(bool working) :
Message(),
m_working(working)
{ }
};
SigMFFileSinkBaseband();
~SigMFFileSinkBaseband();
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;
SigMFFileSinkSink m_sink;
SpectrumVis *m_spectrumSink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
SigMFFileSinkSettings 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 SigMFFileSinkSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
void tick();
};
#endif // INCLUDE_SIFMFFILESINKBASEBAND_H_

View File

@ -0,0 +1,564 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "sigmffilesinkmessages.h"
#include "sigmffilesink.h"
#include "sigmffilesinkgui.h"
#include "ui_sigmffilesinkgui.h"
SigMFFileSinkGUI* SigMFFileSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx)
{
SigMFFileSinkGUI* gui = new SigMFFileSinkGUI(pluginAPI, deviceUISet, channelRx);
return gui;
}
void SigMFFileSinkGUI::destroy()
{
delete this;
}
void SigMFFileSinkGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray SigMFFileSinkGUI::serialize() const
{
return m_settings.serialize();
}
bool SigMFFileSinkGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool SigMFFileSinkGUI::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 (SigMFFileSink::MsgConfigureSigMFFileSink::match(message))
{
const SigMFFileSink::MsgConfigureSigMFFileSink& cfg = (SigMFFileSink::MsgConfigureSigMFFileSink&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (SigMFFileSinkMessages::MsgConfigureSpectrum::match(message))
{
const SigMFFileSinkMessages::MsgConfigureSpectrum& cfg = (SigMFFileSinkMessages::MsgConfigureSpectrum&) message;
ui->glSpectrum->setSampleRate(cfg.getSampleRate());
ui->glSpectrum->setCenterFrequency(cfg.getCenterFrequency());
return true;
}
else if (SigMFFileSinkMessages::MsgReportSquelch::match(message))
{
const SigMFFileSinkMessages::MsgReportSquelch& report = (SigMFFileSinkMessages::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 (SigMFFileSinkMessages::MsgReportRecording::match(message))
{
const SigMFFileSinkMessages::MsgReportSquelch& report = (SigMFFileSinkMessages::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
{
return false;
}
}
SigMFFileSinkGUI::SigMFFileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::SigMFFileSinkGUI),
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_sigMFFileSink = (SigMFFileSink*) channelrx;
m_spectrumVis = m_sigMFFileSink->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum);
m_sigMFFileSink->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("SigMF File Sink");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_settings.setSpectrumGUI(ui->glSpectrumGUI);
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);
}
SigMFFileSinkGUI::~SigMFFileSinkGUI()
{
delete ui;
}
void SigMFFileSinkGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void SigMFFileSinkGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
setTitleColor(m_channelMarker.getColor());
SigMFFileSink::MsgConfigureSigMFFileSink* message = SigMFFileSink::MsgConfigureSigMFFileSink::create(m_settings, force);
m_sigMFFileSink->getInputMessageQueue()->push(message);
}
}
void SigMFFileSinkGUI::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 SigMFFileSinkGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void SigMFFileSinkGUI::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 SigMFFileSinkGUI::displayPos()
{
ui->position->setValue(m_fixedShiftIndex);
ui->filterChainIndex->setText(tr("%1").arg(m_fixedShiftIndex));
}
void SigMFFileSinkGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void SigMFFileSinkGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void SigMFFileSinkGUI::channelMarkerChangedByCursor()
{
if (m_fixedPosition) {
return;
}
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
setPosFromFrequency();
applySettings();
}
void SigMFFileSinkGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void SigMFFileSinkGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void SigMFFileSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void SigMFFileSinkGUI::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_sigMFFileSink->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 SigMFFileSinkGUI::on_deltaFrequency_changed(qint64 value)
{
if (!m_fixedPosition)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
setPosFromFrequency();
applySettings();
}
}
void SigMFFileSinkGUI::on_decimationFactor_currentIndexChanged(int index)
{
m_settings.m_log2Decim = index;
applyDecimation();
displayRate();
displayPos();
applySettings();
if (m_fixedPosition) {
setFrequencyFromPos();
} else {
setPosFromFrequency();
}
}
void SigMFFileSinkGUI::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 SigMFFileSinkGUI::on_position_valueChanged(int value)
{
m_fixedShiftIndex = value;
displayPos();
if (m_fixedPosition)
{
setFrequencyFromPos();
applySettings();
}
}
void SigMFFileSinkGUI::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 SigMFFileSinkGUI::on_squelchLevel_valueChanged(int value)
{
m_settings.m_spectrumSquelch = value;
ui->squelchLevelText->setText(tr("%1").arg(m_settings.m_spectrumSquelch));
applySettings();
}
void SigMFFileSinkGUI::on_preRecordTime_valueChanged(int value)
{
m_settings.m_preRecordTime = value;
ui->preRecordTimeText->setText(tr("%1").arg(m_settings.m_preRecordTime));
applySettings();
}
void SigMFFileSinkGUI::on_postSquelchTime_valueChanged(int value)
{
m_settings.m_squelchPostRecordTime = value;
ui->postSquelchTimeText->setText(tr("%1").arg(m_settings.m_squelchPostRecordTime));
applySettings();
}
void SigMFFileSinkGUI::on_squelchedRecording_toggled(bool checked)
{
ui->record->setEnabled(!checked);
m_settings.m_squelchRecordingEnable = checked;
applySettings();
}
void SigMFFileSinkGUI::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_sigMFFileSink->record(checked);
}
void SigMFFileSinkGUI::on_showFileDialog_clicked(bool checked)
{
(void) checked;
QFileDialog fileDialog(
this,
tr("Save SigMF record file"),
m_settings.m_fileRecordName,
tr("SigMF Files (*.sigmf-meta)")
);
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 SigMFFileSinkGUI::setFrequencyFromPos()
{
int inputFrequencyOffset = SigMFFileSinkSettings::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 SigMFFileSinkGUI::setPosFromFrequency()
{
int fshift = SigMFFileSinkSettings::getHalfBand(m_basebandSampleRate, m_settings.m_log2Decim + 1);
m_fixedShiftIndex = SigMFFileSinkSettings::getFixedShiftIndexFromOffset(
m_basebandSampleRate,
m_settings.m_log2Decim,
m_settings.m_inputFrequencyOffset + (m_settings.m_inputFrequencyOffset < 0 ? -fshift : fshift)
);
displayPos();
}
void SigMFFileSinkGUI::applyDecimation()
{
ui->position->setMaximum(SigMFFileSinkSettings::getNbFixedShiftIndexes(m_settings.m_log2Decim)-1);
ui->position->setValue(m_fixedShiftIndex);
m_fixedShiftIndex = ui->position->value();
}
void SigMFFileSinkGUI::tick()
{
if (++m_tickCount == 20) // once per second
{
uint64_t msTime = m_sigMFFileSink->getMsCount();
uint64_t bytes = m_sigMFFileSink->getByteCount();
unsigned int nbTracks = m_sigMFFileSink->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 SigMFFileSinkGUI::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));
} else {
return tr("%1").arg(QString::number(value, 'e', precision));
}
}

View File

@ -0,0 +1,112 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SIGMFFILESINK_SIGMFFILESINKGUI_H_
#define PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKGUI_H_
#include <stdint.h>
#include <QObject>
#include "dsp/channelmarker.h"
#include "channel/channelgui.h"
#include "util/messagequeue.h"
#include "sigmffilesinksettings.h"
class PluginAPI;
class DeviceUISet;
class SigMFFileSink;
class BasebandSampleSink;
class SpectrumVis;
namespace Ui {
class SigMFFileSinkGUI;
}
class SigMFFileSinkGUI : public ChannelGUI {
Q_OBJECT
public:
static SigMFFileSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
Ui::SigMFFileSinkGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
SigMFFileSinkSettings m_settings;
int m_fixedShiftIndex;
int m_basebandSampleRate;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
bool m_fixedPosition;
bool m_doApplySettings;
SigMFFileSink* m_sigMFFileSink;
SpectrumVis* m_spectrumVis;
MessageQueue m_inputMessageQueue;
uint32_t m_tickCount;
explicit SigMFFileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = nullptr);
virtual ~SigMFFileSinkGUI();
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);
bool handleMessage(const Message& message);
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_SIGMFFILESINK_SIGMFFILESINKGUI_H_ */

View File

@ -0,0 +1,584 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SigMFFileSinkGUI</class>
<widget class="RollupWidget" name="SigMFFileSinkGUI">
<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>SigMF File Sink</string>
</property>
<property name="statusTip">
<string>SigMF 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 (tracks) in record updated at end of track</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>Record 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>Data file 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>File currently opened</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,22 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "sigmffilesinkmessages.h"
MESSAGE_CLASS_DEFINITION(SigMFFileSinkMessages::MsgConfigureSpectrum, Message)
MESSAGE_CLASS_DEFINITION(SigMFFileSinkMessages::MsgReportSquelch, Message)
MESSAGE_CLASS_DEFINITION(SigMFFileSinkMessages::MsgReportRecording, Message)

View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_SIGMFFILESINKMESSAGES_H_
#define INCLUDE_SIGMFFILESINKMESSAGES_H_
#include <QObject>
#include "util/message.h"
class SigMFFileSinkMessages : 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)
{ }
};
};
#endif // INCLUDE_SIGMFFILESINKMESSAGES_H_

View File

@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "sigmffilesinkgui.h"
#endif
#include "sigmffilesink.h"
#include "sigmffilesinkwebapiadapter.h"
#include "sigmffilesinkplugin.h"
const PluginDescriptor SigMFFileSinkPlugin::m_pluginDescriptor = {
SigMFFileSink::m_channelId,
QString("SigMF File Sink"),
QString("6.0.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
SigMFFileSinkPlugin::SigMFFileSinkPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& SigMFFileSinkPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void SigMFFileSinkPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register channel Source
m_pluginAPI->registerRxChannel(SigMFFileSink::m_channelIdURI, SigMFFileSink::m_channelId, this);
}
void SigMFFileSinkPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
SigMFFileSink *instance = new SigMFFileSink(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* SigMFFileSinkPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
return 0;
}
#else
ChannelGUI* SigMFFileSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return SigMFFileSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* SigMFFileSinkPlugin::createChannelWebAPIAdapter() const
{
return new SigMFFileSinkWebAPIAdapter();
}

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 PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKPLUGIN_H_
#define PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKPLUGIN_H_
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class SigMFFileSinkPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.demod.sigmffilesink")
public:
explicit SigMFFileSinkPlugin(QObject* parent = 0);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif /* PLUGINS_CHANNELRX_LOCALSINK_LOCALSINKPLUGIN_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 "sigmffilesinksettings.h"
SigMFFileSinkSettings::SigMFFileSinkSettings()
{
resetToDefaults();
}
void SigMFFileSinkSettings::resetToDefaults()
{
m_ncoMode = false;
m_inputFrequencyOffset = 0;
m_fileRecordName = "";
m_rgbColor = QColor(140, 4, 4).rgb();
m_title = "SigMF 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 SigMFFileSinkSettings::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 SigMFFileSinkSettings::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, "SigMF 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 SigMFFileSinkSettings::getNbFixedShiftIndexes(int log2Decim)
{
int decim = (1<<log2Decim);
return 2*decim - 1;
}
int SigMFFileSinkSettings::getHalfBand(int sampleRate, int log2Decim)
{
int decim = (1<<log2Decim);
return sampleRate / (2*decim);
}
unsigned int SigMFFileSinkSettings::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 SigMFFileSinkSettings::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_SIFMFFILESINKSETTINGS_H_
#define INCLUDE_SIFMFFILESINKSETTINGS_H_
#include <QByteArray>
#include <QString>
class Serializable;
struct SigMFFileSinkSettings
{
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;
SigMFFileSinkSettings();
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_SIFMFFILESINKSETTINGS_H_ */

View File

@ -0,0 +1,296 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "sigmffilesinkmessages.h"
#include "sigmffilesinksink.h"
SigMFFileSinkSink::SigMFFileSinkSink() :
m_spectrumSink(nullptr),
m_msgQueueToGUI(nullptr),
m_preRecordBuffer(48000),
m_preRecordFill(0),
m_recordEnabled(false),
m_record(false),
m_squelchOpen(false),
m_postSquelchCounter(0),
m_msCount(0),
m_byteCount(0)
{}
SigMFFileSinkSink::~SigMFFileSinkSink()
{}
void SigMFFileSinkSink::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;
// 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 SigMFFileSinkSink::stopRecording()
{
if (m_record)
{
m_preRecordBuffer.reset();
m_fileSink.stopRecording();
m_record = false;
}
}
void SigMFFileSinkSink::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)
{
SigMFFileSinkMessages::MsgReportRecording *msg = SigMFFileSinkMessages::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 SigMFFileSinkSink::applyChannelSettings(
int channelSampleRate,
int sinkSampleRate,
int channelFrequencyOffset,
int64_t centerFrequency,
bool force)
{
qDebug() << "SigMFFileSinkSink::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() << "SigMFFileSinkSink::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)
{
SigMFFileSinkMessages::MsgConfigureSpectrum *msg = SigMFFileSinkMessages::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 SigMFFileSinkSink::applySettings(const SigMFFileSinkSettings& settings, bool force)
{
qDebug() << "SigMFFileSinkSink::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::RecordTypeSigMF)
{
m_fileSink.setFileName(fileBase);
m_fileSink.setHardwareId(m_deviceHwId);
m_msCount = 0;
m_byteCount = 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 SigMFFileSinkSink::squelchRecording(bool squelchOpen)
{
if (!m_recordEnabled || !m_settings.m_squelchRecordingEnable) {
return;
}
if (squelchOpen)
{
if (!m_record)
{
startRecording();
if (m_msgQueueToGUI)
{
SigMFFileSinkMessages::MsgReportRecording *msg = SigMFFileSinkMessages::MsgReportRecording::create(true);
m_msgQueueToGUI->push(msg);
}
}
}
else
{
m_postSquelchCounter = m_settings.m_squelchPostRecordTime * m_sinkSampleRate;
}
m_squelchOpen = squelchOpen;
}

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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SIFMFFILESINKSINK_H_
#define INCLUDE_SIFMFFILESINKSINK_H_
#include "dsp/channelsamplesink.h"
#include "dsp/sigmffilerecord.h"
#include "dsp/decimatorc.h"
#include "dsp/samplesimplefifo.h"
#include "dsp/ncof.h"
#include "sigmffilesinksettings.h"
class FileRecordInterface;
class SpectrumVis;
class SigMFFileSinkSink : public ChannelSampleSink {
public:
SigMFFileSinkSink();
~SigMFFileSinkSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
SigMFFileRecord *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 SigMFFileSinkSettings& settings, bool force = false);
uint64_t getMsCount() const { return m_msCount; }
uint64_t getByteCount() const { return m_byteCount; }
unsigned int getNbTracks() const { return m_fileSink.getNbCaptures(); }
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;
SigMFFileSinkSettings m_settings;
SigMFFileRecord m_fileSink;
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_SIFMFFILESINKSINK_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 "sigmffilesink.h"
#include "sigmffilesinkwebapiadapter.h"
SigMFFileSinkWebAPIAdapter::SigMFFileSinkWebAPIAdapter()
{}
SigMFFileSinkWebAPIAdapter::~SigMFFileSinkWebAPIAdapter()
{}
int SigMFFileSinkWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
(void) response;
response.setSigMfFileSinkSettings(new SWGSDRangel::SWGSigMFFileSinkSettings());
response.getSigMfFileSinkSettings()->init();
SigMFFileSink::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int SigMFFileSinkWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
SigMFFileSink::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_SIGMFFILESINK_WEBAPIADAPTER_H
#define INCLUDE_SIGMFFILESINK_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "sigmffilesinksettings.h"
/**
* Standalone API adapter only for the settings
*/
class SigMFFileSinkWebAPIAdapter : public ChannelWebAPIAdapter {
public:
SigMFFileSinkWebAPIAdapter();
virtual ~SigMFFileSinkWebAPIAdapter();
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:
SigMFFileSinkSettings m_settings;
};
#endif // INCLUDE_LOCALSINK_WEBAPIADAPTER_H