1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 09:18:54 -05:00

Remote output/source: use queue langth for rate control and derive rate from Tx side. Other fixes

This commit is contained in:
f4exb 2021-12-14 07:56:02 +01:00
parent 1168eefcc9
commit 54866a1a1e
17 changed files with 503 additions and 123 deletions

View File

@ -28,6 +28,8 @@
#include "SWGRemoteSourceReport.h" #include "SWGRemoteSourceReport.h"
#include "dsp/devicesamplesink.h" #include "dsp/devicesamplesink.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h" #include "device/deviceapi.h"
#include "feature/feature.h" #include "feature/feature.h"
#include "settings/serializable.h" #include "settings/serializable.h"
@ -40,13 +42,17 @@
MESSAGE_CLASS_DEFINITION(RemoteSource::MsgConfigureRemoteSource, Message) MESSAGE_CLASS_DEFINITION(RemoteSource::MsgConfigureRemoteSource, Message)
MESSAGE_CLASS_DEFINITION(RemoteSource::MsgQueryStreamData, Message) MESSAGE_CLASS_DEFINITION(RemoteSource::MsgQueryStreamData, Message)
MESSAGE_CLASS_DEFINITION(RemoteSource::MsgReportStreamData, Message) MESSAGE_CLASS_DEFINITION(RemoteSource::MsgReportStreamData, Message)
MESSAGE_CLASS_DEFINITION(RemoteSource::MsgBasebandSampleRateNotification, Message)
const char* const RemoteSource::m_channelIdURI = "sdrangel.channeltx.remotesource"; const char* const RemoteSource::m_channelIdURI = "sdrangel.channeltx.remotesource";
const char* const RemoteSource::m_channelId ="RemoteSource"; const char* const RemoteSource::m_channelId ="RemoteSource";
RemoteSource::RemoteSource(DeviceAPI *deviceAPI) : RemoteSource::RemoteSource(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource), ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
m_deviceAPI(deviceAPI) m_deviceAPI(deviceAPI),
m_centerFrequency(0),
m_frequencyOffset(0),
m_basebandSampleRate(48000)
{ {
setObjectName(m_channelId); setObjectName(m_channelId);
@ -96,7 +102,27 @@ void RemoteSource::pull(SampleVector::iterator& begin, unsigned int nbSamples)
bool RemoteSource::handleMessage(const Message& cmd) bool RemoteSource::handleMessage(const Message& cmd)
{ {
if (MsgConfigureRemoteSource::match(cmd)) if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "RemoteSource::handleMessage: DSPSignalNotification:"
<< " inputSampleRate: " << notif.getSampleRate()
<< " centerFrequency: " << notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
calculateFrequencyOffset(m_settings.m_log2Interp, m_settings.m_filterChainHash); // This is when device sample rate changes
m_centerFrequency = notif.getCenterFrequency();
if (m_guiMessageQueue)
{
MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate());
m_guiMessageQueue->push(msg);
}
return true;
}
else if (MsgConfigureRemoteSource::match(cmd))
{ {
MsgConfigureRemoteSource& cfg = (MsgConfigureRemoteSource&) cmd; MsgConfigureRemoteSource& cfg = (MsgConfigureRemoteSource&) cmd;
qDebug() << "MsgConfigureRemoteSource::handleMessage: MsgConfigureRemoteSource"; qDebug() << "MsgConfigureRemoteSource::handleMessage: MsgConfigureRemoteSource";
@ -158,6 +184,8 @@ bool RemoteSource::deserialize(const QByteArray& data)
void RemoteSource::applySettings(const RemoteSourceSettings& settings, bool force) void RemoteSource::applySettings(const RemoteSourceSettings& settings, bool force)
{ {
qDebug() << "RemoteSource::applySettings:" qDebug() << "RemoteSource::applySettings:"
<< "m_log2Interp:" << settings.m_log2Interp
<< "m_filterChainHash:" << settings.m_filterChainHash
<< "m_dataAddress:" << settings.m_dataAddress << "m_dataAddress:" << settings.m_dataAddress
<< "m_dataPort:" << settings.m_dataPort << "m_dataPort:" << settings.m_dataPort
<< "m_rgbColor:" << settings.m_rgbColor << "m_rgbColor:" << settings.m_rgbColor
@ -171,14 +199,24 @@ void RemoteSource::applySettings(const RemoteSourceSettings& settings, bool forc
QList<QString> reverseAPIKeys; QList<QString> reverseAPIKeys;
if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) {
reverseAPIKeys.append("log2Interp");
}
if ((m_settings.m_filterChainHash != settings.m_filterChainHash) || force) {
reverseAPIKeys.append("filterChainHash");
}
if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) {
reverseAPIKeys.append("dataAddress"); reverseAPIKeys.append("dataAddress");
} }
if ((m_settings.m_dataPort != settings.m_dataPort) || force) { if ((m_settings.m_dataPort != settings.m_dataPort) || force) {
reverseAPIKeys.append("dataPort"); reverseAPIKeys.append("dataPort");
} }
if ((m_settings.m_log2Interp != settings.m_log2Interp)
|| (m_settings.m_filterChainHash != settings.m_filterChainHash) || force) {
calculateFrequencyOffset(settings.m_log2Interp, settings.m_filterChainHash);
}
if (m_settings.m_streamIndex != settings.m_streamIndex) if (m_settings.m_streamIndex != settings.m_streamIndex)
{ {
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
@ -214,6 +252,23 @@ void RemoteSource::applySettings(const RemoteSourceSettings& settings, bool forc
m_settings = settings; m_settings = settings;
} }
void RemoteSource::validateFilterChainHash(RemoteSourceSettings& settings)
{
unsigned int s = 1;
for (unsigned int i = 0; i < settings.m_log2Interp; i++) {
s *= 3;
}
settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash;
}
void RemoteSource::calculateFrequencyOffset(uint32_t log2Interp, uint32_t filterChainHash)
{
double shiftFactor = HBFilterChainConverter::getShiftFactor(log2Interp, filterChainHash);
m_frequencyOffset = m_basebandSampleRate * shiftFactor;
}
int RemoteSource::webapiSettingsGet( int RemoteSource::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response, SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage) QString& errorMessage)
@ -274,6 +329,12 @@ void RemoteSource::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("title")) { if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getRemoteSourceSettings()->getTitle(); settings.m_title = *response.getRemoteSourceSettings()->getTitle();
} }
if (channelSettingsKeys.contains("log2Interp")) {
settings.m_log2Interp = response.getRemoteSourceSettings()->getLog2Interp();
}
if (channelSettingsKeys.contains("filterChainHash")) {
settings.m_filterChainHash = response.getRemoteSourceSettings()->getFilterChainHash();
}
if (channelSettingsKeys.contains("streamIndex")) { if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getRemoteSourceSettings()->getStreamIndex(); settings.m_streamIndex = response.getRemoteSourceSettings()->getStreamIndex();
} }
@ -325,6 +386,8 @@ void RemoteSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings&
response.getRemoteSourceSettings()->setTitle(new QString(settings.m_title)); response.getRemoteSourceSettings()->setTitle(new QString(settings.m_title));
} }
response.getRemoteSourceSettings()->setLog2Interp(settings.m_log2Interp);
response.getRemoteSourceSettings()->setFilterChainHash(settings.m_filterChainHash);
response.getRemoteSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); response.getRemoteSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getRemoteSourceSettings()->getReverseApiAddress()) { if (response.getRemoteSourceSettings()->getReverseApiAddress()) {
@ -367,8 +430,9 @@ void RemoteSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& resp
response.getRemoteSourceReport()->setUncorrectableErrorsCount(m_basebandSource->getNbUncorrectableErrors()); response.getRemoteSourceReport()->setUncorrectableErrorsCount(m_basebandSource->getNbUncorrectableErrors());
response.getRemoteSourceReport()->setNbOriginalBlocks(currentMeta.m_nbOriginalBlocks); response.getRemoteSourceReport()->setNbOriginalBlocks(currentMeta.m_nbOriginalBlocks);
response.getRemoteSourceReport()->setNbFecBlocks(currentMeta.m_nbFECBlocks); response.getRemoteSourceReport()->setNbFecBlocks(currentMeta.m_nbFECBlocks);
response.getRemoteSourceReport()->setCenterFreq(currentMeta.m_centerFrequency); response.getRemoteSourceReport()->setCenterFreq(m_frequencyOffset);
response.getRemoteSourceReport()->setSampleRate(currentMeta.m_sampleRate); double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Interp);
response.getRemoteSourceReport()->setSampleRate(channelSampleRate);
response.getRemoteSourceReport()->setDeviceCenterFreq(m_deviceAPI->getSampleSink()->getCenterFrequency()); response.getRemoteSourceReport()->setDeviceCenterFreq(m_deviceAPI->getSampleSink()->getCenterFrequency());
response.getRemoteSourceReport()->setDeviceSampleRate(m_deviceAPI->getSampleSink()->getSampleRate()); response.getRemoteSourceReport()->setDeviceSampleRate(m_deviceAPI->getSampleSink()->getSampleRate());
} }
@ -445,6 +509,12 @@ void RemoteSource::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("rgbColor") || force) { if (channelSettingsKeys.contains("rgbColor") || force) {
swgRemoteSourceSettings->setRgbColor(settings.m_rgbColor); swgRemoteSourceSettings->setRgbColor(settings.m_rgbColor);
} }
if (channelSettingsKeys.contains("log2Interp") || force) {
swgRemoteSourceSettings->setLog2Interp(settings.m_log2Interp);
}
if (channelSettingsKeys.contains("filterChainHash") || force) {
swgRemoteSourceSettings->setFilterChainHash(settings.m_filterChainHash);
}
if (channelSettingsKeys.contains("title") || force) { if (channelSettingsKeys.contains("title") || force) {
swgRemoteSourceSettings->setTitle(new QString(settings.m_title)); swgRemoteSourceSettings->setTitle(new QString(settings.m_title));
} }

