Multiple audio support: NFM demodulator

This commit is contained in:
f4exb 2018-03-27 13:18:00 +02:00
parent 4d324875b9
commit 275144ae97
16 changed files with 164 additions and 58 deletions

View File

@ -59,6 +59,7 @@ QByteArray AMDemodSettings::serialize() const
s.writeBool(8, m_bandpassEnable);
s.writeString(9, m_title);
s.writeString(11, m_audioDeviceName);
return s.final();
}

View File

@ -80,7 +80,8 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) :
m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution
m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 0.5ms test period, 300ms average span, 48kS/s SR, 100ms attack, no decay
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_audioNetSink = new AudioNetSink(0); // parent thread allocated dynamically - no RTP
m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort);
@ -381,6 +382,20 @@ bool NFMDemod::handleMessage(const Message& cmd)
m_audioNetSink->moveToThread(const_cast<QThread*>(thread)); // use the thread for udp sinks
return true;
}
else if (DSPConfigureAudio::match(cmd))
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
qDebug() << "NFMDemod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
return true;
}
else if (DSPSignalNotification::match(cmd))
{
return true;
@ -391,6 +406,25 @@ bool NFMDemod::handleMessage(const Message& cmd)
}
}
void NFMDemod::applyAudioSampleRate(int sampleRate)
{
qDebug("NFMDemod::applyAudioSampleRate: %d", sampleRate);
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
sampleRate, m_settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
m_lowpass.create(301, sampleRate, 250.0);
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
m_settingsMutex.unlock();
m_audioSampleRate = sampleRate;
}
void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
{
qDebug() << "NFMDemod::applyChannelSettings:"
@ -406,9 +440,9 @@ void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse
if ((inputSampleRate != m_inputSampleRate) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2);
m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate;
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
m_settingsMutex.unlock();
}
@ -433,15 +467,15 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
<< " m_copyAudioToUDP: " << settings.m_copyAudioToUDP
<< " m_udpAddress: " << settings.m_udpAddress
<< " m_udpPort: " << settings.m_udpPort
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) ||
(settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force)
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
m_settingsMutex.unlock();
}
@ -451,12 +485,10 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
m_phaseDiscri.setFMScaling((8.0f*settings.m_rfBandwidth) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
}
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) ||
(settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force)
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
{
m_settingsMutex.lock();
m_lowpass.create(301, settings.m_audioSampleRate, 250.0);
m_bandpass.create(301, settings.m_audioSampleRate, 300.0, settings.m_afBandwidth);
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth);
m_settingsMutex.unlock();
}
@ -495,6 +527,19 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
setSelectedCtcssIndex(settings.m_ctcssIndex);
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);
//qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_audioSampleRate != audioSampleRate) {
applyAudioSampleRate(audioSampleRate);
}
}
m_settings = settings;
}
@ -514,7 +559,7 @@ bool NFMDemod::deserialize(const QByteArray& data)
}
NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create(
48000, m_settings.m_inputFrequencyOffset);
m_audioSampleRate, m_settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(m_settings, true);
@ -548,9 +593,6 @@ int NFMDemod::webapiSettingsPutPatch(
if (channelSettingsKeys.contains("audioMute")) {
settings.m_audioMute = response.getNfmDemodSettings()->getAudioMute() != 0;
}
if (channelSettingsKeys.contains("audioSampleRate")) {
settings.m_audioSampleRate = response.getNfmDemodSettings()->getAudioSampleRate();
}
if (channelSettingsKeys.contains("copyAudioToUDP")) {
settings.m_copyAudioToUDP = response.getNfmDemodSettings()->getCopyAudioToUdp() != 0;
}
@ -631,7 +673,6 @@ void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
{
response.getNfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth);
response.getNfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getNfmDemodSettings()->setAudioSampleRate(settings.m_audioSampleRate);
response.getNfmDemodSettings()->setCopyAudioToUdp(settings.m_copyAudioToUDP ? 1 : 0);
response.getNfmDemodSettings()->setCtcssIndex(settings.m_ctcssIndex);
response.getNfmDemodSettings()->setCtcssOn(settings.m_ctcssOn ? 1 : 0);
@ -667,4 +708,6 @@ void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
response.getNfmDemodReport()->setCtcssTone(m_settings.m_ctcssOn ? (m_ctcssIndex ? 0 : m_ctcssDetector.getToneSet()[m_ctcssIndex-1]) : 0);
response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0);
response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate);
response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate);
}

