1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-01 21:54:55 -04:00

Add DAB demodulator

This commit is contained in:
Jon Beniston
2021-04-16 22:56:15 +01:00
parent 76f09a17a7
commit 8a5685cdfd
36 changed files with 5274 additions and 6 deletions
+4
View File
@@ -18,6 +18,10 @@ add_subdirectory(demodchirpchat)
add_subdirectory(demodvorsc)
add_subdirectory(demodpacket)
if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND)
add_subdirectory(demoddab)
endif()
if(APT_FOUND)
add_subdirectory(demodapt)
endif()
+64
View File
@@ -0,0 +1,64 @@
project(demoddab)
add_definitions(-DDABLIN_AAC_FAAD2)
set(demoddab_SOURCES
dabdemod.cpp
dabdemodsettings.cpp
dabdemodbaseband.cpp
dabdemodsink.cpp
dabdemodplugin.cpp
dabdemodwebapiadapter.cpp
dabdemoddevice.cpp
)
set(demoddab_HEADERS
dabdemod.h
dabdemodsettings.h
dabdemodbaseband.h
dabdemodsink.h
dabdemodplugin.h
dabdemodwebapiadapter.h
dabdemoddevice.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${DAB_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(demoddab_SOURCES
${demoddab_SOURCES}
dabdemodgui.cpp
dabdemodgui.ui
)
set(demoddab_HEADERS
${demoddab_HEADERS}
dabdemodgui.h
)
set(TARGET_NAME demoddab)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demoddabsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demoddab_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${DAB_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
+541
View File
@@ -0,0 +1,541 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 "dabdemod.h"
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGDABDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGMapItem.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(DABDemod::MsgConfigureDABDemod, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABEnsembleName, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramName, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramData, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABSystemData, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramQuality, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABFIBQuality, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABSampleRate, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABData, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABReset, Message)
MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABResetService, Message)
const char * const DABDemod::m_channelIdURI = "sdrangel.channel.dabdemod";
const char * const DABDemod::m_channelId = "DABDemod";
DABDemod::DABDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new DABDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
m_basebandSink->setChannel(this);
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*)));
}
DABDemod::~DABDemod()
{
qDebug("DABDemod::~DABDemod");
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 DABDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void DABDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void DABDemod::start()
{
qDebug("DABDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
DABDemodBaseband::MsgConfigureDABDemodBaseband *msg = DABDemodBaseband::MsgConfigureDABDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void DABDemod::stop()
{
qDebug("DABDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool DABDemod::handleMessage(const Message& cmd)
{
if (MsgConfigureDABDemod::match(cmd))
{
MsgConfigureDABDemod& cfg = (MsgConfigureDABDemod&) cmd;
qDebug() << "DABDemod::handleMessage: MsgConfigureDABDemod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "DABDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (m_guiMessageQueue)
{
rep = new DSPSignalNotification(notif);
m_guiMessageQueue->push(rep);
}
return true;
}
else if (MsgDABSystemData::match(cmd))
{
MsgDABSystemData& report = (MsgDABSystemData&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABSystemData(report));
}
return true;
}
else if (MsgDABProgramQuality::match(cmd))
{
MsgDABProgramQuality& report = (MsgDABProgramQuality&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABProgramQuality(report));
}
return true;
}
else if (MsgDABFIBQuality::match(cmd))
{
MsgDABFIBQuality& report = (MsgDABFIBQuality&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABFIBQuality(report));
}
return true;
}
else if (MsgDABSampleRate::match(cmd))
{
MsgDABSampleRate& report = (MsgDABSampleRate&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABSampleRate(report));
}
return true;
}
else if (MsgDABEnsembleName::match(cmd))
{
MsgDABEnsembleName& report = (MsgDABEnsembleName&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABEnsembleName(report));
}
return true;
}
else if (MsgDABProgramName::match(cmd))
{
MsgDABProgramName& report = (MsgDABProgramName&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABProgramName(report));
}
return true;
}
else if (MsgDABProgramData::match(cmd))
{
MsgDABProgramData& report = (MsgDABProgramData&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABProgramData(report));
}
return true;
}
else if (MsgDABData::match(cmd))
{
MsgDABData& report = (MsgDABData&)cmd;
if (getMessageQueueToGUI())
{
getMessageQueueToGUI()->push(new MsgDABData(report));
}
return true;
}
else if (MsgDABReset::match(cmd))
{
MsgDABReset& report = (MsgDABReset&)cmd;
m_basebandSink->getInputMessageQueue()->push(new MsgDABReset(report));
return true;
}
else if (MsgDABResetService::match(cmd))
{
MsgDABResetService& report = (MsgDABResetService&)cmd;
m_basebandSink->getInputMessageQueue()->push(new MsgDABResetService(report));
return true;
}
else
{
return false;
}
}
void DABDemod::applySettings(const DABDemodSettings& settings, bool force)
{
qDebug() << "DABDemod::applySettings:"
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_program != m_settings.m_program) || force) {
reverseAPIKeys.append("program");
}
if ((settings.m_volume != m_settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
if ((settings.m_audioMute != m_settings.m_audioMute) || force) {
reverseAPIKeys.append("audioMute");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
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");
}
DABDemodBaseband::MsgConfigureDABDemodBaseband *msg = DABDemodBaseband::MsgConfigureDABDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
}
QByteArray DABDemod::serialize() const
{
return m_settings.serialize();
}
bool DABDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int DABDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings());
response.getDabDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int DABDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
DABDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("DABDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureDABDemod *msgToGUI = MsgConfigureDABDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void DABDemod::webapiUpdateChannelSettings(
DABDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getDabDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getDabDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("program")) {
settings.m_program = *response.getDabDemodSettings()->getProgram();
}
if (channelSettingsKeys.contains("m_volume")) {
settings.m_volume = response.getDabDemodSettings()->getVolume();
}
if (channelSettingsKeys.contains("audioMute")) {
settings.m_audioMute = response.getDabDemodSettings()->getAudioMute();
}
if (channelSettingsKeys.contains("audioDeviceName")) {
settings.m_audioDeviceName = *response.getDabDemodSettings()->getAudioDeviceName();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getDabDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getDabDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getDabDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getDabDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getDabDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getDabDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getDabDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getDabDemodSettings()->getReverseApiChannelIndex();
}
}
void DABDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DABDemodSettings& settings)
{
response.getDabDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getDabDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getDabDemodSettings()->setProgram(new QString(settings.m_program));
response.getDabDemodSettings()->setVolume(settings.m_volume);
response.getDabDemodSettings()->setAudioMute(settings.m_audioMute);
response.getDabDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
response.getDabDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getDabDemodSettings()->getTitle()) {
*response.getDabDemodSettings()->getTitle() = settings.m_title;
} else {
response.getDabDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getDabDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getDabDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getDabDemodSettings()->getReverseApiAddress()) {
*response.getDabDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getDabDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getDabDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getDabDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getDabDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void DABDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DABDemodSettings& 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 DABDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const DABDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("DABDemod"));
swgChannelSettings->setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings());
SWGSDRangel::SWGDABDemodSettings *swgDABDemodSettings = swgChannelSettings->getDabDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgDABDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgDABDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("program") || force) {
swgDABDemodSettings->setProgram(new QString(settings.m_program));
}
if (channelSettingsKeys.contains("volume") || force) {
swgDABDemodSettings->setVolume(settings.m_volume);
}
if (channelSettingsKeys.contains("audioMute") || force) {
swgDABDemodSettings->setAudioMute(settings.m_audioMute);
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgDABDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgDABDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgDABDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgDABDemodSettings->setStreamIndex(settings.m_streamIndex);
}
}
void DABDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "DABDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("DABDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
+375
View File
@@ -0,0 +1,375 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMOD_H
#define INCLUDE_DABDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QThread>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "dabdemodbaseband.h"
#include "dabdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class DABDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureDABDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DABDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureDABDemod* create(const DABDemodSettings& settings, bool force)
{
return new MsgConfigureDABDemod(settings, force);
}
private:
DABDemodSettings m_settings;
bool m_force;
MsgConfigureDABDemod(const DABDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgDABEnsembleName : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString getName() const { return m_name; }
int getId() const { return m_id; }
static MsgDABEnsembleName* create(const QString& name, int id)
{
return new MsgDABEnsembleName(name, id);
}
private:
QString m_name;
int m_id;
MsgDABEnsembleName(const QString& name, int id) :
Message(),
m_name(name),
m_id(id)
{ }
};
class MsgDABProgramName : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString getName() const { return m_name; }
int getId() const { return m_id; }
static MsgDABProgramName* create(const QString& name, int id)
{
return new MsgDABProgramName(name, id);
}
private:
QString m_name;
int m_id;
MsgDABProgramName(const QString& name, int id) :
Message(),
m_name(name),
m_id(id)
{ }
};
class MsgDABProgramData: public Message {
MESSAGE_CLASS_DECLARATION
public:
int getBitrate() const { return m_bitrate; }
const QString getAudio() const { return m_audio; }
const QString getLanguage() const { return m_language; }
const QString getProgramType() const { return m_programType; }
static MsgDABProgramData* create(int bitrate, const QString& audio, const QString& language, const QString& programType)
{
return new MsgDABProgramData(bitrate, audio, language, programType);
}
private:
int m_bitrate;
QString m_audio;
QString m_language;
QString m_programType;
MsgDABProgramData(int bitrate, const QString& audio, const QString& language, const QString& programType) :
Message(),
m_bitrate(bitrate),
m_audio(audio),
m_language(language),
m_programType(programType)
{ }
};
class MsgDABSystemData : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getSync() const { return m_sync; }
int16_t getSNR() const { return m_snr; }
int32_t getFrequencyOffset() const { return m_frequencyOffset; }
static MsgDABSystemData* create(bool sync, int16_t snr, int32_t frequencyOffset)
{
return new MsgDABSystemData(sync, snr, frequencyOffset);
}
private:
bool m_sync;
int16_t m_snr;
int32_t m_frequencyOffset;
MsgDABSystemData(bool sync, int16_t snr, int32_t frequencyOffset) :
Message(),
m_sync(sync),
m_snr(snr),
m_frequencyOffset(frequencyOffset)
{ }
};
class MsgDABProgramQuality : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getFrames() const { return m_frames; }
int getRS() const { return m_rs; }
int getAAC() const { return m_aac; }
static MsgDABProgramQuality* create(int frames, int rs, int aac)
{
return new MsgDABProgramQuality(frames, rs, aac);
}
private:
int m_frames;
int m_rs;
int m_aac;
MsgDABProgramQuality(int frames, int rs, int aac) :
Message(),
m_frames(frames),
m_rs(rs),
m_aac(aac)
{ }
};
class MsgDABFIBQuality : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getPercent() const { return m_percent; }
static MsgDABFIBQuality* create(int percent)
{
return new MsgDABFIBQuality(percent);
}
private:
int m_percent;
MsgDABFIBQuality(int percent) :
Message(),
m_percent(percent)
{ }
};
class MsgDABSampleRate : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
static MsgDABSampleRate* create(int sampleRate)
{
return new MsgDABSampleRate(sampleRate);
}
private:
int m_sampleRate;
MsgDABSampleRate(int sampleRate) :
Message(),
m_sampleRate(sampleRate)
{ }
};
class MsgDABData : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString getData() const { return m_data; }
static MsgDABData* create(const QString& data)
{
return new MsgDABData(data);
}
private:
QString m_data;
MsgDABData(const QString& data) :
Message(),
m_data(data)
{ }
};
class MsgDABReset : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgDABReset* create()
{
return new MsgDABReset();
}
private:
MsgDABReset() :
Message()
{ }
};
class MsgDABResetService : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgDABResetService* create()
{
return new MsgDABResetService();
}
private:
MsgDABResetService() :
Message()
{ }
};
DABDemod(DeviceAPI *deviceAPI);
virtual ~DABDemod();
virtual void destroy() { delete this; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return 0; }
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 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const DABDemodSettings& settings);
static void webapiUpdateChannelSettings(
DABDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
double getMagSq() const { return m_basebandSink->getMagSq(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
/* void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}*/
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
DABDemodBaseband* m_basebandSink;
DABDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const DABDemodSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DABDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const DABDemodSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_DABDEMOD_H
@@ -0,0 +1,203 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "dabdemodbaseband.h"
#include "dabdemod.h"
MESSAGE_CLASS_DEFINITION(DABDemodBaseband::MsgConfigureDABDemodBaseband, Message)
DABDemodBaseband::DABDemodBaseband(DABDemod *packetDemod) :
m_sink(packetDemod),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("DABDemodBaseband::DABDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(1000000));
m_channelizer = new DownChannelizer(&m_sink);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
}
DABDemodBaseband::~DABDemodBaseband()
{
m_inputMessageQueue.clear();
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
delete m_channelizer;
}
void DABDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void DABDemodBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&DABDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void DABDemodBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&DABDemodBaseband::handleData
);
m_running = false;
}
void DABDemodBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void DABDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void DABDemodBaseband::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 DABDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool DABDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureDABDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureDABDemodBaseband& cfg = (MsgConfigureDABDemodBaseband&) cmd;
qDebug() << "DABDemodBaseband::handleMessage: MsgConfigureDABDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "DABDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else if (DABDemod::MsgDABReset::match(cmd))
{
m_sink.reset();
return true;
}
else if (DABDemod::MsgDABResetService::match(cmd))
{
m_sink.resetService();
return true;
}
else
{
return false;
}
}
void DABDemodBaseband::applySettings(const DABDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(DABDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);
audioDeviceManager->removeAudioSink(m_sink.getAudioFifo());
audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_sink.getAudioSampleRate() != audioSampleRate)
{
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_sink.applyAudioSampleRate(audioSampleRate);
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
void DABDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
@@ -0,0 +1,95 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODBASEBAND_H
#define INCLUDE_DABDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "dabdemodsink.h"
class DownChannelizer;
class ChannelAPI;
class DABDemod;
class DABDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureDABDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DABDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureDABDemodBaseband* create(const DABDemodSettings& settings, bool force)
{
return new MsgConfigureDABDemodBaseband(settings, force);
}
private:
DABDemodSettings m_settings;
bool m_force;
MsgConfigureDABDemodBaseband(const DABDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
DABDemodBaseband(DABDemod *packetDemod);
~DABDemodBaseband();
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
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_sink.getMagSqLevels(avg, peak, nbSamples);
}
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setBasebandSampleRate(int sampleRate);
void setChannel(ChannelAPI *channel);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
DABDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
DABDemodSettings m_settings;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const DABDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_DABDEMODBASEBAND_H
@@ -0,0 +1,120 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2010, 2011, 2012 Jan van Katwijk (J.vanKatwijk@gmail.com)
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 "dabdemoddevice.h"
// Implementation of default device, as not included in library
deviceHandler::deviceHandler(void)
{
lastFrequency = 100000;
}
deviceHandler::~deviceHandler(void)
{
}
bool deviceHandler::restartReader(int32_t)
{
return true;
}
void deviceHandler::stopReader(void)
{
}
void deviceHandler::run(void)
{
}
int32_t deviceHandler::getSamples(std::complex<float> *v, int32_t amount)
{
(void)v;
(void)amount;
return 0;
}
int32_t deviceHandler::Samples(void)
{
return 0;
}
int32_t deviceHandler::defaultFrequency(void)
{
return 220000000;
}
void deviceHandler::resetBuffer(void)
{
}
void deviceHandler::setGain(int32_t x)
{
(void)x;
}
bool deviceHandler::has_autogain(void)
{
return false;
}
void deviceHandler::set_autogain(bool b)
{
(void)b;
}
void deviceHandler::set_ifgainReduction(int x)
{
(void)x;
}
void deviceHandler::set_lnaState(int x)
{
(void)x;
}
// Device with source data from SDRangel
// Other methods not needed for DAB library
DABDemodDevice::DABDemodDevice() :
m_iqBuffer (4 * 1024 * 1024)
{
}
void DABDemodDevice::putSample(std::complex<float> s)
{
std::complex<float> localBuf[1];
localBuf[0] = s;
if (m_iqBuffer.GetRingBufferWriteAvailable() > 0)
m_iqBuffer.putDataIntoBuffer(localBuf, 1);
}
int32_t DABDemodDevice::getSamples(std::complex<float> *buf, int32_t size)
{
return m_iqBuffer.getDataFromBuffer(buf, size);
}
int32_t DABDemodDevice::Samples(void)
{
return m_iqBuffer.GetRingBufferReadAvailable();
}
void DABDemodDevice::resetBuffer(void)
{
m_iqBuffer.FlushRingBuffer();
}
@@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODDEVICE_H
#define INCLUDE_DABDEMODDEVICE_H
#include <dab-api.h>
class DABDemodDevice : public deviceHandler {
public:
DABDemodDevice();
void putSample(std::complex<float> s);
int32_t getSamples(std::complex<float> *buf, int32_t size) override;
int32_t Samples(void) override;
void resetBuffer(void) override;
private:
RingBuffer<std::complex<float>> m_iqBuffer;
};
#endif // INCLUDE_DABDEMODDEVICE_H
+651
View File
@@ -0,0 +1,651 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 <limits>
#include <ctype.h>
#include <QDebug>
#include <QAction>
#include <QRegExp>
#include "dabdemodgui.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_dabdemodgui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "gui/audioselectdialog.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "dsp/dspengine.h"
#include "gui/crightclickenabler.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "dabdemod.h"
#include "dabdemodsink.h"
// Table column indexes
#define PROGRAMS_COL_NAME 0
#define PROGRAMS_COL_ID 1
#define PROGRAMS_COL_FREQUENCY 2
void DABDemodGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
// Trailing spaces are for sort arrow
int row = ui->programs->rowCount();
ui->programs->setRowCount(row + 1);
ui->programs->setItem(row, PROGRAMS_COL_NAME, new QTableWidgetItem("Some Random Radio Station"));
ui->programs->setItem(row, PROGRAMS_COL_ID, new QTableWidgetItem("123456"));
ui->programs->setItem(row, PROGRAMS_COL_FREQUENCY, new QTableWidgetItem("200.000"));
ui->programs->resizeColumnsToContents();
ui->programs->removeRow(row);
}
// Columns in table reordered
void DABDemodGUI::programs_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void DABDemodGUI::programs_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_columnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void DABDemodGUI::columnSelectMenu(QPoint pos)
{
menu->popup(ui->programs->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void DABDemodGUI::columnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->programs->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *DABDemodGUI::createCheckableItem(QString &text, int idx, bool checked)
{
QAction *action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
return action;
}
DABDemodGUI* DABDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
DABDemodGUI* gui = new DABDemodGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void DABDemodGUI::destroy()
{
delete this;
}
void DABDemodGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray DABDemodGUI::serialize() const
{
return m_settings.serialize();
}
bool DABDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
// Add row to table
void DABDemodGUI::addProgramName(const DABDemod::MsgDABProgramName& program)
{
ui->programs->setSortingEnabled(false);
int row = ui->programs->rowCount();
ui->programs->setRowCount(row + 1);
QTableWidgetItem *nameItem = new QTableWidgetItem();
QTableWidgetItem *idItem = new QTableWidgetItem();
QTableWidgetItem *frequencyItem = new QTableWidgetItem();
ui->programs->setItem(row, PROGRAMS_COL_NAME, nameItem);
ui->programs->setItem(row, PROGRAMS_COL_ID, idItem);
ui->programs->setItem(row, PROGRAMS_COL_FREQUENCY, frequencyItem);
nameItem->setText(program.getName());
idItem->setText(QString::number(program.getId()));
double frequencyInHz;
if (ChannelWebAPIUtils::getCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz))
{
double frequencyInMHz = (frequencyInHz+m_settings.m_inputFrequencyOffset)/1e6;
frequencyItem->setText(QString::number(frequencyInMHz, 'f', 3));
frequencyItem->setData(Qt::UserRole, frequencyInHz+m_settings.m_inputFrequencyOffset);
}
else
frequencyItem->setData(Qt::UserRole, 0.0);
ui->programs->setSortingEnabled(true);
filterRow(row);
}
// Tune to the selected program
void DABDemodGUI::on_programs_cellDoubleClicked(int row, int column)
{
(void) column;
m_settings.m_program = ui->programs->item(row, PROGRAMS_COL_NAME)->text();
double frequencyInHz = ui->programs->item(row, PROGRAMS_COL_FREQUENCY)->data(Qt::UserRole).toDouble();
ChannelWebAPIUtils::setCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz-m_settings.m_inputFrequencyOffset);
applySettings();
}
bool DABDemodGUI::handleMessage(const Message& message)
{
if (DABDemod::MsgConfigureDABDemod::match(message))
{
qDebug("DABDemodGUI::handleMessage: DABDemod::MsgConfigureDABDemod");
const DABDemod::MsgConfigureDABDemod& cfg = (DABDemod::MsgConfigureDABDemod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
m_basebandSampleRate = notif.getSampleRate();
return true;
}
else if (DABDemod::MsgDABEnsembleName::match(message))
{
DABDemod::MsgDABEnsembleName& report = (DABDemod::MsgDABEnsembleName&) message;
ui->ensemble->setText(report.getName());
return true;
}
else if (DABDemod::MsgDABProgramName::match(message))
{
DABDemod::MsgDABProgramName& report = (DABDemod::MsgDABProgramName&) message;
addProgramName(report);
return true;
}
else if (DABDemod::MsgDABProgramData::match(message))
{
DABDemod::MsgDABProgramData& report = (DABDemod::MsgDABProgramData&) message;
ui->program->setText(m_settings.m_program);
ui->bitrate->setText(QString("%1kbps").arg(report.getBitrate()));
ui->audio->setText(report.getAudio());
ui->language->setText(report.getLanguage());
ui->programType->setText(report.getProgramType());
return true;
}
else if (DABDemod::MsgDABSystemData::match(message))
{
DABDemod::MsgDABSystemData& report = (DABDemod::MsgDABSystemData&) message;
if (report.getSync())
ui->sync->setText("Yes");
else
ui->sync->setText("No");
ui->snr->setText(QString("%1").arg(report.getSNR()));
ui->freqOffset->setText(QString("%1").arg(report.getFrequencyOffset()));
return true;
}
else if (DABDemod::MsgDABProgramQuality::match(message))
{
DABDemod::MsgDABProgramQuality& report = (DABDemod::MsgDABProgramQuality&) message;
ui->frames->setText(QString("%1%").arg(report.getFrames()));
ui->rs->setText(QString("%1%").arg(report.getRS()));
ui->aac->setText(QString("%1%").arg(report.getAAC()));
return true;
}
else if (DABDemod::MsgDABFIBQuality::match(message))
{
DABDemod::MsgDABFIBQuality& report = (DABDemod::MsgDABFIBQuality&) message;
ui->fib->setText(QString("%1%").arg(report.getPercent()));
return true;
}
else if (DABDemod::MsgDABSampleRate::match(message))
{
DABDemod::MsgDABSampleRate& report = (DABDemod::MsgDABSampleRate&) message;
qDebug() << "Ssample rate: " << report.getSampleRate();
ui->sampleRate->setText(QString("%1k").arg(report.getSampleRate()/1000.0, 0, 'f', 0));
return true;
}
else if (DABDemod::MsgDABData::match(message))
{
DABDemod::MsgDABData& report = (DABDemod::MsgDABData&) message;
ui->data->setText(report.getData());
return true;
}
return false;
}
void DABDemodGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void DABDemodGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void DABDemodGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void DABDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void DABDemodGUI::on_audioMute_toggled(bool checked)
{
m_settings.m_audioMute = checked;
applySettings();
}
void DABDemodGUI::on_volume_valueChanged(int value)
{
ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
m_settings.m_volume = value / 10.0;
applySettings();
}
void DABDemodGUI::on_rfBW_valueChanged(int value)
{
float bw = value * 100.0f;
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_channelMarker.setBandwidth(bw);
m_settings.m_rfBandwidth = bw;
applySettings();
}
void DABDemodGUI::on_filter_editingFinished()
{
m_settings.m_filter = ui->filter->text();
filter();
applySettings();
}
void DABDemodGUI::on_clearTable_clicked()
{
ui->programs->setRowCount(0);
// Reset the DAB library, so it re-outputs program names
DABDemod::MsgDABReset* message = DABDemod::MsgDABReset::create();
m_dabDemod->getInputMessageQueue()->push(message);
}
void DABDemodGUI::filterRow(int row)
{
bool hidden = false;
if (m_settings.m_filter != "")
{
QRegExp re(m_settings.m_filter);
QTableWidgetItem *fromItem = ui->programs->item(row, PROGRAMS_COL_NAME);
if (re.indexIn(fromItem->text()) == -1)
hidden = true;
}
ui->programs->setRowHidden(row, hidden);
}
void DABDemodGUI::filter()
{
for (int i = 0; i < ui->programs->rowCount(); i++)
{
filterRow(i);
}
}
void DABDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void DABDemodGUI::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_dabDemod->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();
}
DABDemodGUI::DABDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::DABDemodGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_doApplySettings(true),
m_tickCount(0),
m_channelFreq(0.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_dabDemod = reinterpret_cast<DABDemod*>(rxChannel);
m_dabDemod->setMessageQueueToGUI(getInputMessageQueue());
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::yellow);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle("Packet Demodulator");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
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(handleInputMessages()));
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->programs->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->programs->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
menu = new QMenu(ui->programs);
for (int i = 0; i < ui->programs->horizontalHeader()->count(); i++)
{
QString text = ui->programs->horizontalHeaderItem(i)->text();
menu->addAction(createCheckableItem(text, i, true));
}
ui->programs->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->programs->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->programs->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(programs_sectionMoved(int, int, int)));
connect(ui->programs->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(programs_sectionResized(int, int, int)));
displaySettings();
applySettings(true);
}
DABDemodGUI::~DABDemodGUI()
{
delete ui;
}
void DABDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void DABDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
DABDemod::MsgConfigureDABDemod* message = DABDemod::MsgConfigureDABDemod::create( m_settings, force);
m_dabDemod->getInputMessageQueue()->push(message);
}
}
void DABDemodGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
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->audioMute->setChecked(m_settings.m_audioMute);
ui->volume->setValue(m_settings.m_volume * 10.0);
ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 0, 'f', 1));
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
displayStreamIndex();
ui->filter->setText(m_settings.m_filter);
// Order and size columns
QHeaderView *header = ui->programs->horizontalHeader();
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
{
bool hidden = m_settings.m_columnSizes[i] == 0;
header->setSectionHidden(i, hidden);
menu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_columnSizes[i] > 0)
ui->programs->setColumnWidth(i, m_settings.m_columnSizes[i]);
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
}
filter();
blockApplySettings(false);
}
void DABDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void DABDemodGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void DABDemodGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void DABDemodGUI::resetService()
{
// Reset DAB audio service, to avoid unpleasent noise when changing frequency
DABDemod::MsgDABResetService* message = DABDemod::MsgDABResetService::create();
m_dabDemod->getInputMessageQueue()->push(message);
// Clear current program
ui->program->setText("-");
ui->ensemble->setText("-");
ui->programType->setText("-");
ui->language->setText("-");
ui->audio->setText("-");
ui->bitrate->setText("-");
ui->sampleRate->setText("-");
ui->data->setText("");
}
void DABDemodGUI::on_channel_currentIndexChanged(int index)
{
(void) index;
QString text = ui->channel->currentText();
if (!text.isEmpty())
{
resetService();
// Tune to requested channel
QString freq = text.split(" ")[2];
m_channelFreq = freq.toDouble() * 1e6;
ChannelWebAPIUtils::setCenterFrequency(m_dabDemod->getDeviceSetIndex(), m_channelFreq - m_settings.m_inputFrequencyOffset);
}
}
void DABDemodGUI::audioSelect()
{
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName);
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void DABDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_dabDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
}
// Update channel combobox according to current frequency
double frequencyInHz;
if (ChannelWebAPIUtils::getCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz))
{
frequencyInHz += m_settings.m_inputFrequencyOffset;
double frequencyInkHz = std::round(frequencyInHz / 1e3);
double frequencyInMHz = frequencyInkHz / 1000.0;
if (m_channelFreq != frequencyInMHz * 1e6)
{
QString freqText = QString::number(frequencyInMHz, 'f', 3);
int i;
for (i = 0; i < ui->channel->count(); i++)
{
if (ui->channel->itemText(i).contains(freqText))
{
ui->channel->blockSignals(true);
ui->channel->setCurrentIndex(i);
ui->channel->blockSignals(false);
break;
}
}
if (i == ui->channel->count())
{
ui->channel->setCurrentIndex(-1);
m_channelFreq = -1.0;
}
}
}
m_tickCount++;
}
+113
View File
@@ -0,0 +1,113 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODGUI_H
#define INCLUDE_DABDEMODGUI_H
#include <QAbstractListModel>
#include <QTableWidgetItem>
#include <QMenu>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "util/messagequeue.h"
#include "dabdemodsettings.h"
#include "dabdemod.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class DABDemod;
class DABDemodGUI;
namespace Ui {
class DABDemodGUI;
}
class DABDemodGUI;
class DABDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static DABDemodGUI* 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::DABDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
DABDemodSettings m_settings;
bool m_doApplySettings;
DABDemod* m_dabDemod;
int m_basebandSampleRate;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
double m_channelFreq;
QMenu *menu; // Column select context menu
explicit DABDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~DABDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
void addProgramName(const DABDemod::MsgDABProgramName& program);
bool handleMessage(const Message& message);
void audioSelect();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void resetService();
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_audioMute_toggled(bool checked);
void on_volume_valueChanged(int value);
void on_rfBW_valueChanged(int index);
void on_filter_editingFinished();
void on_clearTable_clicked();
void on_programs_cellDoubleClicked(int row, int column);
void on_channel_currentIndexChanged(int index);
void filterRow(int row);
void filter();
void programs_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void programs_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_DABDEMODGUI_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 "dabdemodgui.h"
#endif
#include "dabdemod.h"
#include "dabdemodwebapiadapter.h"
#include "dabdemodplugin.h"
const PluginDescriptor DABDemodPlugin::m_pluginDescriptor = {
DABDemod::m_channelId,
QStringLiteral("DAB Demodulator"),
QStringLiteral("6.9.0"),
QStringLiteral("(c) Jon Beniston, M7RCE. DAB library by Jvan Katwijk"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
DABDemodPlugin::DABDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& DABDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void DABDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(DABDemod::m_channelIdURI, DABDemod::m_channelId, this);
}
void DABDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
DABDemod *instance = new DABDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* DABDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* DABDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return DABDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* DABDemodPlugin::createChannelWebAPIAdapter() const
{
return new DABDemodWebAPIAdapter();
}
@@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODPLUGIN_H
#define INCLUDE_DABDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class DABDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.dabdemod")
public:
explicit DABDemodPlugin(QObject* parent = NULL);
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 // INCLUDE_DABDEMODPLUGIN_H
@@ -0,0 +1,149 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "dabdemodsettings.h"
DABDemodSettings::DABDemodSettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void DABDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 1537000.0f;
m_filter = "";
m_volume = 5.0f;
m_audioMute = false;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_rgbColor = QColor(77, 105, 25).rgb();
m_title = "DAB Demodulator";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray DABDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_streamIndex);
s.writeString(3, m_filter);
s.writeFloat(4, m_rfBandwidth);
s.writeFloat(5, m_volume);
s.writeBool(6, m_audioMute);
s.writeString(7, m_audioDeviceName);
if (m_channelMarker) {
s.writeBlob(8, m_channelMarker->serialize());
}
s.writeU32(9, m_rgbColor);
s.writeString(10, m_title);
s.writeBool(11, m_useReverseAPI);
s.writeString(12, m_reverseAPIAddress);
s.writeU32(13, m_reverseAPIPort);
s.writeU32(14, m_reverseAPIDeviceIndex);
s.writeU32(15, m_reverseAPIChannelIndex);
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]);
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
s.writeS32(200 + i, m_columnSizes[i]);
return s.final();
}
bool DABDemodSettings::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.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(2, &m_streamIndex, 0);
d.readString(3, &m_filter, "");
d.readFloat(4, &m_rfBandwidth, 1537000.0f);
d.readFloat(5, &m_volume, 5.0f);
d.readBool(6, &m_audioMute, false);
d.readString(7, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readBlob(8, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readU32(9, &m_rgbColor, QColor(77, 105, 25).rgb());
d.readString(10, &m_title, "DAB Demodulator");
d.readBool(11, &m_useReverseAPI, false);
d.readString(12, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(13, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(14, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(15, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i);
for (int i = 0; i < DABDEMOD_COLUMNS; i++)
d.readS32(200 + i, &m_columnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}
@@ -0,0 +1,59 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODSETTINGS_H
#define INCLUDE_DABDEMODSETTINGS_H
#include <QByteArray>
class Serializable;
// Number of columns in the table
#define DABDEMOD_COLUMNS 3
struct DABDemodSettings
{
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth;
QString m_filter;
QString m_program;
Real m_volume;
bool m_audioMute;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
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;
int m_columnIndexes[DABDEMOD_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[DABDEMOD_COLUMNS]; //!< Size of the columns in the table
DABDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_DABDEMODSETTINGS_H */
+644
View File
@@ -0,0 +1,644 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 <complex.h>
#include "dsp/dspengine.h"
#include "dsp/datafifo.h"
#include "util/db.h"
#include "pipes/pipeendpoint.h"
#include "maincore.h"
#include "dabdemod.h"
#include "dabdemodsink.h"
// Callbacks from DAB library
void syncHandler(bool value, void *ctx)
{
(void)value;
(void)ctx;
}
void systemDataHandler(bool sync, int16_t snr, int32_t freqOffset, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->systemData(sync, snr, freqOffset);
}
void ensembleNameHandler(std::string name, int32_t id, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->ensembleName(QString::fromStdString(name), id);
}
void programNameHandler(std::string name, int32_t id, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->programName(QString::fromStdString(name), id);
}
void fibQualityHandler(int16_t percent, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->fibQuality(percent);
}
void audioHandler(int16_t *buffer, int size, int samplerate, bool stereo, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->audio(buffer, size, samplerate, stereo);
}
void dataHandler(std::string data, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->data(QString::fromStdString(data));
}
void byteHandler(uint8_t *data, int16_t a, uint8_t b, void *ctx)
{
(void)data;
(void)a;
(void)b;
(void)ctx;
}
// Note: North America has different table
static const char *dabProgramType[] =
{
"No programme type",
"News",
"Current Affairs",
"Information",
"Sport",
"Education",
"Drama",
"Culture",
"Science",
"Varied",
"Pop Music",
"Rock Music",
"Easy Listening Music",
"Light Classical",
"Serious Classical",
"Other Music",
"Weather/meteorology",
"Finance/Business",
"Children's programmes",
"Social Affairs",
"Religion",
"Phone In",
"Travel",
"Leisure",
"Jazz Music",
"Country Music",
"National Music",
"Oldies Music",
"Folk Music",
"Documentary",
"Not used",
"Not used",
};
static const char *dabLanguageCode[] =
{
"Unknown",
"Albanian",
"Breton",
"Catalan",
"Croatian",
"Welsh",
"Czech",
"Danish",
"German",
"English",
"Spanish",
"Esperanto",
"Estonian",
"Basque",
"Faroese",
"French",
"Frisian",
"Irish",
"Gaelic",
"Galician",
"Icelandic",
"Italian",
"Sami",
"Latin",
"Latvian",
"Luxembourgian",
"Lithuanian",
"Hungarian",
"Maltese",
"Dutch",
"Norwegian",
"Occitan",
"Polish",
"Portuguese",
"Romanian",
"Romansh",
"Serbian",
"Slovak",
"Slovene",
"Finnish",
"Swedish",
"Turkish",
"Flemish",
"Walloon",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Background sound",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Zulu",
"Vietnamese",
"Uzbek",
"Urdu",
"Ukranian",
"Thai",
"Telugu",
"Tatar",
"Tamil",
"Tadzhik",
"Swahili",
"Sranan Tongo",
"Somali",
"Sinhalese",
"Shona",
"Serbo-Croat",
"Rusyn",
"Russian",
"Quechua",
"Pushtu",
"Punjabi",
"Persian",
"Papiamento",
"Oriya",
"Nepali",
"Ndebele",
"Marathi",
"Moldavian",
"Malaysian",
"Malagasay",
"Macedonian",
"Laotian",
"Korean",
"Khmer",
"Kazakh",
"Kannada",
"Japanese",
"Indonesian",
"Hindi",
"Hebrew",
"Hausa",
"Gurani",
"Gujurati",
"Greek",
"Georgian",
"Fulani",
"Dari",
"Chuvash",
"Chinese",
"Burmese",
"Bulgarian",
"Bengali",
"Belorussian",
"Bambora",
"Azerbaijani",
"Assamese",
"Armenian",
"Arabic",
"Amharic",
};
void programDataHandler(audiodata *data, void *ctx)
{
QString audio;
if (data->ASCTy == 0)
audio = "DAB";
else if (data->ASCTy == 63)
audio = "DAB+";
else
audio = "Unknown";
QString language = "";
if ((data->language < 0x80) && (data->language >= 0))
language = dabLanguageCode[data->language & 0x7f];
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->programData(data->bitRate, audio, language, dabProgramType[data->programType & 0x1f]);
}
void programQualityHandler(int16_t frames, int16_t rs, int16_t aac, void *ctx)
{
DABDemodSink *sink = (DABDemodSink *)ctx;
sink->programQuality(frames, rs, aac);
}
void motDataHandler(std::string data, int a, void *ctx)
{
(void)data;
(void)ctx;
}
void DABDemodSink::systemData(bool sync, int16_t snr, int32_t freqOffset)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABSystemData *msg = DABDemod::MsgDABSystemData::create(sync, snr, freqOffset);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::ensembleName(const QString& name, int id)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABEnsembleName *msg = DABDemod::MsgDABEnsembleName::create(name, id);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::programName(const QString& name, int id)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABProgramName *msg = DABDemod::MsgDABProgramName::create(name, id);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::programData(int bitrate, const QString& audio, const QString& language, const QString& programType)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABProgramData *msg = DABDemod::MsgDABProgramData::create(bitrate, audio, language, programType);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::fibQuality(int16_t percent)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABFIBQuality *msg = DABDemod::MsgDABFIBQuality::create(percent);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::programQuality(int16_t frames, int16_t rs, int16_t aac)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABProgramQuality *msg = DABDemod::MsgDABProgramQuality::create(frames, rs, aac);
getMessageQueueToChannel()->push(msg);
}
}
void DABDemodSink::data(const QString& data)
{
if (getMessageQueueToChannel())
{
DABDemod::MsgDABData *msg = DABDemod::MsgDABData::create(data);
getMessageQueueToChannel()->push(msg);
}
}
static int16_t scale(int16_t sample, float factor)
{
int32_t prod = (int32_t)(((int32_t)sample) * factor);
prod = std::min(prod, 32767);
prod = std::max(prod, -32768);
return (int16_t)prod;
}
void DABDemodSink::audio(int16_t *buffer, int size, int samplerate, bool stereo)
{
(void)stereo;
(void)samplerate;
if (samplerate != m_dabAudioSampleRate)
{
applyDABAudioSampleRate(samplerate);
if (getMessageQueueToChannel())
{
DABDemod::MsgDABSampleRate *msg = DABDemod::MsgDABSampleRate::create(samplerate);
getMessageQueueToChannel()->push(msg);
}
}
// buffer is always 2 channels
for (int i = 0; i < size; i+=2)
{
Complex ci, ca;
if (!m_settings.m_audioMute)
{
ci.real(buffer[i]);
ci.imag(buffer[i+1]);
}
else
{
ci.real(0.0f);
ci.imag(0.0f);
}
if (m_audioInterpolatorDistance < 1.0f) // interpolate
{
while (!m_audioInterpolator.interpolate(&m_audioInterpolatorDistanceRemain, ci, &ca))
{
processOneAudioSample(ca);
m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance;
}
}
else // decimate
{
if (m_audioInterpolator.decimate(&m_audioInterpolatorDistanceRemain, ci, &ca))
{
processOneAudioSample(ca);
m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance;
}
}
}
}
void DABDemodSink::reset()
{
dabReset(m_dab);
}
void DABDemodSink::resetService()
{
dabReset_msc(m_dab);
}
void DABDemodSink::processOneAudioSample(Complex &ci)
{
float factor = m_settings.m_volume / 5.0f; // Should this be 5 or 10? 5 allows some positive gain
qint16 l = scale(ci.real(), factor);
qint16 r = scale(ci.real(), factor);
m_audioBuffer[m_audioBufferFill].l = l;
m_audioBuffer[m_audioBufferFill].r = r;
++m_audioBufferFill;
if (m_audioBufferFill >= m_audioBuffer.size())
{
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill)
{
qDebug("DABDemodSink::audio: %u/%u audio samples written", res, m_audioBufferFill);
m_audioFifo.clear();
}
m_audioBufferFill = 0;
}
m_demodBuffer[m_demodBufferFill++] = l; // FIXME: What about right channel?
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
if (dataFifos)
{
QList<DataFifo*>::iterator it = dataFifos->begin();
for (; it != dataFifos->end(); ++it) {
(*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
}
}
m_demodBufferFill = 0;
}
}
DABDemodSink::DABDemodSink(DABDemod *packetDemod) :
m_dabDemod(packetDemod),
m_audioSampleRate(48000),
m_dabAudioSampleRate(10000), // Unused value to begin with
m_channelSampleRate(DABDEMOD_CHANNEL_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_audioFifo(48000)
{
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_magsq = 0.0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
int mode = 1; // Latest DAB spec only has mode 1
m_dab = dabInit(&m_device,
mode,
syncHandler,
systemDataHandler,
ensembleNameHandler,
programNameHandler,
fibQualityHandler,
audioHandler,
dataHandler,
byteHandler,
programDataHandler,
programQualityHandler,
motDataHandler,
nullptr,
nullptr,
this);
dabStartProcessing(m_dab);
}
DABDemodSink::~DABDemodSink()
{
dabExit(m_dab);
}
void DABDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance == 1.0f)
{
processOneSample(c);
}
else if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void DABDemodSink::processOneSample(Complex &ci)
{
// Calculate average and peak levels for level meter
double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();
Real magsq = (Real)(magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED));
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
// Send sample to DAB library
std::complex<float> c;
c.real(ci.real()/SDR_RX_SCALED);
c.imag(ci.imag()/SDR_RX_SCALED);
m_device.putSample(c);
}
void DABDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "DABDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) channelSampleRate / (Real) DABDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void DABDemodSink::applySettings(const DABDemodSettings& settings, bool force)
{
qDebug() << "DABDemodSink::applySettings:"
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) DABDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
if ((settings.m_program != m_settings.m_program) || force)
{
if (!settings.m_program.isEmpty())
{
QByteArray ba = settings.m_program.toLatin1();
const char *program = ba.data();
if (!is_audioService (m_dab, program))
qWarning() << settings.m_program << " is not an audio service";
else
{
dataforAudioService(m_dab, program, &m_ad, 0);
if (!m_ad.defined)
qWarning() << settings.m_program << " audio data is not defined";
else
{
dabReset_msc(m_dab);
set_audioChannel(m_dab, &m_ad);
}
}
}
}
m_settings = settings;
}
// Called when audio device sample rate changes
void DABDemodSink::applyAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("DABDemodSink::applyAudioSampleRate: invalid sample rate: %d", sampleRate);
return;
}
qDebug("DABDemodSink::applyAudioSampleRate: m_audioSampleRate: %d m_dabAudioSampleRate: %d", sampleRate, m_dabAudioSampleRate);
m_audioInterpolator.create(16, m_dabAudioSampleRate, m_dabAudioSampleRate/2.2f);
m_audioInterpolatorDistanceRemain = 0;
m_audioInterpolatorDistance = (Real) m_dabAudioSampleRate / (Real) sampleRate;
m_audioFifo.setSize(sampleRate);
m_audioSampleRate = sampleRate;
}
// Called when DAB audio sample rate changes
void DABDemodSink::applyDABAudioSampleRate(int sampleRate)
{
qDebug("DABDemodSink::applyDABAudioSampleRate: m_audioSampleRate: %d new m_dabAudioSampleRate: %d", m_audioSampleRate, sampleRate);
m_audioInterpolator.create(16, sampleRate, sampleRate/2.2f);
m_audioInterpolatorDistanceRemain = 0;
m_audioInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
m_dabAudioSampleRate = sampleRate;
}
+147
View File
@@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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_DABDEMODSINK_H
#define INCLUDE_DABDEMODSINK_H
#include <QVector>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "audio/audiofifo.h"
#include "dabdemodsettings.h"
#include "dabdemoddevice.h"
#include <vector>
#include <dab-api.h>
#define DABDEMOD_CHANNEL_SAMPLE_RATE 2048000
class ChannelAPI;
class DABDemod;
class DABDemodSink : public ChannelSampleSink {
public:
DABDemodSink(DABDemod *packetDemod);
~DABDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const DABDemodSettings& settings, bool force = false);
void applyAudioSampleRate(int sampleRate);
void applyDABAudioSampleRate(int sampleRate);
int getAudioSampleRate() const { return m_audioSampleRate; }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
double getMagSq() const { return m_magsq; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
void reset();
void resetService();
// Callbacks
void systemData(bool sync, int16_t snr, int32_t freqOffset);
void ensembleName(const QString& name, int id);
void programName(const QString& name, int id);
void programData(int bitrate, const QString& audio, const QString& language, const QString& programType);
void audio(int16_t *buffer, int size, int samplerate, bool stereo);
void programQuality(int16_t frames, int16_t rs, int16_t aac);
void fibQuality(int16_t percent);
void data(const QString& data);
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
DABDemod *m_dabDemod;
DABDemodSettings m_settings;
ChannelAPI *m_channel;
int m_audioSampleRate; // Output device sample rate
int m_dabAudioSampleRate;
int m_channelSampleRate;
int m_channelFrequencyOffset;
void *m_dab;
DABDemodDevice m_device;
audiodata m_ad;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MessageQueue *m_messageQueueToChannel;
MovingAverageUtil<Real, double, 16> m_movingAverage;
Interpolator m_audioInterpolator;
Real m_audioInterpolatorDistance;
Real m_audioInterpolatorDistanceRemain;
AudioVector m_audioBuffer;
AudioFifo m_audioFifo;
uint32_t m_audioBufferFill;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
void processOneSample(Complex &ci);
void processOneAudioSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
};
#endif // INCLUDE_DABDEMODSINK_H
@@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// 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 "dabdemod.h"
#include "dabdemodwebapiadapter.h"
DABDemodWebAPIAdapter::DABDemodWebAPIAdapter()
{}
DABDemodWebAPIAdapter::~DABDemodWebAPIAdapter()
{}
int DABDemodWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings());
response.getDabDemodSettings()->init();
DABDemod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int DABDemodWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
DABDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}
@@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// 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_DABDEMOD_WEBAPIADAPTER_H
#define INCLUDE_DABDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "dabdemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class DABDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
DABDemodWebAPIAdapter();
virtual ~DABDemodWebAPIAdapter();
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:
DABDemodSettings m_settings;
};
#endif // INCLUDE_DABDEMOD_WEBAPIADAPTER_H
+80
View File
@@ -0,0 +1,80 @@
<h1>DAB demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate DAB and DAB+ radio.
The DAB demodulator uses a sample rate of 2.048MHz.
<h2>Interface</h2>
![DAB Demodulator plugin GUI](../../../doc/img/DABDemod_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.
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Audio mute</h3>
Left click on this button to toggle audio mute for this channel.
If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details.
<h3>4: RF level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>5: Audio volume</h3>
This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. A value of 5.0 corresponds to a gain of 0dB.
<h3>8: Channel</h3>
Displays a list of DAB Band III channels and frequencies. Selecting an item will set the Device center frequency accordingly.
If the center frequency is set manually, this box will be updated to reflect the corresponding channel, or left blank if there is not a channel that corresponds to the current centre frequency.
<h3>5: RF Bandwidth</h3>
This specifies the bandwidth of a filter that is applied to the input signal before decimation or interpolation to limit the received signal's bandwidth. This should typically be 1.537 MHz.
<h3>6: Find</h3>
Enter a regular expression used to filter the program table.
<h3>7: Clear programs</h3>
Clear all programs in the table.
<h3>Program Table</h3>
The program table shows programs that have been detected within a tuned ensemble. Double clicking on a program will cause
the demodulator to set the corresponding center frequency and then to play audio for the program.
<h3>Current Program</h3>
The current program area display information about the currently playing program, including:
* Program name.
* Ensemble name.
* Program type (E.g. News / Pop).
* Language.
* Audio (DAB or DAB+).
* Bitrate in kbps.
* Audio sample rate (in kSa/s). If this does not match the sample rate of the selected audio device, it will be resampled to match.
* Data broadcast with the program (E.g. song name).
<h3>Statistics</h3>
The statitics areas displays statistics generated by the demodulator that may give an indiciation of the quality of the received signal.
If you are hearing dropouts in audio, try adjusting your antenna in order to improve the reported SNR.
<h2>Attribution</h2>
The DAB demodulator used DAB library by Jvan Katwijk.
@@ -25,6 +25,7 @@
const QStringList DemodAnalyzerSettings::m_channelTypes = {
QStringLiteral("AMDemod"),
QStringLiteral("AMMod"),
QStringLiteral("DABDemod"),
QStringLiteral("DSDDemod"),
QStringLiteral("NFMDemod"),
QStringLiteral("NFMMod"),
@@ -39,6 +40,7 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = {
const QStringList DemodAnalyzerSettings::m_channelURIs = {
QStringLiteral("sdrangel.channel.amdemod"),
QStringLiteral("sdrangel.channeltx.modam"),
QStringLiteral("sdrangel.channel.dabdemod"),
QStringLiteral("sdrangel.channel.dsddemod"),
QStringLiteral("sdrangel.channel.nfmdemod"),
QStringLiteral("sdrangel.channeltx.modnfm"),