1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 17:28:50 -05:00

AMBE feature: creation and changes to DSD demod

This commit is contained in:
f4exb 2022-05-24 15:18:55 +02:00
parent ee65186057
commit 1d72798d42
28 changed files with 2553 additions and 7 deletions

View File

@ -39,6 +39,7 @@
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "feature/featureset.h"
#include "settings/serializable.h"
#include "util/db.h"
#include "maincore.h"
@ -46,6 +47,8 @@
#include "dsddemod.h"
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message)
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgQueryAvailableAMBEFeatures, Message)
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgReportAvailableAMBEFeatures, Message)
const char* const DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod";
const char* const DSDDemod::m_channelId = "DSDDemod";
@ -82,6 +85,20 @@ DSDDemod::DSDDemod(DeviceAPI *deviceAPI) :
this,
&DSDDemod::handleIndexInDeviceSetChanged
);
QObject::connect(
MainCore::instance(),
&MainCore::featureAdded,
this,
&DSDDemod::handleFeatureAdded
);
QObject::connect(
MainCore::instance(),
&MainCore::featureRemoved,
this,
&DSDDemod::handleFeatureRemoved
);
scanAvailableAMBEFeatures();
}
DSDDemod::~DSDDemod()
@ -176,6 +193,11 @@ bool DSDDemod::handleMessage(const Message& cmd)
return true;
}
else if (MsgQueryAvailableAMBEFeatures::match(cmd))
{
notifyUpdateAMBEFeatures();
return true;
}
else
{
return false;
@ -219,6 +241,8 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
<< " m_traceStroke: " << settings.m_traceStroke
<< " m_traceDecay: " << settings.m_traceDecay
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_ambeFeatureIndex: " << settings.m_ambeFeatureIndex
<< " m_connectAMBE: " << settings.m_connectAMBE
<< " force: " << force;
QList<QString> reverseAPIKeys;
@ -280,6 +304,30 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((settings.m_ambeFeatureIndex != m_settings.m_ambeFeatureIndex) || force) {
reverseAPIKeys.append("ambeFeatureIndex");
}
if ((settings.m_connectAMBE != m_settings.m_connectAMBE) || force) {
reverseAPIKeys.append("connectAMBE");
}
if ((m_settings.m_connectAMBE != settings.m_connectAMBE)
|| (m_settings.m_ambeFeatureIndex != settings.m_ambeFeatureIndex) || force)
{
if (settings.m_connectAMBE)
{
for (const auto& feature : m_availableAMBEFeatures)
{
if (feature.m_featureIndex == settings.m_ambeFeatureIndex) {
m_basebandSink->setAMBEFeatureMessageQueue(feature.m_feature->getInputMessageQueue());
}
}
}
else
{
m_basebandSink->setAMBEFeatureMessageQueue(nullptr);
}
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
@ -339,6 +387,32 @@ bool DSDDemod::deserialize(const QByteArray& data)
}
}
void DSDDemod::scanAvailableAMBEFeatures()
{
MainCore *mainCore = MainCore::instance();
int nbFeatures = mainCore->getFeatureeSets()[0]->getNumberOfFeatures();
m_availableAMBEFeatures.clear();
for (int i = 0; i < nbFeatures; i++)
{
Feature *feature = mainCore->getFeatureeSets()[0]->getFeatureAt(i);
if (feature->getURI() == "sdrangel.feature.ambe") {
m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{i, feature};
}
}
}
void DSDDemod::notifyUpdateAMBEFeatures()
{
if (getMessageQueueToGUI())
{
MsgReportAvailableAMBEFeatures *msg = MsgReportAvailableAMBEFeatures::create();
msg->getFeatures() = m_availableAMBEFeatures.values();
getMessageQueueToGUI()->push(msg);
}
}
void DSDDemod::sendSampleRateToDemodAnalyzer()
{
QList<ObjectPipe*> pipes;
@ -794,3 +868,40 @@ void DSDDemod::handleIndexInDeviceSetChanged(int index)
m_basebandSink->setFifoLabel(fifoLabel);
m_basebandSink->setAudioFifoLabel(fifoLabel);
}
void DSDDemod::handleFeatureAdded(int featureSetIndex, Feature *feature)
{
if (featureSetIndex != 0) {
return;
}
if ((feature->getURI() == "sdrangel.feature.ambe") && !m_availableAMBEFeatures.contains(feature))
{
m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{feature->getIndexInFeatureSet(), feature};
if (m_settings.m_connectAMBE && (m_settings.m_ambeFeatureIndex == feature->getIndexInFeatureSet())) {
m_basebandSink->setAMBEFeatureMessageQueue(feature->getInputMessageQueue());
}
notifyUpdateAMBEFeatures();
}
}
void DSDDemod::handleFeatureRemoved(int featureSetIndex, Feature *feature)
{
if (featureSetIndex != 0) {
return;
}
if (m_availableAMBEFeatures.contains(feature))
{
if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[feature].m_featureIndex)
{
m_settings.m_connectAMBE = false;
m_basebandSink->setAMBEFeatureMessageQueue(nullptr);
}
m_availableAMBEFeatures.remove(feature);
notifyUpdateAMBEFeatures();
}
}

View File

@ -62,6 +62,38 @@ public:
{ }
};
class MsgQueryAvailableAMBEFeatures : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgQueryAvailableAMBEFeatures* create() {
return new MsgQueryAvailableAMBEFeatures();
}
protected:
MsgQueryAvailableAMBEFeatures() :
Message()
{ }
};
class MsgReportAvailableAMBEFeatures : public Message {
MESSAGE_CLASS_DECLARATION
public:
QList<DSDDemodSettings::AvailableAMBEFeature>& getFeatures() { return m_availableFeatures; }
static MsgReportAvailableAMBEFeatures* create() {
return new MsgReportAvailableAMBEFeatures();
}
private:
QList<DSDDemodSettings::AvailableAMBEFeature> m_availableFeatures;
MsgReportAvailableAMBEFeatures() :
Message()
{}
};
DSDDemod(DeviceAPI *deviceAPI);
virtual ~DSDDemod();
virtual void destroy() { delete this; }
@ -140,6 +172,7 @@ private:
DSDDemodBaseband *m_basebandSink;
DSDDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
QHash<Feature*, DSDDemodSettings::AvailableAMBEFeature> m_availableAMBEFeatures;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
@ -148,6 +181,8 @@ private:
virtual bool handleMessage(const Message& cmd);
void applySettings(const DSDDemodSettings& settings, bool force = false);
void scanAvailableAMBEFeatures();
void notifyUpdateAMBEFeatures();
void sendSampleRateToDemodAnalyzer();
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force);
@ -167,6 +202,8 @@ private:
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleIndexInDeviceSetChanged(int index);
void handleFeatureAdded(int featureSetIndex, Feature *feature);
void handleFeatureRemoved(int featureSetIndex, Feature *feature);
};
#endif // INCLUDE_DSDDEMOD_H