View File

@ -179,6 +179,7 @@ private:
int m_inputSampleRate;
int m_inputFrequencyOffset;
NFMDemodSettings m_settings;
uint32_t m_audioSampleRate;
bool m_running;
NCO m_nco;
@ -221,6 +222,7 @@ private:
// void apply(bool force = false);
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const NFMDemodSettings& settings, bool force = false);
void applyAudioSampleRate(int sampleRate);
void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
};

View File

@ -11,6 +11,8 @@
#include "util/simpleserializer.h"
#include "util/db.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "dsp/dspengine.h"
#include "mainwindow.h"
#include "nfmdemod.h"
@ -265,6 +267,9 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect()));
blockApplySettings(true);
ui->rfBW->clear();
@ -427,6 +432,19 @@ void NFMDemodGUI::blockApplySettings(bool block)
m_doApplySettings = !block;
}
void NFMDemodGUI::audioSelect()
{
qDebug("NFMDemodGUI::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 NFMDemodGUI::tick()
{
double magsqAvg, magsqPeak;

View File

@ -82,6 +82,7 @@ private slots:
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void audioSelect();
void tick();
};

View File

@ -49,12 +49,12 @@ void NFMDemodSettings::resetToDefaults()
m_ctcssOn = false;
m_audioMute = false;
m_ctcssIndex = 0;
m_audioSampleRate = DSPEngine::instance()->getDefaultAudioSampleRate();
m_copyAudioToUDP = false;
m_udpAddress = "127.0.0.1";
m_udpPort = 9998;
m_rgbColor = QColor(255, 0, 0).rgb();
m_title = "NFM Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
}
QByteArray NFMDemodSettings::serialize() const
@ -77,6 +77,7 @@ QByteArray NFMDemodSettings::serialize() const
}
s.writeString(14, m_title);
s.writeString(15, m_audioDeviceName);
return s.final();
}
@ -120,6 +121,7 @@ bool NFMDemodSettings::deserialize(const QByteArray& data)
d.readS32(11, &m_squelchGate, 5);
d.readBool(12, &m_deltaSquelch, false);
d.readString(14, &m_title, "NFM Demodulator");
d.readString(15, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
return true;
}

View File

@ -27,7 +27,7 @@ struct NFMDemodSettings
static const int m_rfBW[];
static const int m_fmDev[];
int64_t m_inputFrequencyOffset;
int32_t m_inputFrequencyOffset;
Real m_rfBandwidth;
Real m_afBandwidth;
int m_fmDeviation;
@ -38,12 +38,12 @@ struct NFMDemodSettings
bool m_ctcssOn;
bool m_audioMute;
int m_ctcssIndex;
uint32_t m_audioSampleRate;
bool m_copyAudioToUDP;
QString m_udpAddress;
uint16_t m_udpPort;
quint32 m_rgbColor;
QString m_title;
QString m_audioDeviceName;
Serializable *m_channelMarker;

View File

@ -7,7 +7,7 @@
const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
QString("NFM Demodulator"),
QString("3.12.0"),
QString("3.14.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,

View File

@ -1484,6 +1484,12 @@ margin-bottom: 20px;
"squelch" : {
"type" : "integer",
"description" : "squelch status (1 if open else 0)"
},
"audioSampleRate" : {
"type" : "integer"
},
"channelSampleRate" : {
"type" : "integer"
}
},
"description" : "NFMDemod"
@ -1533,9 +1539,6 @@ margin-bottom: 20px;
"ctcssIndex" : {
"type" : "integer"
},
"audioSampleRate" : {
"type" : "integer"
},
"copyAudioToUDP" : {
"type" : "integer"
},
@ -18060,7 +18063,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2018-03-27T09:54:41.046+02:00
Generated 2018-03-27T12:01:24.051+02:00
</div>
</div>
</div>