View File

@ -154,6 +154,27 @@ public:
{ } { }
}; };
class MsgBasebandSampleRateNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgBasebandSampleRateNotification* create(int sampleRate) {
return new MsgBasebandSampleRateNotification(sampleRate);
}
int getBasebandSampleRate() const { return m_sampleRate; }
private:
MsgBasebandSampleRateNotification(int sampleRate) :
Message(),
m_sampleRate(sampleRate)
{ }
int m_sampleRate;
};
RemoteSource(DeviceAPI *deviceAPI); RemoteSource(DeviceAPI *deviceAPI);
virtual ~RemoteSource(); virtual ~RemoteSource();
@ -218,7 +239,13 @@ private:
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest; QNetworkRequest m_networkRequest;
uint64_t m_centerFrequency;
int64_t m_frequencyOffset;
uint32_t m_basebandSampleRate;
void applySettings(const RemoteSourceSettings& settings, bool force = false); void applySettings(const RemoteSourceSettings& settings, bool force = false);
static void validateFilterChainHash(RemoteSourceSettings& settings);
void calculateFrequencyOffset(uint32_t log2Interp, uint32_t filterChainHash);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const RemoteSourceSettings& settings, bool force); void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const RemoteSourceSettings& settings, bool force);
void sendChannelSettings( void sendChannelSettings(

View File

@ -164,6 +164,8 @@ bool RemoteSourceBaseband::handleMessage(const Message& cmd)
void RemoteSourceBaseband::applySettings(const RemoteSourceSettings& settings, bool force) void RemoteSourceBaseband::applySettings(const RemoteSourceSettings& settings, bool force)
{ {
qDebug() << "RemoteSourceBaseband::applySettings:" qDebug() << "RemoteSourceBaseband::applySettings:"
<< "m_log2Interp:" << settings.m_log2Interp
<< "m_filterChainHash:" << settings.m_filterChainHash
<< "m_dataAddress:" << settings.m_dataAddress << "m_dataAddress:" << settings.m_dataAddress
<< "m_dataPort:" << settings.m_dataPort << "m_dataPort:" << settings.m_dataPort
<< "force:" << force; << "force:" << force;
@ -173,6 +175,12 @@ void RemoteSourceBaseband::applySettings(const RemoteSourceSettings& settings, b
m_source.dataBind(settings.m_dataAddress, settings.m_dataPort); m_source.dataBind(settings.m_dataAddress, settings.m_dataPort);
} }
if ((m_settings.m_filterChainHash != settings.m_filterChainHash)
|| (m_settings.m_log2Interp != settings.m_log2Interp) || force)
{
m_channelizer->setInterpolation(settings.m_log2Interp, settings.m_filterChainHash);
}
m_settings = settings; m_settings = settings;
} }

View File

@ -19,6 +19,7 @@
#include "device/deviceapi.h" #include "device/deviceapi.h"
#include "device/deviceuiset.h" #include "device/deviceuiset.h"
#include "dsp/hbfilterchainconverter.h"
#include "gui/basicchannelsettingsdialog.h" #include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h" #include "gui/devicestreamselectiondialog.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -65,7 +66,14 @@ bool RemoteSourceGUI::deserialize(const QByteArray& data)
bool RemoteSourceGUI::handleMessage(const Message& message) bool RemoteSourceGUI::handleMessage(const Message& message)
{ {
if (RemoteSource::MsgConfigureRemoteSource::match(message)) if (RemoteSource::MsgBasebandSampleRateNotification::match(message))
{
RemoteSource::MsgBasebandSampleRateNotification& notif = (RemoteSource::MsgBasebandSampleRateNotification&) message;
m_basebandSampleRate = notif.getBasebandSampleRate();
displayRateAndShift();
return true;
}
else if (RemoteSource::MsgConfigureRemoteSource::match(message))
{ {
const RemoteSource::MsgConfigureRemoteSource& cfg = (RemoteSource::MsgConfigureRemoteSource&) message; const RemoteSource::MsgConfigureRemoteSource& cfg = (RemoteSource::MsgConfigureRemoteSource&) message;
m_settings = cfg.getSettings(); m_settings = cfg.getSettings();
@ -149,6 +157,8 @@ RemoteSourceGUI::RemoteSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet,
m_pluginAPI(pluginAPI), m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet), m_deviceUISet(deviceUISet),
m_remoteSampleRate(48000), m_remoteSampleRate(48000),
m_basebandSampleRate(48000),
m_shiftFrequencyFactor(0.0),
m_countUnrecoverable(0), m_countUnrecoverable(0),
m_countRecovered(0), m_countRecovered(0),
m_lastCountUnrecoverable(0), m_lastCountUnrecoverable(0),
@ -189,6 +199,8 @@ RemoteSourceGUI::RemoteSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet,
m_time.start(); m_time.start();
displaySettings(); displaySettings();
displayPosition();
displayRateAndShift();
applySettings(true); applySettings(true);
} }
@ -233,6 +245,25 @@ void RemoteSourceGUI::displaySettings()
blockApplySettings(false); blockApplySettings(false);
} }
void RemoteSourceGUI::displayRateAndShift()
{
int shift = m_shiftFrequencyFactor * m_basebandSampleRate;
double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Interp);
QLocale loc;
ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
m_channelMarker.setCenterFrequency(shift);
m_channelMarker.setBandwidth(channelSampleRate);
}
void RemoteSourceGUI::displayPosition()
{
ui->filterChainIndex->setText(tr("%1").arg(m_settings.m_filterChainHash));
QString s;
HBFilterChainConverter::convertToString(m_settings.m_log2Interp, m_settings.m_filterChainHash, s);
ui->filterChainText->setText(s);
}
void RemoteSourceGUI::displayStreamIndex() void RemoteSourceGUI::displayStreamIndex()
{ {
if (m_deviceUISet->m_deviceMIMOEngine) { if (m_deviceUISet->m_deviceMIMOEngine) {
@ -319,6 +350,18 @@ void RemoteSourceGUI::onMenuDialogCalled(const QPoint &p)
resetContextMenuType(); resetContextMenuType();
} }
void RemoteSourceGUI::on_interpolationFactor_currentIndexChanged(int index)
{
m_settings.m_log2Interp = index;
applyInterpolation();
}
void RemoteSourceGUI::on_position_valueChanged(int value)
{
m_settings.m_filterChainHash = value;
applyPosition();
}
void RemoteSourceGUI::on_dataAddress_returnPressed() void RemoteSourceGUI::on_dataAddress_returnPressed()
{ {
m_settings.m_dataAddress = ui->dataAddress->text(); m_settings.m_dataAddress = ui->dataAddress->text();
@ -364,6 +407,31 @@ void RemoteSourceGUI::on_eventCountsReset_clicked(bool checked)
displayEventTimer(); displayEventTimer();
} }
void RemoteSourceGUI::applyInterpolation()
{
uint32_t maxHash = 1;
for (uint32_t i = 0; i < m_settings.m_log2Interp; i++) {
maxHash *= 3;
}
ui->position->setMaximum(maxHash-1);
ui->position->setValue(m_settings.m_filterChainHash);
m_settings.m_filterChainHash = ui->position->value();
applyPosition();
}
void RemoteSourceGUI::applyPosition()
{
ui->filterChainIndex->setText(tr("%1").arg(m_settings.m_filterChainHash));
QString s;
m_shiftFrequencyFactor = HBFilterChainConverter::convertToString(m_settings.m_log2Interp, m_settings.m_filterChainHash, s);
ui->filterChainText->setText(s);
displayRateAndShift();
applySettings();
}
void RemoteSourceGUI::displayEventCounts() void RemoteSourceGUI::displayEventCounts()
{ {
QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0')); QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0'));

View File

@ -57,7 +57,9 @@ private:
ChannelMarker m_channelMarker; ChannelMarker m_channelMarker;
RemoteSourceSettings m_settings; RemoteSourceSettings m_settings;
int m_remoteSampleRate; int m_remoteSampleRate;
int m_basebandSampleRate;
bool m_doApplySettings; bool m_doApplySettings;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
RemoteSource* m_remoteSrc; RemoteSource* m_remoteSrc;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
@ -78,6 +80,8 @@ private:
void blockApplySettings(bool block); void blockApplySettings(bool block);
void applySettings(bool force = false); void applySettings(bool force = false);
void displaySettings(); void displaySettings();
void displayRateAndShift();
void displayPosition();
void displayStreamIndex(); void displayStreamIndex();
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
@ -88,8 +92,13 @@ private:
void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventStatus(int recoverableCount, int unrecoverableCount);
void displayEventTimer(); void displayEventTimer();
void applyInterpolation();
void applyPosition();
private slots: private slots:
void handleSourceMessages(); void handleSourceMessages();
void on_interpolationFactor_currentIndexChanged(int index);
void on_position_valueChanged(int value);
void on_dataAddress_returnPressed(); void on_dataAddress_returnPressed();
void on_dataPort_returnPressed(); void on_dataPort_returnPressed();
void on_dataApplyButton_clicked(bool checked); void on_dataApplyButton_clicked(bool checked);

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>320</width> <width>320</width>
<height>140</height> <height>221</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -43,7 +43,7 @@
<x>10</x> <x>10</x>
<y>10</y> <y>10</y>
<width>301</width> <width>301</width>
<height>121</height> <height>191</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -156,6 +156,188 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QVBoxLayout" name="decimationLayer">
<property name="spacing">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="decimationStageLayer">
<item>
<widget class="QLabel" name="interpolationLabel">
<property name="text">
<string>Int</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="interpolationFactor">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Decimation factor</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="channelRateText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Effective channel rate (kS/s)</string>
</property>
<property name="text">
<string>0000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain stages left to right (L: low, C: center, H: high) </string>
</property>
<property name="text">
<string>LLLLLL</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="offsetFrequencyText">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Offset frequency with thousands separator (Hz)</string>
</property>
<property name="text">
<string>-9,999,999 Hz</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="decimationShiftLayer">
<property name="rightMargin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="positionLabel">
<property name="text">
<string>Pos</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="position">
<property name="toolTip">
<string>Center frequency position</string>
</property>
<property name="maximum">
<number>2</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainIndex">
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain hash code</string>
</property>
<property name="text">
<string>000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="nominalValuesLayout"> <layout class="QHBoxLayout" name="nominalValuesLayout">
<item> <item>

View File

@ -34,6 +34,8 @@ void RemoteSourceSettings::resetToDefaults()
m_dataPort = 9090; m_dataPort = 9090;
m_rgbColor = QColor(140, 4, 4).rgb(); m_rgbColor = QColor(140, 4, 4).rgb();
m_title = "Remote source"; m_title = "Remote source";
m_log2Interp = 0;
m_filterChainHash = 0;
m_channelMarker = nullptr; m_channelMarker = nullptr;
m_streamIndex = 0; m_streamIndex = 0;
m_useReverseAPI = false; m_useReverseAPI = false;
@ -57,6 +59,8 @@ QByteArray RemoteSourceSettings::serialize() const
s.writeU32(9, m_reverseAPIChannelIndex); s.writeU32(9, m_reverseAPIChannelIndex);
s.writeS32(10, m_streamIndex); s.writeS32(10, m_streamIndex);
s.writeBlob(11, m_rollupState); s.writeBlob(11, m_rollupState);
s.writeU32(12, m_log2Interp);
s.writeU32(13, m_filterChainHash);
return s.final(); return s.final();
} }
@ -103,6 +107,8 @@ bool RemoteSourceSettings::deserialize(const QByteArray& data)
m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp; m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp;
d.readS32(10, &m_streamIndex, 0); d.readS32(10, &m_streamIndex, 0);
d.readBlob(11, &m_rollupState); d.readBlob(11, &m_rollupState);
d.readU32(13, &m_filterChainHash, 0);
d.readS32(14, &m_streamIndex, 0);
return true; return true;
} }