View File

@ -75,6 +75,7 @@ public:
void setChannel(ChannelAPI *channel);
void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
void setAudioFifoLabel(const QString& label) { m_sink.setAudioFifoLabel(label); }
void setAMBEFeatureMessageQueue(MessageQueue *ambeFeatureMessageQueue) { m_sink.setAmbeFeatureMessageQueue(ambeFeatureMessageQueue); }
private:
SampleSinkFifo m_sampleFifo;

View File

@ -113,6 +113,13 @@ bool DSDDemodGUI::handleMessage(const Message& message)
updateAbsoluteCenterFrequency();
return true;
}
else if (DSDDemod::MsgReportAvailableAMBEFeatures::match(message))
{
DSDDemod::MsgReportAvailableAMBEFeatures& report = (DSDDemod::MsgReportAvailableAMBEFeatures&) message;
m_availableAMBEFeatures = report.getFeatures();
updateAMBEFeaturesList();
return true;
}
else
{
return false;
@ -263,6 +270,19 @@ void DSDDemodGUI::on_symbolPLLLock_toggled(bool checked)
applySettings();
}
void DSDDemodGUI::on_ambeSupport_clicked(bool checked)
{
m_settings.m_connectAMBE = checked;
m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[ui->ambeFeatures->currentIndex()].m_featureIndex;
applySettings();
}
void DSDDemodGUI::on_ambeFeatures_currentIndexChanged(int index)
{
m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[index].m_featureIndex;
applySettings();
}
void DSDDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
@ -490,6 +510,17 @@ void DSDDemodGUI::displaySettings()
ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay));
m_scopeVisXY->setDecay(m_settings.m_traceDecay);
ui->ambeSupport->setChecked(m_settings.m_connectAMBE);
for (int i = 0; i < ui->ambeFeatures->count(); i++)
{
if (ui->ambeFeatures->itemData(i).toInt() == m_settings.m_ambeFeatureIndex)
{
ui->ambeFeatures->setCurrentIndex(i);
break;
}
}
updateIndexLabel();
getRollupContents()->restoreState(m_rollupState);
@ -497,6 +528,32 @@ void DSDDemodGUI::displaySettings()
blockApplySettings(false);
}
void DSDDemodGUI::updateAMBEFeaturesList()
{
ui->ambeFeatures->blockSignals(true);
ui->ambeSupport->blockSignals(true);
ui->ambeFeatures->clear();
bool unsetAMBE = true;
for (int i = 0; i < m_availableAMBEFeatures.count(); i++)
{
ui->ambeFeatures->addItem(tr("F:%1").arg(m_availableAMBEFeatures[i].m_featureIndex), m_availableAMBEFeatures[i].m_featureIndex);
if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[i].m_featureIndex)
{
unsetAMBE = false;
ui->ambeFeatures->setCurrentIndex(i);
}
}
if (unsetAMBE) {
ui->ambeSupport->setChecked(false);
}
ui->ambeSupport->blockSignals(false);
ui->ambeFeatures->blockSignals(false);
}
void DSDDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
@ -658,6 +715,8 @@ void DSDDemodGUI::makeUIConnections()
QObject::connect(ui->audioMute, &QToolButton::toggled, this, &DSDDemodGUI::on_audioMute_toggled);
QObject::connect(ui->symbolPLLLock, &QToolButton::toggled, this, &DSDDemodGUI::on_symbolPLLLock_toggled);
QObject::connect(ui->viewStatusLog, &QPushButton::clicked, this, &DSDDemodGUI::on_viewStatusLog_clicked);
QObject::connect(ui->ambeSupport, &QCheckBox::clicked, this, &DSDDemodGUI::on_ambeSupport_clicked);
QObject::connect(ui->ambeFeatures, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DSDDemodGUI::on_ambeFeatures_currentIndexChanged);
}
void DSDDemodGUI::updateAbsoluteCenterFrequency()

View File

@ -90,6 +90,7 @@ private:
qint64 m_deviceCenterFrequency;
int m_basebandSampleRate;
bool m_doApplySettings;
QList<DSDDemodSettings::AvailableAMBEFeature> m_availableAMBEFeatures;
ScopeVisXY* m_scopeVisXY;
@ -117,6 +118,7 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateAMBEFeaturesList();
void updateMyPosition();
bool handleMessage(const Message& message);
void makeUIConnections();
@ -145,6 +147,8 @@ private slots:
void on_highPassFilter_toggled(bool checked);
void on_audioMute_toggled(bool checked);
void on_symbolPLLLock_toggled(bool checked);
void on_ambeSupport_clicked(bool checked);
void on_ambeFeatures_currentIndexChanged(int index);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void on_viewStatusLog_clicked();

View File

@ -1231,6 +1231,29 @@
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QCheckBox" name="ambeSupport">
<property name="geometry">
<rect>
<x>10</x>
<y>170</y>
<width>71</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>AMBE</string>
</property>
</widget>
<widget class="QComboBox" name="ambeFeatures">
<property name="geometry">
<rect>
<x>80</x>
<y>170</y>
<width>50</width>
<height>20</height>
</rect>
</property>
</widget>
</widget>
</item>
</layout>

View File

@ -30,7 +30,7 @@
const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = {
DSDDemod::m_channelId,
QStringLiteral("DSD Demodulator"),
QStringLiteral("7.0.0"),
QStringLiteral("7.2.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,

View File

@ -61,6 +61,8 @@ void DSDDemodSettings::resetToDefaults()
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
m_ambeFeatureIndex = -1;
m_connectAMBE = false;
}
QByteArray DSDDemodSettings::serialize() const
@ -107,6 +109,8 @@ QByteArray DSDDemodSettings::serialize() const
s.writeS32(32, m_workspaceIndex);
s.writeBlob(33, m_geometryBytes);
s.writeBool(34, m_hidden);
s.writeS32(35, m_ambeFeatureIndex);
s.writeBool(36, m_connectAMBE);
return s.final();
}
@ -190,6 +194,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data)
d.readS32(32, &m_workspaceIndex, 0);
d.readBlob(33, &m_geometryBytes);
d.readBool(34, &m_hidden, false);
d.readS32(35, &m_ambeFeatureIndex, -1);
d.readBool(36, &m_connectAMBE, false);
return true;
}

View File