View File

@ -33,8 +33,6 @@ NFMDemodSettings:
type: integer
ctcssIndex:
type: integer
audioSampleRate:
type: integer
copyAudioToUDP:
type: integer
udpAddress:
@ -60,3 +58,8 @@ NFMDemodReport:
squelch:
description: squelch status (1 if open else 0)
type: integer
audioSampleRate:
type: integer
channelSampleRate:
type: integer

View File

@ -33,8 +33,6 @@ NFMDemodSettings:
type: integer
ctcssIndex:
type: integer
audioSampleRate:
type: integer
copyAudioToUDP:
type: integer
udpAddress:
@ -60,3 +58,8 @@ NFMDemodReport:
squelch:
description: squelch status (1 if open else 0)
type: integer
audioSampleRate:
type: integer
channelSampleRate:
type: integer

View File

@ -1484,6 +1484,12 @@ margin-bottom: 20px;
"squelch" : {
"type" : "integer",
"description" : "squelch status (1 if open else 0)"
},
"audioSampleRate" : {
"type" : "integer"
},
"channelSampleRate" : {
"type" : "integer"
}
},
"description" : "NFMDemod"
@ -1533,9 +1539,6 @@ margin-bottom: 20px;
"ctcssIndex" : {
"type" : "integer"
},
"audioSampleRate" : {
"type" : "integer"
},
"copyAudioToUDP" : {
"type" : "integer"
},
@ -18060,7 +18063,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2018-03-27T09:54:41.046+02:00
Generated 2018-03-27T12:01:24.051+02:00
</div>
</div>
</div>

View File

@ -34,6 +34,10 @@ SWGNFMDemodReport::SWGNFMDemodReport() {
m_ctcss_tone_isSet = false;
squelch = 0;
m_squelch_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
SWGNFMDemodReport::~SWGNFMDemodReport() {
@ -48,6 +52,10 @@ SWGNFMDemodReport::init() {
m_ctcss_tone_isSet = false;
squelch = 0;
m_squelch_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
void
@ -55,6 +63,8 @@ SWGNFMDemodReport::cleanup() {
}
SWGNFMDemodReport*
@ -74,6 +84,10 @@ SWGNFMDemodReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&squelch, pJson["squelch"], "qint32", "");
::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", "");
::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", "");
}
QString
@ -99,6 +113,12 @@ SWGNFMDemodReport::asJsonObject() {
if(m_squelch_isSet){
obj->insert("squelch", QJsonValue(squelch));
}
if(m_audio_sample_rate_isSet){
obj->insert("audioSampleRate", QJsonValue(audio_sample_rate));
}
if(m_channel_sample_rate_isSet){
obj->insert("channelSampleRate", QJsonValue(channel_sample_rate));
}
return obj;
}
@ -133,6 +153,26 @@ SWGNFMDemodReport::setSquelch(qint32 squelch) {
this->m_squelch_isSet = true;
}
qint32
SWGNFMDemodReport::getAudioSampleRate() {
return audio_sample_rate;
}
void
SWGNFMDemodReport::setAudioSampleRate(qint32 audio_sample_rate) {
this->audio_sample_rate = audio_sample_rate;
this->m_audio_sample_rate_isSet = true;
}
qint32
SWGNFMDemodReport::getChannelSampleRate() {
return channel_sample_rate;
}
void
SWGNFMDemodReport::setChannelSampleRate(qint32 channel_sample_rate) {
this->channel_sample_rate = channel_sample_rate;
this->m_channel_sample_rate_isSet = true;
}
bool
SWGNFMDemodReport::isSet(){
@ -141,6 +181,8 @@ SWGNFMDemodReport::isSet(){
if(m_channel_power_db_isSet){ isObjectUpdated = true; break;}
if(m_ctcss_tone_isSet){ isObjectUpdated = true; break;}
if(m_squelch_isSet){ isObjectUpdated = true; break;}
if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;}
if(m_channel_sample_rate_isSet){ isObjectUpdated = true; break;}
}while(false);
return isObjectUpdated;
}