View File

@ -29,6 +29,8 @@ struct RemoteSourceSettings
uint16_t m_dataPort; //!< Listening data port uint16_t m_dataPort; //!< Listening data port
quint32 m_rgbColor; quint32 m_rgbColor;
QString m_title; QString m_title;
uint32_t m_log2Interp;
uint32_t m_filterChainHash;
int m_streamIndex; int m_streamIndex;
bool m_useReverseAPI; bool m_useReverseAPI;
QString m_reverseAPIAddress; QString m_reverseAPIAddress;

View File

@ -46,8 +46,6 @@ MESSAGE_CLASS_DEFINITION(RemoteOutput::MsgReportRemoteData, Message)
MESSAGE_CLASS_DEFINITION(RemoteOutput::MsgReportRemoteFixedData, Message) MESSAGE_CLASS_DEFINITION(RemoteOutput::MsgReportRemoteFixedData, Message)
MESSAGE_CLASS_DEFINITION(RemoteOutput::MsgRequestFixedData, Message) MESSAGE_CLASS_DEFINITION(RemoteOutput::MsgRequestFixedData, Message)
const uint32_t RemoteOutput::NbSamplesForRateCorrection = 5000000;
RemoteOutput::RemoteOutput(DeviceAPI *deviceAPI) : RemoteOutput::RemoteOutput(DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI), m_deviceAPI(deviceAPI),
m_settings(), m_settings(),
@ -59,15 +57,7 @@ RemoteOutput::RemoteOutput(DeviceAPI *deviceAPI) :
m_masterTimer(deviceAPI->getMasterTimer()), m_masterTimer(deviceAPI->getMasterTimer()),
m_tickCount(0), m_tickCount(0),
m_greaterTickCount(0), m_greaterTickCount(0),
m_tickMultiplier(1), m_tickMultiplier(1)
m_lastRemoteSampleCount(0),
m_lastSampleCount(0),
m_lastRemoteTimestampRateCorrection(0),
m_lastTimestampRateCorrection(0),
m_lastQueueLength(-2),
m_nbRemoteSamplesSinceRateCorrection(0),
m_nbSamplesSinceRateCorrection(0),
m_chunkSizeCorrection(0)
{ {
m_deviceAPI->setNbSinkStreams(1); m_deviceAPI->setNbSinkStreams(1);
m_networkManager = new QNetworkAccessManager(); m_networkManager = new QNetworkAccessManager();
@ -102,14 +92,9 @@ bool RemoteOutput::start()
m_remoteOutputWorker->connectTimer(m_masterTimer); m_remoteOutputWorker->connectTimer(m_masterTimer);
startWorker(); startWorker();
// restart auto rate correction
m_lastRemoteTimestampRateCorrection = 0;
m_lastTimestampRateCorrection = 0;
m_lastQueueLength = -2; // set first value out of bounds
m_chunkSizeCorrection = 0;
mutexLocker.unlock(); mutexLocker.unlock();
//applySettings(m_generalSettings, m_settings, true); applySampleRate();
qDebug("RemoteOutput::start: started"); qDebug("RemoteOutput::start: started");
return true; return true;
@ -337,9 +322,9 @@ void RemoteOutput::applySampleRate()
m_remoteOutputWorker->setSamplerate(m_sampleRate); m_remoteOutputWorker->setSamplerate(m_sampleRate);
} }
m_tickMultiplier = (21*NbSamplesForRateCorrection) / (2*m_sampleRate); // two times per sample filling period plus small extension m_tickMultiplier = 480000 / m_sampleRate;
m_tickMultiplier /= 20; // greter tick (one per second) m_tickMultiplier = m_tickMultiplier < 1 ? 1 : m_tickMultiplier > 10 ? 10 : m_tickMultiplier;
m_tickMultiplier = m_tickMultiplier < 1 ? 1 : m_tickMultiplier; // not below 1 second m_greaterTickCount = 0;
DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency); DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
@ -548,7 +533,7 @@ void RemoteOutput::analyzeApiReply(const QJsonObject& jsonObject, const QString&
{ {
MsgReportRemoteData::RemoteData msgRemoteData; MsgReportRemoteData::RemoteData msgRemoteData;
QJsonObject report = jsonObject["RemoteSourceReport"].toObject(); QJsonObject report = jsonObject["RemoteSourceReport"].toObject();
uint64_t centerFrequency = report["deviceCenterFreq"].toInt(); uint64_t centerFrequency = report["deviceCenterFreq"].toInt() + report["centerFreq"].toInt();
if (centerFrequency != m_centerFrequency) if (centerFrequency != m_centerFrequency)
{ {
@ -556,7 +541,7 @@ void RemoteOutput::analyzeApiReply(const QJsonObject& jsonObject, const QString&
applyCenterFrequency(); applyCenterFrequency();
} }
int remoteRate = report["deviceSampleRate"].toInt(); int remoteRate = report["sampleRate"].toInt();
if (remoteRate != m_sampleRate) if (remoteRate != m_sampleRate)
{ {
@ -572,7 +557,6 @@ void RemoteOutput::analyzeApiReply(const QJsonObject& jsonObject, const QString&
msgRemoteData.m_queueSize = queueSize; msgRemoteData.m_queueSize = queueSize;
int queueLength = report["queueLength"].toInt(); int queueLength = report["queueLength"].toInt();
msgRemoteData.m_queueLength = queueLength; msgRemoteData.m_queueLength = queueLength;
int queueLengthPercent = (queueLength*100)/queueSize;
uint64_t remoteTimestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); uint64_t remoteTimestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt();
msgRemoteData.m_timestampUs = remoteTimestampUs; msgRemoteData.m_timestampUs = remoteTimestampUs;
int intRemoteSampleCount = report["samplesCount"].toInt(); int intRemoteSampleCount = report["samplesCount"].toInt();
@ -593,63 +577,12 @@ void RemoteOutput::analyzeApiReply(const QJsonObject& jsonObject, const QString&
return; return;
} }
if (++m_greaterTickCount != m_tickMultiplier) { if (++m_greaterTickCount == m_tickMultiplier)
return;
}
uint32_t remoteSampleCountDelta;
if (remoteSampleCount < m_lastRemoteSampleCount) {
remoteSampleCountDelta = (0xFFFFFFFFU - m_lastRemoteSampleCount) + remoteSampleCount + 1;
} else {
remoteSampleCountDelta = remoteSampleCount - m_lastRemoteSampleCount;
}
uint32_t sampleCountDelta, sampleCount;
uint64_t timestampUs;
sampleCount = m_remoteOutputWorker->getSamplesCount(timestampUs);
if (sampleCount < m_lastSampleCount) {
sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1;
} else {
sampleCountDelta = sampleCount - m_lastSampleCount;
}
// on initial state wait for queue stabilization
if ((m_lastRemoteTimestampRateCorrection == 0) && (queueLength >= m_lastQueueLength-1) && (queueLength <= m_lastQueueLength+1))
{ {
m_lastRemoteTimestampRateCorrection = remoteTimestampUs; queueLengthCompensation(m_sampleRate, queueLength, queueSize);
m_lastTimestampRateCorrection = timestampUs;
m_nbRemoteSamplesSinceRateCorrection = 0;
m_nbSamplesSinceRateCorrection = 0;
}
else
{
m_nbRemoteSamplesSinceRateCorrection += remoteSampleCountDelta;
m_nbSamplesSinceRateCorrection += sampleCountDelta;
qDebug("RemoteOutput::analyzeApiReply: queueLengthPercent: %d m_nbSamplesSinceRateCorrection: %u",
queueLengthPercent,
m_nbRemoteSamplesSinceRateCorrection);
if (m_nbRemoteSamplesSinceRateCorrection > NbSamplesForRateCorrection)
{
sampleRateCorrection(remoteTimestampUs - m_lastRemoteTimestampRateCorrection,
timestampUs - m_lastTimestampRateCorrection,
m_nbRemoteSamplesSinceRateCorrection,
m_nbSamplesSinceRateCorrection);
m_lastRemoteTimestampRateCorrection = remoteTimestampUs;
m_lastTimestampRateCorrection = timestampUs;
m_nbRemoteSamplesSinceRateCorrection = 0;
m_nbSamplesSinceRateCorrection = 0;
}
}
m_lastRemoteSampleCount = remoteSampleCount;
m_lastSampleCount = sampleCount;
m_lastQueueLength = queueLength;
m_greaterTickCount = 0; m_greaterTickCount = 0;
} }
}
else if (jsonObject.contains("remoteOutputSettings")) else if (jsonObject.contains("remoteOutputSettings"))
{ {
qDebug("RemoteOutput::analyzeApiReply: reply:\n%s", answer.toStdString().c_str()); qDebug("RemoteOutput::analyzeApiReply: reply:\n%s", answer.toStdString().c_str());
@ -685,16 +618,18 @@ void RemoteOutput::analyzeApiReply(const QJsonObject& jsonObject, const QString&
} }
} }
void RemoteOutput::sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount) void RemoteOutput::queueLengthCompensation(
int nominalSR,
int queueLength,
int queueSize
)
{ {
double deltaSR = (remoteSampleCount/remoteTimeDeltaUs) - (sampleCount/timeDeltaUs); int deltaQueueBlocks = (queueSize/2) - queueLength;
double chunkCorr = 50000 * deltaSR; // for 50ms chunk intervals (50000us) int blockMultiplier = nominalSR / 4000;
m_chunkSizeCorrection += roundf(chunkCorr); blockMultiplier = blockMultiplier < 12 ? 12 : blockMultiplier;
int corr = deltaQueueBlocks * blockMultiplier;
qDebug("RemoteOutput::sampleRateCorrection: remote: %u / %f us local: %u / %f us corr: %d (%f) samples", qDebug("RemoteOutput::queueLengthCompensation: deltaQueueBlocks: %d corr: %d", deltaQueueBlocks, corr);
remoteSampleCount, remoteTimeDeltaUs, sampleCount, timeDeltaUs, m_chunkSizeCorrection, chunkCorr); MsgConfigureRemoteOutputChunkCorrection* message = MsgConfigureRemoteOutputChunkCorrection::create(corr);
MsgConfigureRemoteOutputChunkCorrection* message = MsgConfigureRemoteOutputChunkCorrection::create(m_chunkSizeCorrection);
getInputMessageQueue()->push(message); getInputMessageQueue()->push(message);
} }

View File

@ -288,16 +288,6 @@ private:
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest; QNetworkRequest m_networkRequest;
uint32_t m_lastRemoteSampleCount;
uint32_t m_lastSampleCount;
uint64_t m_lastRemoteTimestampRateCorrection;
uint64_t m_lastTimestampRateCorrection;
int m_lastQueueLength;
uint32_t m_nbRemoteSamplesSinceRateCorrection;
uint32_t m_nbSamplesSinceRateCorrection;
int m_chunkSizeCorrection;
static const uint32_t NbSamplesForRateCorrection;
void startWorker(); void startWorker();
void stopWorker(); void stopWorker();
void applySettings(const RemoteOutputSettings& settings, bool force = false); void applySettings(const RemoteOutputSettings& settings, bool force = false);
@ -306,7 +296,11 @@ private:
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
void analyzeApiReply(const QJsonObject& jsonObject, const QString& answer); void analyzeApiReply(const QJsonObject& jsonObject, const QString& answer);
void sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount); void queueLengthCompensation(
int nominalSR,
int queueLength,
int queueSize
);
void webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const RemoteOutputSettings& settings, bool force); void webapiReverseSendSettings(QList<QString>& deviceSettingsKeys, const RemoteOutputSettings& settings, bool force);
void webapiReverseSendStartStop(bool start); void webapiReverseSendStartStop(bool start);