@ -21,9 +21,20 @@
#include <QByteArray>
class Serializable;
class Feature;
struct DSDDemodSettings
{
struct AvailableAMBEFeature
{
int m_featureIndex;
Feature *m_feature;
AvailableAMBEFeature() = default;
AvailableAMBEFeature(const AvailableAMBEFeature&) = default;
AvailableAMBEFeature& operator=(const AvailableAMBEFeature&) = default;
};
qint64 m_inputFrequencyOffset;
Real m_rfBandwidth;
Real m_fmDeviation;
@ -55,6 +66,8 @@ struct DSDDemodSettings
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
int m_ambeFeatureIndex;
bool m_connectAMBE;
Serializable *m_channelMarker;
Serializable *m_rollupState;

View File

@ -34,6 +34,7 @@
#include "dsp/dspengine.h"
#include "dsp/basebandsamplesink.h"
#include "dsp/datafifo.h"
#include "dsp/dspcommands.h"
#include "audio/audiooutputdevice.h"
#include "util/db.h"
#include "util/messagequeue.h"
@ -44,6 +45,7 @@
DSDDemodSink::DSDDemodSink() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_ambeFeatureMessageQueue(nullptr),
m_audioSampleRate(48000),
m_interpolatorDistance(0.0f),
m_interpolatorDistanceRemain(0.0f),
@ -229,20 +231,32 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
m_scopeSampleBuffer.push_back(s);
}
if (DSPEngine::instance()->hasDVSerialSupport())
// if (DSPEngine::instance()->hasDVSerialSupport())
if (m_ambeFeatureMessageQueue)
{
if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1())
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_ambeFeatureMessageQueue->push(
new DSPPushMbeFrame(
m_dsdDecoder.getMbeDVFrame1(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo1);
&m_audioFifo1
)
);
// DSPEngine::instance()->pushMbeFrame(
// m_dsdDecoder.getMbeDVFrame1(),
// m_dsdDecoder.getMbeRateIndex(),
// m_settings.m_volume * 10.0,
// m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
// m_settings.m_highPassFilter,
// m_audioSampleRate/8000, // upsample from native 8k
// &m_audioFifo1);
}
m_dsdDecoder.resetMbeDV1();
@ -252,14 +266,25 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_ambeFeatureMessageQueue->push(
new DSPPushMbeFrame(
m_dsdDecoder.getMbeDVFrame2(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo2);
&m_audioFifo2
)
);
// DSPEngine::instance()->pushMbeFrame(
// m_dsdDecoder.getMbeDVFrame2(),
// m_dsdDecoder.getMbeRateIndex(),
// m_settings.m_volume * 10.0,
// m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
// m_settings.m_highPassFilter,
// m_audioSampleRate/8000, // upsample from native 8k
// &m_audioFifo2);
}
m_dsdDecoder.resetMbeDV2();
@ -270,7 +295,8 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
}
}
if (!DSPEngine::instance()->hasDVSerialSupport())
if (!m_ambeFeatureMessageQueue)
// if (!DSPEngine::instance()->hasDVSerialSupport())
{
if (m_settings.m_slot1On)
{

View File

@ -83,6 +83,7 @@ public:
}
const char *updateAndGetStatusText();
void setAmbeFeatureMessageQueue(MessageQueue *queue) { m_ambeFeatureMessageQueue = queue; }
private:
struct MagSqLevelsStore
@ -114,6 +115,7 @@ private:
int m_channelFrequencyOffset;
DSDDemodSettings m_settings;
ChannelAPI *m_channel;
MessageQueue *m_ambeFeatureMessageQueue;
int m_audioSampleRate;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;

View File

@ -39,3 +39,7 @@ endif()
if (ENABLE_LIMESUITE AND LIMESUITE_FOUND)
add_subdirectory(limerfe)
endif()
if (LIBSERIALDV_FOUND)
add_subdirectory(ambe)
endif()

View File

@ -0,0 +1,65 @@
project(ambe)
set(ambe_SOURCES
ambe.cpp
ambesettings.cpp
ambeplugin.cpp
ambeengine.cpp
ambeworker.cpp
# ambewebapiadapter.cpp
)
set(ambe_HEADERS
ambe.h
ambesettings.h
ambeplugin.h
ambeengine.h
ambeworker.h
# ambewebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${LIBSERIALDV_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(ambe_SOURCES
${ambe_SOURCES}
ambegui.cpp
ambegui.ui
)
set(ambe_HEADERS
${ambe_HEADERS}
ambegui.h
)
set(TARGET_NAME featureambe)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featureambesrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${ambe_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${LIBSERIALDV_LIBRARY}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,170 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "settings/serializable.h"
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "ambe.h"
MESSAGE_CLASS_DEFINITION(AMBE::MsgConfigureAMBE, Message)
const char* const AMBE::m_featureIdURI = "sdrangel.feature.ambe";
const char* const AMBE::m_featureId = "AMBE";
AMBE::AMBE(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface)
{
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "AMBE error";
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&AMBE::networkManagerFinished
);
}
AMBE::~AMBE()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&AMBE::networkManagerFinished
);
delete m_networkManager;
}
void AMBE::start()
{
qDebug("AMBE::start");
m_state = StRunning;
}
void AMBE::stop()
{
qDebug("AMBE::stop");
m_state = StIdle;
}
void AMBE::applySettings(const AMBESettings& settings, bool force)
{
(void) force;
m_settings = settings;
}
bool AMBE::handleMessage(const Message& cmd)
{
if (MsgConfigureAMBE::match(cmd))
{
MsgConfigureAMBE& cfg = (MsgConfigureAMBE&) cmd;
qDebug() << "AMBE::handleMessage: MsgConfigureAMBE";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPPushMbeFrame::match(cmd))
{
DSPPushMbeFrame& cfg = (DSPPushMbeFrame&) cmd;
m_ambeEngine.pushMbeFrame(
cfg.getMbeFrame(),
cfg.getMbeRateIndex(),
cfg.getMbeVolumeIndex(),
cfg.getChannels(),
cfg.getUseHP(),
cfg.getUpsampling(),
cfg.getAudioFifo()
);
return true;
}
return false;
}
QByteArray AMBE::serialize() const
{
SimpleSerializer s(1);
s.writeBlob(1, m_settings.serialize());
return s.final();
}
bool AMBE::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
m_settings.resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
d.readBlob(1, &bytetmp);
if (m_settings.deserialize(bytetmp))
{
MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
else
{
return false;
}
}
void AMBE::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AMBE::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AMBE::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_FEATURE_AMBE_H_
#define INCLUDE_FEATURE_AMBE_H_
#include <QNetworkRequest>
#include <QSet>
#include "feature/feature.h"
#include "util/message.h"
#include "ambeengine.h"
#include "ambesettings.h"
class WebAPIAdapterInterface;
class QNetworkAccessManager;
class QNetworkReply;
class AMBE : public Feature
{
Q_OBJECT
public:
class MsgConfigureAMBE : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AMBESettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAMBE* create(const AMBESettings& settings, bool force) {
return new MsgConfigureAMBE(settings, force);
}
private:
AMBESettings m_settings;
bool m_force;
MsgConfigureAMBE(const AMBESettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
AMBE(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~AMBE();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) const { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
AMBEEngine *getAMBEEngine() { return &m_ambeEngine; }
static const char* const m_featureIdURI;
static const char* const m_featureId;
private:
AMBESettings m_settings;
AMBEEngine m_ambeEngine;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void start();
void stop();
void applySettings(const AMBESettings& settings, bool force = false);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif

View File

@ -0,0 +1,402 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 F4EXB //
// written by Edouard Griffiths //
// //
// 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 __APPLE__
#include <fcntl.h>
#include <sys/stat.h>
#endif
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#include <libgen.h>
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/serial.h>
#endif
#include <chrono>
#include <thread>
#include <QThread>
#include <QBuffer>
#include <QDataStream>
#include "ambeworker.h"
#include "ambeengine.h"
AMBEEngine::AMBEEngine()
{}
AMBEEngine::~AMBEEngine()
{
qDebug("AMBEEngine::~AMBEEngine: %lu controllers", m_controllers.size());
}
#if defined(_WIN32)
void AMBEEngine::getComList()
{
m_comList.clear();
m_comList8250.clear();
char comCStr[16];
// Arbitrarily set the list to the 20 first COM ports
for (int i = 1; i <= 20; i++)
{
sprintf(comCStr, "COM%d", i);
m_comList.push_back(std::string(comCStr));
}
}
// Do not activate serial support at all for windows
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
{
(void) ambeDevices;
}
#elif defined(__APPLE__)
void AMBEEngine::getComList()
{
}
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
{
(void) ambeDevices;
}
#else
void AMBEEngine::getComList()
{
int n;
struct dirent **namelist;
m_comList.clear();
m_comList8250.clear();
const char* sysdir = "/sys/class/tty/";
// Scan through /sys/class/tty - it contains all tty-devices in the system
n = scandir(sysdir, &namelist, NULL, alphasort);
if (n < 0)
{
perror("scandir");
}
else
{
while (n--)
{
if (strcmp(namelist[n]->d_name, "..") && strcmp(namelist[n]->d_name, "."))
{
// Construct full absolute file path
std::string devicedir = sysdir;
devicedir += namelist[n]->d_name;
// Register the device
register_comport(m_comList, m_comList8250, devicedir);
}
free(namelist[n]);
}
free(namelist);
}
// Only non-serial8250 has been added to comList without any further testing
// serial8250-devices must be probe to check for validity
probe_serial8250_comports(m_comList, m_comList8250);
}
#endif // not Windows nor Apple
#if !defined(_WIN32) && !defined(__APPLE__)
void AMBEEngine::register_comport(
std::vector<std::string>& comList,
std::vector<std::string>& comList8250,
const std::string& dir)
{
// Get the driver the device is using
std::string driver = get_driver(dir);
// Skip devices without a driver
if (driver.size() > 0)
{
//std::cerr << "register_comport: dir: "<< dir << " driver: " << driver << std::endl;
std::string devfile = std::string("/dev/") + basename((char *) dir.c_str());
// Put serial8250-devices in a seperate list
if (driver == "serial8250") {
comList8250.push_back(devfile);
} else {
comList.push_back(devfile);
}
}
}
void AMBEEngine::probe_serial8250_comports(
std::vector<std::string>& comList,
std::vector<std::string> comList8250)
{
struct serial_struct serinfo;
std::vector<std::string>::iterator it = comList8250.begin();
// Iterate over all serial8250-devices
while (it != comList8250.end())
{
// Try to open the device
int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd >= 0)
{
// Get serial_info
if (ioctl(fd, TIOCGSERIAL, &serinfo) == 0)
{
// If device type is no PORT_UNKNOWN we accept the port
if (serinfo.type != PORT_UNKNOWN) {
comList.push_back(*it);
}
}
close(fd);
}
it++;
}
}
std::string AMBEEngine::get_driver(const std::string& tty)
{
struct stat st;
std::string devicedir = tty;
// Append '/device' to the tty-path
devicedir += "/device";
// Stat the devicedir and handle it if it is a symlink
if (lstat(devicedir.c_str(), &st) == 0 && S_ISLNK(st.st_mode))
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
// Append '/driver' and return basename of the target
devicedir += "/driver";
if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0) {
return basename(buffer);
}
}
return "";
}
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
{
getComList();
std::vector<std::string>::const_iterator it = m_comList.begin();
ambeDevices.clear();
while (it != m_comList.end())
{
AMBEWorker *worker = new AMBEWorker();
qDebug("AMBEEngine::scan: com: %s", it->c_str());
if (worker->open(*it))
{
ambeDevices.push_back(QString(it->c_str()));
worker->close();
}
delete worker;
++it;
}
}
#endif // not Windows nor Apple
bool AMBEEngine::registerController(const std::string& deviceRef)
{
AMBEWorker *worker = new AMBEWorker();
if (worker->open(deviceRef))
{
qDebug("AMBEEngine::registerController: device: %s", deviceRef.c_str());
m_controllers.push_back(AMBEController());
m_controllers.back().worker = worker;
m_controllers.back().thread = new QThread();
m_controllers.back().device = deviceRef;
m_controllers.back().worker->moveToThread(m_controllers.back().thread);
connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().thread, SLOT(quit()));
connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().worker, SLOT(deleteLater()));
connect(m_controllers.back().thread, SIGNAL(finished()), m_controllers.back().thread, SLOT(deleteLater()));
connect(&m_controllers.back().worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), m_controllers.back().worker, SLOT(handleInputMessages()));
std::this_thread::sleep_for(std::chrono::seconds(1));
m_controllers.back().thread->start();
return true;
}
qWarning("AMBEEngine::registerController: failed to register device: %s", deviceRef.c_str());
return false;
}
void AMBEEngine::releaseController(const std::string& deviceRef)
{
std::vector<AMBEController>::iterator it = m_controllers.begin();
while (it != m_controllers.end())
{
if (it->device == deviceRef)
{
disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages()));
it->worker->stop();
it->thread->wait(100);
it->worker->m_inputMessageQueue.clear();
it->worker->close();
qDebug() << "AMBEEngine::releaseController: closed device at: " << it->device.c_str();
m_controllers.erase(it);
break;
}
++it;
}
}
void AMBEEngine::releaseAll()
{
std::vector<AMBEController>::iterator it = m_controllers.begin();
while (it != m_controllers.end())
{
disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages()));
it->worker->stop();
it->thread->wait(100);
it->worker->m_inputMessageQueue.clear();
it->worker->close();
qDebug() << "AMBEEngine::release: closed device at: " << it->device.c_str();
++it;
}
m_controllers.clear();
}
void AMBEEngine::getDeviceRefs(std::vector<QString>& deviceNames)
{
std::vector<AMBEController>::const_iterator it = m_controllers.begin();
while (it != m_controllers.end())
{
deviceNames.push_back(QString(it->device.c_str()));
++it;
}
}
void AMBEEngine::pushMbeFrame(
const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useLP,
int upsampling,
AudioFifo *audioFifo)
{
std::vector<AMBEController>::iterator it = m_controllers.begin();
std::vector<AMBEController>::iterator itAvail = m_controllers.end();
bool done = false;
QMutexLocker locker(&m_mutex);
while (it != m_controllers.end())
{
if (it->worker->hasFifo(audioFifo))
{
it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo);
done = true;
}
else if (it->worker->isAvailable())
{
itAvail = it;
}
++it;
}
if (!done)
{
if (itAvail != m_controllers.end())
{
int wNum = itAvail - m_controllers.begin();
qDebug("AMBEEngine::pushMbeFrame: push %p on empty queue %d", audioFifo, wNum);
itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo);
}
else
{
qDebug("AMBEEngine::pushMbeFrame: no DV device available. MBE frame dropped");
}
}
}
QByteArray AMBEEngine::serialize() const
{
qDebug("AMBEEngine::serialize");
QStringList qDeviceList;
std::vector<AMBEController>::const_iterator it = m_controllers.begin();
while (it != m_controllers.end())
{
qDebug("AMBEEngine::serialize: %s", it->device.c_str());
qDeviceList << QString(it->device.c_str());
++it;
}
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << qDeviceList;
delete stream;
return data;
}
bool AMBEEngine::deserialize(const QByteArray& data)
{
if (data.size() <= 0)
{
qDebug("AMBEEngine::deserialize: invalid or no data");
return false;
}
QStringList qDeviceList;
QDataStream *stream = new QDataStream(data);
(*stream) >> qDeviceList;
delete stream;
releaseAll();
for (int i = 0; i < qDeviceList.size(); ++i)
{
qDebug(" AMBEEngine::deserialize: %s", qDeviceList.at(i).toStdString().c_str());
registerController(qDeviceList.at(i).toStdString());
}
return true;
}
void AMBEEngine::formatTo(SWGSDRangel::SWGObject *swgObject) const
{
(void) swgObject;
// TODO
}
void AMBEEngine::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject)
{
(void) keys;
(void) swgObject;
// TODO
}