View File

@ -50,6 +50,12 @@ public:
qint32 getSquelch();
void setSquelch(qint32 squelch);
qint32 getAudioSampleRate();
void setAudioSampleRate(qint32 audio_sample_rate);
qint32 getChannelSampleRate();
void setChannelSampleRate(qint32 channel_sample_rate);
virtual bool isSet() override;
@ -63,6 +69,12 @@ private:
qint32 squelch;
bool m_squelch_isSet;
qint32 audio_sample_rate;
bool m_audio_sample_rate_isSet;
qint32 channel_sample_rate;
bool m_channel_sample_rate_isSet;
};
}

View File

@ -50,8 +50,6 @@ SWGNFMDemodSettings::SWGNFMDemodSettings() {
m_audio_mute_isSet = false;
ctcss_index = 0;
m_ctcss_index_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
copy_audio_to_udp = 0;
m_copy_audio_to_udp_isSet = false;
udp_address = nullptr;
@ -92,8 +90,6 @@ SWGNFMDemodSettings::init() {
m_audio_mute_isSet = false;
ctcss_index = 0;
m_ctcss_index_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
copy_audio_to_udp = 0;
m_copy_audio_to_udp_isSet = false;
udp_address = new QString("");
@ -120,7 +116,6 @@ SWGNFMDemodSettings::cleanup() {
if(udp_address != nullptr) {
delete udp_address;
}
@ -164,8 +159,6 @@ SWGNFMDemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&ctcss_index, pJson["ctcssIndex"], "qint32", "");
::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", "");
::SWGSDRangel::setValue(&copy_audio_to_udp, pJson["copyAudioToUDP"], "qint32", "");
::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString");
@ -225,9 +218,6 @@ SWGNFMDemodSettings::asJsonObject() {
if(m_ctcss_index_isSet){
obj->insert("ctcssIndex", QJsonValue(ctcss_index));
}
if(m_audio_sample_rate_isSet){
obj->insert("audioSampleRate", QJsonValue(audio_sample_rate));
}
if(m_copy_audio_to_udp_isSet){
obj->insert("copyAudioToUDP", QJsonValue(copy_audio_to_udp));
}
@ -357,16 +347,6 @@ SWGNFMDemodSettings::setCtcssIndex(qint32 ctcss_index) {
this->m_ctcss_index_isSet = true;
}
qint32
SWGNFMDemodSettings::getAudioSampleRate() {
return audio_sample_rate;
}
void
SWGNFMDemodSettings::setAudioSampleRate(qint32 audio_sample_rate) {
this->audio_sample_rate = audio_sample_rate;
this->m_audio_sample_rate_isSet = true;
}
qint32
SWGNFMDemodSettings::getCopyAudioToUdp() {
return copy_audio_to_udp;
@ -433,7 +413,6 @@ SWGNFMDemodSettings::isSet(){
if(m_ctcss_on_isSet){ isObjectUpdated = true; break;}
if(m_audio_mute_isSet){ isObjectUpdated = true; break;}
if(m_ctcss_index_isSet){ isObjectUpdated = true; break;}
if(m_audio_sample_rate_isSet){ isObjectUpdated = true; break;}
if(m_copy_audio_to_udp_isSet){ isObjectUpdated = true; break;}
if(udp_address != nullptr && *udp_address != QString("")){ isObjectUpdated = true; break;}
if(m_udp_port_isSet){ isObjectUpdated = true; break;}

View File

@ -75,9 +75,6 @@ public:
qint32 getCtcssIndex();
void setCtcssIndex(qint32 ctcss_index);
qint32 getAudioSampleRate();
void setAudioSampleRate(qint32 audio_sample_rate);
qint32 getCopyAudioToUdp();
void setCopyAudioToUdp(qint32 copy_audio_to_udp);
@ -130,9 +127,6 @@ private:
qint32 ctcss_index;
bool m_ctcss_index_isSet;
qint32 audio_sample_rate;
bool m_audio_sample_rate_isSet;
qint32 copy_audio_to_udp;
bool m_copy_audio_to_udp_isSet;