View File

@ -91,13 +91,14 @@ void RemoteDataReadQueue::readSample(Sample& s, bool scaleForTx)
m_sampleIndex = 0; m_sampleIndex = 0;
convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx);
m_sampleIndex++; m_sampleIndex++;
m_sampleCount++;
} }
else else
{ {
s = Sample{0, 0}; s = Sample{0, 0};
} }
m_sampleCount++;
return; return;
} }

View File

@ -10004,6 +10004,12 @@ margin-bottom: 20px;
"title" : { "title" : {
"type" : "string" "type" : "string"
}, },
"log2Interp" : {
"type" : "integer"
},
"filterChainHash" : {
"type" : "integer"
},
"streamIndex" : { "streamIndex" : {
"type" : "integer", "type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)." "description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
@ -51597,7 +51603,7 @@ except ApiException as e:
</div> </div>
<div id="generator"> <div id="generator">
<div class="content"> <div class="content">
Generated 2021-12-12T19:10:03.240+01:00 Generated 2021-12-12T22:32:39.234+01:00
</div> </div>
</div> </div>
</div> </div>

View File

@ -11,6 +11,10 @@ RemoteSourceSettings:
type: integer type: integer
title: title:
type: string type: string
log2Interp:
type: integer
filterChainHash:
type: integer
streamIndex: streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx). description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer type: integer

View File

@ -11,6 +11,10 @@ RemoteSourceSettings:
type: integer type: integer
title: title:
type: string type: string
log2Interp:
type: integer
filterChainHash:
type: integer
streamIndex: streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx). description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer type: integer