View File

@ -0,0 +1,93 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 F4EXB //
// written by Edouard Griffiths //
// //
// 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 SDRBASE_AMBE_AMBEENGINE_H_
#define SDRBASE_AMBE_AMBEENGINE_H_
#include <vector>
#include <string>
#include <QObject>
#include <QMutex>
#include <QString>
#include <QByteArray>
#include "settings/serializable.h"
class QThread;
class AMBEWorker;
class AudioFifo;
class AMBEEngine : public QObject, public Serializable
{
Q_OBJECT
public:
AMBEEngine();
~AMBEEngine();
void scan(std::vector<QString>& ambeDevices);
void releaseAll();
int getNbDevices() const { return m_controllers.size(); } //!< number of devices used
void getDeviceRefs(std::vector<QString>& devicesRefs); //!< reference of the devices used (device path or url)
bool registerController(const std::string& deviceRef); //!< create a new controller for the device in reference
void releaseController(const std::string& deviceRef); //!< release controller resources for the device in reference
void pushMbeFrame(
const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; //!< Serialize to API
virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); //!< Deserialize from API
private:
struct AMBEController
{
AMBEController() :
thread(nullptr),
worker(nullptr)
{}
QThread *thread;
AMBEWorker *worker;
std::string device;
};
#ifndef _WIN32
static std::string get_driver(const std::string& tty);
static void register_comport(std::vector<std::string>& comList, std::vector<std::string>& comList8250, const std::string& dir);
static void probe_serial8250_comports(std::vector<std::string>& comList, std::vector<std::string> comList8250);
#endif
void getComList();
std::vector<AMBEController> m_controllers;
std::vector<std::string> m_comList;
std::vector<std::string> m_comList8250;
QMutex m_mutex;
};
#endif /* SDRBASE_AMBE_AMBEENGINE_H_ */

View File

@ -0,0 +1,343 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "feature/featureuiset.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/crightclickenabler.h"
#include "ui_ambegui.h"
#include "ambegui.h"
#include "ambe.h"
AMBEGUI* AMBEGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
AMBEGUI* gui = new AMBEGUI(pluginAPI, featureUISet, feature);
return gui;
}
void AMBEGUI::destroy()
{
delete this;
}
AMBEGUI::AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::AMBEGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true)
{
m_feature = feature;
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/feature/ambe/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_ambe = reinterpret_cast<AMBE*>(feature);
m_ambe->setMessageQueueToGUI(&m_inputMessageQueue);
m_settings.setRollupState(&m_rollupState);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
populateSerialList();
refreshInUseList();
displaySettings();
makeUIConnections();
}
AMBEGUI::~AMBEGUI()
{
delete ui;
}
void AMBEGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray AMBEGUI::serialize() const
{
return m_settings.serialize();
}
bool AMBEGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
displaySettings();
refreshInUseList();
return true;
}
else
{
resetToDefaults();
return false;
}
}
void AMBEGUI::setWorkspaceIndex(int index)
{
m_settings.m_workspaceIndex = index;
m_feature->setWorkspaceIndex(index);
}
void AMBEGUI::resizeEvent(QResizeEvent* size)
{
int maxWidth = getRollupContents()->maximumWidth();
int minHeight = getRollupContents()->minimumHeight() + getAdditionalHeight();
resize(width() < maxWidth ? width() : maxWidth, minHeight);
size->accept();
}
void AMBEGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySettings();
}
void AMBEGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.setDefaultTitle(m_displayedName);
dialog.move(p);
dialog.exec();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void AMBEGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
setTitle(m_settings.m_title);
}
void AMBEGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AMBE::MsgConfigureAMBE* message = AMBE::MsgConfigureAMBE::create( m_settings, force);
m_ambe->getInputMessageQueue()->push(message);
}
}
bool AMBEGUI::handleMessage(const Message& message)
{
if (AMBE::MsgConfigureAMBE::match(message))
{
qDebug("AMBEGUI::handleMessage: AMBE::MsgConfigureAMBE");
const AMBE::MsgConfigureAMBE& cfg = (AMBE::MsgConfigureAMBE&) message;
m_settings = cfg.getSettings();
displaySettings();
return true;
}
return false;
}
void AMBEGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void AMBEGUI::populateSerialList()
{
std::vector<QString> ambeSerialDevices;
m_ambe->getAMBEEngine()->scan(ambeSerialDevices);
ui->ambeSerialDevices->clear();
std::vector<QString>::const_iterator it = ambeSerialDevices.begin();
for (; it != ambeSerialDevices.end(); ++it) {
ui->ambeSerialDevices->addItem(QString(*it));
}
}
void AMBEGUI::refreshInUseList()
{
std::vector<QString> inUseDevices;
m_ambe->getAMBEEngine()->getDeviceRefs(inUseDevices);
ui->ambeDeviceRefs->clear();
std::vector<QString>::const_iterator it = inUseDevices.begin();
for (; it != inUseDevices.end(); ++it)
{
qDebug("AMBEGUI::refreshInUseList: %s", qPrintable(*it));
ui->ambeDeviceRefs->addItem(*it);
}
}
void AMBEGUI::on_importSerial_clicked()
{
QListWidgetItem *serialItem = ui->ambeSerialDevices->currentItem();
if (!serialItem)
{
ui->statusText->setText("No selection");
return;
}
QString serialName = serialItem->text();
QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive);
if (foundItems.size() == 0)
{
if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString()))
{
ui->ambeDeviceRefs->addItem(serialName);
ui->statusText->setText(tr("%1 added").arg(serialName));
}
else
{
ui->statusText->setText(tr("Cannot open %1").arg(serialName));
}
}
else
{
ui->statusText->setText("Device already in use");
}
}
void AMBEGUI::on_importAllSerial_clicked()
{
int count = 0;
for (int i = 0; i < ui->ambeSerialDevices->count(); i++)
{
const QListWidgetItem *serialItem = ui->ambeSerialDevices->item(i);
QString serialName = serialItem->text();
QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive);
if (foundItems.size() == 0)
{
if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString()))
{
ui->ambeDeviceRefs->addItem(serialName);
count++;
}
}
}
ui->statusText->setText(tr("%1 devices added").arg(count));
}
void AMBEGUI::on_removeAmbeDevice_clicked()
{
QListWidgetItem *deviceItem = ui->ambeDeviceRefs->currentItem();
if (!deviceItem)
{
ui->statusText->setText("No selection");
return;
}
QString deviceName = deviceItem->text();
m_ambe->getAMBEEngine()->releaseController(deviceName.toStdString());
ui->statusText->setText(tr("%1 removed").arg(deviceName));
refreshInUseList();
}
void AMBEGUI::on_refreshAmbeList_clicked()
{
refreshInUseList();
ui->statusText->setText("In use refreshed");
}
void AMBEGUI::on_clearAmbeList_clicked()
{
if (ui->ambeDeviceRefs->count() == 0)
{
ui->statusText->setText("No active items");
return;
}
m_ambe->getAMBEEngine()->releaseAll();
ui->ambeDeviceRefs->clear();
ui->statusText->setText("All items released");
}
void AMBEGUI::on_importAddress_clicked()
{
QString addressAndPort = ui->ambeAddressText->text();
QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(addressAndPort, Qt::MatchFixedString|Qt::MatchCaseSensitive);
if (foundItems.size() == 0)
{
if (m_ambe->getAMBEEngine()->registerController(addressAndPort.toStdString()))
{
ui->ambeDeviceRefs->addItem(addressAndPort);
ui->statusText->setText(tr("%1 added").arg(addressAndPort));
}
else
{
ui->statusText->setText(tr("Cannot open %1").arg(addressAndPort));
}
}
else
{
ui->statusText->setText("Address already in use");
}
}
void AMBEGUI::makeUIConnections()
{
QObject::connect(ui->importSerial, &QPushButton::clicked, this, &AMBEGUI::on_importSerial_clicked);
QObject::connect(ui->importAllSerial, &QPushButton::clicked, this, &AMBEGUI::on_importAllSerial_clicked);
QObject::connect(ui->removeAmbeDevice, &QPushButton::clicked, this, &AMBEGUI::on_removeAmbeDevice_clicked);
QObject::connect(ui->refreshAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_refreshAmbeList_clicked);
QObject::connect(ui->clearAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_clearAmbeList_clicked);
QObject::connect(ui->importAddress, &QPushButton::clicked, this, &AMBEGUI::on_importAddress_clicked);
}

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_FEATURE_AMBEGUI_H_
#define INCLUDE_FEATURE_AMBEGUI_H_
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "util/movingaverage.h"
#include "settings/rollupstate.h"
#include "ambesettings.h"
class PluginAPI;
class FeatureUISet;
class Feature;
class AMBE;
class DSPDeviceSourceEngine;
class DSPDeviceSinkEngine;
namespace Ui {
class AMBEGUI;
}
class AMBEGUI : public FeatureGUI
{
Q_OBJECT
public:
static AMBEGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index);
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; }
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; }
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; }
protected:
void resizeEvent(QResizeEvent* size);
private:
Ui::AMBEGUI* ui;
AMBE *m_ambe;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
AMBESettings m_settings;
RollupState m_rollupState;
bool m_doApplySettings;
MessageQueue m_inputMessageQueue;
explicit AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~AMBEGUI();
void populateSerialList();
void refreshInUseList();
void blockApplySettings(bool block) { m_doApplySettings = !block; }
void applySettings(bool force = false);
void displaySettings();
bool handleMessage(const Message& message);
void makeUIConnections();
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_importSerial_clicked();
void on_importAllSerial_clicked();
void on_removeAmbeDevice_clicked();
void on_refreshAmbeList_clicked();
void on_clearAmbeList_clicked();
void on_importAddress_clicked();
};
#endif