View File

@ -10004,6 +10004,12 @@ margin-bottom: 20px;
"title" : { "title" : {
"type" : "string" "type" : "string"
}, },
"log2Interp" : {
"type" : "integer"
},
"filterChainHash" : {
"type" : "integer"
},
"streamIndex" : { "streamIndex" : {
"type" : "integer", "type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)." "description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
@ -51597,7 +51603,7 @@ except ApiException as e:
</div> </div>
<div id="generator"> <div id="generator">
<div class="content"> <div class="content">
Generated 2021-12-12T19:10:03.240+01:00 Generated 2021-12-12T22:32:39.234+01:00
</div> </div>
</div> </div>
</div> </div>

View File

@ -36,6 +36,10 @@ SWGRemoteSourceSettings::SWGRemoteSourceSettings() {
m_rgb_color_isSet = false; m_rgb_color_isSet = false;
title = nullptr; title = nullptr;
m_title_isSet = false; m_title_isSet = false;
log2_interp = 0;
m_log2_interp_isSet = false;
filter_chain_hash = 0;
m_filter_chain_hash_isSet = false;
stream_index = 0; stream_index = 0;
m_stream_index_isSet = false; m_stream_index_isSet = false;
use_reverse_api = 0; use_reverse_api = 0;
@ -66,6 +70,10 @@ SWGRemoteSourceSettings::init() {
m_rgb_color_isSet = false; m_rgb_color_isSet = false;
title = new QString(""); title = new QString("");
m_title_isSet = false; m_title_isSet = false;
log2_interp = 0;
m_log2_interp_isSet = false;
filter_chain_hash = 0;
m_filter_chain_hash_isSet = false;
stream_index = 0; stream_index = 0;
m_stream_index_isSet = false; m_stream_index_isSet = false;
use_reverse_api = 0; use_reverse_api = 0;
@ -94,6 +102,8 @@ SWGRemoteSourceSettings::cleanup() {
} }
if(reverse_api_address != nullptr) { if(reverse_api_address != nullptr) {
delete reverse_api_address; delete reverse_api_address;
} }
@ -124,6 +134,10 @@ SWGRemoteSourceSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&log2_interp, pJson["log2Interp"], "qint32", "");
::SWGSDRangel::setValue(&filter_chain_hash, pJson["filterChainHash"], "qint32", "");
::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", ""); ::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", "");
@ -166,6 +180,12 @@ SWGRemoteSourceSettings::asJsonObject() {
if(title != nullptr && *title != QString("")){ if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString")); toJsonValue(QString("title"), title, obj, QString("QString"));
} }
if(m_log2_interp_isSet){
obj->insert("log2Interp", QJsonValue(log2_interp));
}
if(m_filter_chain_hash_isSet){
obj->insert("filterChainHash", QJsonValue(filter_chain_hash));
}
if(m_stream_index_isSet){ if(m_stream_index_isSet){
obj->insert("streamIndex", QJsonValue(stream_index)); obj->insert("streamIndex", QJsonValue(stream_index));
} }
@ -231,6 +251,26 @@ SWGRemoteSourceSettings::setTitle(QString* title) {
this->m_title_isSet = true; this->m_title_isSet = true;
} }
qint32
SWGRemoteSourceSettings::getLog2Interp() {
return log2_interp;
}
void
SWGRemoteSourceSettings::setLog2Interp(qint32 log2_interp) {
this->log2_interp = log2_interp;
this->m_log2_interp_isSet = true;
}
qint32
SWGRemoteSourceSettings::getFilterChainHash() {
return filter_chain_hash;
}
void
SWGRemoteSourceSettings::setFilterChainHash(qint32 filter_chain_hash) {
this->filter_chain_hash = filter_chain_hash;
this->m_filter_chain_hash_isSet = true;
}
qint32 qint32
SWGRemoteSourceSettings::getStreamIndex() { SWGRemoteSourceSettings::getStreamIndex() {
return stream_index; return stream_index;
@ -318,6 +358,12 @@ SWGRemoteSourceSettings::isSet(){
if(title && *title != QString("")){ if(title && *title != QString("")){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }
if(m_log2_interp_isSet){
isObjectUpdated = true; break;
}
if(m_filter_chain_hash_isSet){
isObjectUpdated = true; break;
}
if(m_stream_index_isSet){ if(m_stream_index_isSet){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }

View File

@ -55,6 +55,12 @@ public:
QString* getTitle(); QString* getTitle();
void setTitle(QString* title); void setTitle(QString* title);
qint32 getLog2Interp();
void setLog2Interp(qint32 log2_interp);
qint32 getFilterChainHash();
void setFilterChainHash(qint32 filter_chain_hash);
qint32 getStreamIndex(); qint32 getStreamIndex();
void setStreamIndex(qint32 stream_index); void setStreamIndex(qint32 stream_index);
@ -92,6 +98,12 @@ private:
QString* title; QString* title;
bool m_title_isSet; bool m_title_isSet;
qint32 log2_interp;
bool m_log2_interp_isSet;
qint32 filter_chain_hash;
bool m_filter_chain_hash_isSet;
qint32 stream_index; qint32 stream_index;
bool m_stream_index_isSet; bool m_stream_index_isSet;