View File

@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AMBEGUI</class>
<widget class="RollupContents" name="AMBEGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>452</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>452</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>560</width>
<height>452</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>AMBE Decoder Controller</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>450</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="ipTextLayout">
<item>
<widget class="QLabel" name="ambeServerLabel">
<property name="text">
<string>AMBE server IP and port or direct input</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ipLayout">
<item>
<widget class="QLineEdit" name="ambeAddressText">
<property name="toolTip">
<string>AMBE server address as ip:port or direct input</string>
</property>
<property name="inputMask">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="inUseLayout">
<item>
<widget class="QLabel" name="ambeSerialLabel">
<property name="text">
<string>In use</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importAddress">
<property name="toolTip">
<string>Use server</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeAmbeDevice">
<property name="toolTip">
<string>Release all devices and servers</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refreshAmbeList">
<property name="toolTip">
<string>Refresh list of devices and servers in use</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearAmbeList">
<property name="toolTip">
<string>Remove all devices</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sweep.png</normaloff>:/sweep.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="localDeviceLayout">
<item>
<widget class="QListWidget" name="ambeDeviceRefs">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>140</height>
</size>
</property>
<property name="toolTip">
<string>List of devices/servers in use</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="serialLayout">
<item>
<widget class="QLabel" name="ambeSerialLabel_2">
<property name="text">
<string>Serial devices</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importSerial">
<property name="toolTip">
<string>Use serial device</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importAllSerial">
<property name="toolTip">
<string>Use all serial devices</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/double_arrow_up.png</normaloff>:/double_arrow_up.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="serialLayout_2">
<item>
<widget class="QListWidget" name="ambeSerialDevices">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>140</height>
</size>
</property>
<property name="toolTip">
<string>List of available AMBE serial devices available</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="statusLayout">
<item>
<widget class="QLabel" name="statusText">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "ambegui.h"
#endif
#include "ambe.h"
#include "ambeplugin.h"
// #include "simplepttwebapiadapter.h"
const PluginDescriptor AMBEPlugin::m_pluginDescriptor = {
AMBE::m_featureId,
QStringLiteral("AMBE Controller"),
QStringLiteral("7.2.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
AMBEPlugin::AMBEPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& AMBEPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AMBEPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register Simple PTT feature
m_pluginAPI->registerFeature(AMBE::m_featureIdURI, AMBE::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return AMBEGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* AMBEPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new AMBE(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* AMBEPlugin::createFeatureWebAPIAdapter() const
{
return nullptr; // TODO new SimplePTTWebAPIAdapter();
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_FEATURE_AMBEPLUGIN_H
#define INCLUDE_FEATURE_AMBEPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class AMBEPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.ambe")
public:
explicit AMBEPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_AMBEPLUGIN_H

View File

@ -0,0 +1,114 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "ambesettings.h"
AMBESettings::AMBESettings() :
m_rollupState(nullptr)
{
resetToDefaults();
}
void AMBESettings::resetToDefaults()
{
m_title = "AMBE Controller";
m_rgbColor = QColor(255, 0, 0).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
m_workspaceIndex = 0;
}
QByteArray AMBESettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_title);
s.writeU32(2, m_rgbColor);
s.writeBool(7, m_useReverseAPI);
s.writeString(8, m_reverseAPIAddress);
s.writeU32(9, m_reverseAPIPort);
s.writeU32(10, m_reverseAPIFeatureSetIndex);
s.writeU32(11, m_reverseAPIFeatureIndex);
if (m_rollupState) {
s.writeBlob(12, m_rollupState->serialize());
}
s.writeS32(18, m_workspaceIndex);
s.writeBlob(19, m_geometryBytes);
return s.final();
}
bool AMBESettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
d.readString(1, &m_title, "Simple PTT");
d.readU32(2, &m_rgbColor, QColor(255, 0, 0).rgb());
d.readBool(7, &m_useReverseAPI, false);
d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(9, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(10, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(11, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(12, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(18, &m_workspaceIndex, 0);
d.readBlob(19, &m_geometryBytes);
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_FEATURE_AMBESETTINGS_H_
#define INCLUDE_FEATURE_AMBESETTINGS_H_
#include <QByteArray>
#include <QString>
class Serializable;
struct AMBESettings
{
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
Serializable *m_rollupState;
int m_workspaceIndex;
QByteArray m_geometryBytes;
AMBESettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
};
#endif // INCLUDE_FEATURE_AMBESETTINGS_H_

View File

@ -0,0 +1,232 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 F4EXB //
// written by Edouard Griffiths //
// //
// 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 <algorithm>
#include <chrono>
#include <thread>
#include "audio/audiofifo.h"
#include "ambeworker.h"
MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgMbeDecode, Message)
MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgTest, Message)
AMBEWorker::AMBEWorker() :
m_running(false),
m_currentGainIn(0),
m_currentGainOut(0),
m_upsamplerLastValue(0.0f),
m_phase(0),
m_upsampling(1),
m_volume(1.0f)
{
m_audioBuffer.resize(48000);
m_audioBufferFill = 0;
m_audioFifo = 0;
std::fill(m_dvAudioSamples, m_dvAudioSamples+SerialDV::MBE_AUDIO_BLOCK_SIZE, 0);
setVolumeFactors();
}
AMBEWorker::~AMBEWorker()
{}
bool AMBEWorker::open(const std::string& deviceRef)
{
return m_dvController.open(deviceRef);
}
void AMBEWorker::close()
{
m_dvController.close();
}
void AMBEWorker::process()
{
m_running = true;
qDebug("AMBEWorker::process: started");
while (m_running)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
qDebug("AMBEWorker::process: stopped");
emit finished();
}
void AMBEWorker::stop()
{
m_running = false;
}
void AMBEWorker::handleInputMessages()
{
Message* message;
m_audioBufferFill = 0;
AudioFifo *audioFifo = 0;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (MsgMbeDecode::match(*message))
{
MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message;
int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 4;
float volume = pow(10.0, dBVolume / 10.0f);
int upsampling = decodeMsg->getUpsampling();
upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling;
if ((volume != m_volume) || (upsampling != m_upsampling))
{
m_volume = volume;
m_upsampling = upsampling;
setVolumeFactors();
}
m_upsampleFilter.useHP(decodeMsg->getUseHP());
if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate()))
{
if (upsampling > 1) {
upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels());
} else {
noUpsample(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels());
}
audioFifo = decodeMsg->getAudioFifo();
if (audioFifo && (m_audioBufferFill >= m_audioBuffer.size() - 960))
{
uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill) {
qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
}
else
{
qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: decode failed");
}
}
delete message;
if (m_inputMessageQueue.size() > 100)
{
qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: too many messages in queue. Flushing...");
m_inputMessageQueue.clear();
break;
}
}
if (audioFifo)
{
uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill) {
qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
m_timestamp = QDateTime::currentDateTime();
}
void AMBEWorker::pushMbeFrame(const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo)
{
m_audioFifo = audioFifo;
m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo));
}
bool AMBEWorker::isAvailable()
{
if (m_audioFifo == 0) {
return true;
}
return m_timestamp.time().msecsTo(QDateTime::currentDateTime().time()) > 1000; // 1 second inactivity timeout
}
bool AMBEWorker::hasFifo(AudioFifo *audioFifo)
{
return m_audioFifo == audioFifo;
}
void AMBEWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels)
{
for (int i = 0; i < nbSamplesIn; i++)
{
//float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]);
float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i];
float prev = m_upsamplerLastValue;
qint16 upsample;
for (int j = 1; j <= upsampling; j++)
{
upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]);
m_audioBuffer[m_audioBufferFill].l = channels & 1 ? m_compressor.compress(upsample) : 0;
m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? m_compressor.compress(upsample) : 0;
if (m_audioBufferFill < m_audioBuffer.size() - 1) {
++m_audioBufferFill;
}
}
m_upsamplerLastValue = cur;
}
if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
qDebug("AMBEWorker::upsample(%d): audio buffer is full check its size", upsampling);
}
}
void AMBEWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channels)
{
for (int i = 0; i < nbSamplesIn; i++)
{
float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i];
m_audioBuffer[m_audioBufferFill].l = channels & 1 ? cur*m_upsamplingFactors[0] : 0;
m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? cur*m_upsamplingFactors[0] : 0;
if (m_audioBufferFill < m_audioBuffer.size() - 1) {
++m_audioBufferFill;
}
}
if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
qDebug("AMBEWorker::noUpsample: audio buffer is full check its size");
}
}
void AMBEWorker::setVolumeFactors()
{
m_upsamplingFactors[0] = m_volume;
for (int i = 1; i <= m_upsampling; i++) {
m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling;
}
}

View File

@ -0,0 +1,156 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 F4EXB //
// written by Edouard Griffiths //
// //
// 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 SDRBASE_AMBE_AMBEWORKER_H_
#define SDRBASE_AMBE_AMBEWORKER_H_
#include <QObject>
#include <QDebug>
#include <QDateTime>
#include "export.h"
#include "dvcontroller.h"
#include "util/messagequeue.h"
#include "util/message.h"
#include "dsp/filtermbe.h"
#include "dsp/dsptypes.h"
#include "audio/audiocompressor.h"
class AudioFifo;
class SDRBASE_API AMBEWorker : public QObject {
Q_OBJECT
public:
class MsgTest : public Message
{
MESSAGE_CLASS_DECLARATION
public:
static MsgTest* create() { return new MsgTest(); }
private:
MsgTest() {}
};
class MsgMbeDecode : public Message
{
MESSAGE_CLASS_DECLARATION
public:
const unsigned char *getMbeFrame() const { return m_mbeFrame; }
SerialDV::DVRate getMbeRate() const { return m_mbeRate; }
int getVolumeIndex() const { return m_volumeIndex; }
unsigned char getChannels() const { return m_channels % 4; }
bool getUseHP() const { return m_useHP; }
int getUpsampling() const { return m_upsampling; }
AudioFifo *getAudioFifo() { return m_audioFifo; }
static MsgMbeDecode* create(
const unsigned char *mbeFrame,
int mbeRateIndex,
int volumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo)
{
return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upsampling, audioFifo);
}
private:
unsigned char m_mbeFrame[SerialDV::MBE_FRAME_MAX_LENGTH_BYTES];
SerialDV::DVRate m_mbeRate;
int m_volumeIndex;
unsigned char m_channels;
bool m_useHP;
int m_upsampling;
AudioFifo *m_audioFifo;
MsgMbeDecode(const unsigned char *mbeFrame,
SerialDV::DVRate mbeRate,
int volumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo) :
Message(),
m_mbeRate(mbeRate),
m_volumeIndex(volumeIndex),
m_channels(channels),
m_useHP(useHP),
m_upsampling(upsampling),
m_audioFifo(audioFifo)
{
memcpy((void *) m_mbeFrame, (const void *) mbeFrame, SerialDV::DVController::getNbMbeBytes(m_mbeRate));
}
};
AMBEWorker();
~AMBEWorker();
void pushMbeFrame(const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo);
bool open(const std::string& deviceRef); //!< Either serial device or ip:port
void close();
void process();
void stop();
bool isAvailable();
bool hasFifo(AudioFifo *audioFifo);
void postTest()
{
//emit inputMessageReady();
m_inputMessageQueue.push(MsgTest::create());
}
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
signals:
void finished();
public slots:
void handleInputMessages();
private:
void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels);
void noUpsample(short *in, int nbSamplesIn, unsigned char channels);
void setVolumeFactors();
SerialDV::DVController m_dvController;
AudioFifo *m_audioFifo;
QDateTime m_timestamp;
volatile bool m_running;
int m_currentGainIn;
int m_currentGainOut;
short m_dvAudioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE];
AudioVector m_audioBuffer;
uint m_audioBufferFill;
float m_upsamplerLastValue;
float m_phase;
MBEAudioInterpolatorFilter m_upsampleFilter;
int m_upsampling;
float m_volume;
float m_upsamplingFactors[7];
AudioCompressor m_compressor;
};
#endif // SDRBASE_AMBE_AMBEWORKER_H_

View File

@ -44,3 +44,4 @@ MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message)
MESSAGE_CLASS_DEFINITION(DSPMIMOSignalNotification, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message)
MESSAGE_CLASS_DEFINITION(DSPPushMbeFrame, Message)

View File

@ -345,4 +345,45 @@ private:
AudioType m_autioType;
};
class SDRBASE_API DSPPushMbeFrame : public Message {
MESSAGE_CLASS_DECLARATION
public:
DSPPushMbeFrame(
const unsigned char *mbeFrame,
int mbeRateIndex,
int mbeVolumeIndex,
unsigned char channels,
bool useHP,
int upsampling,
AudioFifo *audioFifo
) :
Message(),
m_mbeFrame(mbeFrame),
m_mbeRateIndex(mbeRateIndex),
m_mbeVolumeIndex(mbeVolumeIndex),
m_channels(channels),
m_useHP(useHP),
m_upsampling(upsampling),
m_audioFifo(audioFifo)
{ }
const unsigned char * getMbeFrame() const { return m_mbeFrame; }
int getMbeRateIndex() const { return m_mbeRateIndex; }
int getMbeVolumeIndex() const { return m_mbeVolumeIndex; }
unsigned char getChannels() const { return m_channels; }
bool getUseHP() const { return m_useHP; }
int getUpsampling() const { return m_upsampling; }
AudioFifo *getAudioFifo() const { return m_audioFifo; }
private:
const unsigned char *m_mbeFrame;
int m_mbeRateIndex;
int m_mbeVolumeIndex;
unsigned char m_channels;
bool m_useHP;
int m_upsampling;
AudioFifo *m_audioFifo;
};
#endif // INCLUDE_DSPCOMMANDS_H