From 566bd6d18c61f0577b52aaf49ee693c419929296 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Sun, 13 Nov 2022 15:53:48 +0000 Subject: [PATCH 1/9] Allow transmit window to specify min/max FPS --- src/sacn/e1_11.h | 1 + src/sacn/sacnsender.cpp | 55 ++++++++++++++++++++++++++++++--------- src/sacn/sacnsender.h | 27 ++++++++++++++----- src/sacn/streamcommon.h | 7 +++++ src/ui/transmitwindow.cpp | 31 ++++++++++++++++++++++ src/ui/transmitwindow.h | 3 +++ ui/transmitwindow.ui | 33 ++++++++++++++++++++++- 7 files changed, 137 insertions(+), 20 deletions(-) diff --git a/src/sacn/e1_11.h b/src/sacn/e1_11.h index 6964117d..44a81451 100644 --- a/src/sacn/e1_11.h +++ b/src/sacn/e1_11.h @@ -2,6 +2,7 @@ #define E1_11_H namespace E1_11 { + static const unsigned int MIN_REFRESH_RATE_HZ = 1; // E1.11:2008 Table 6 static const unsigned int MAX_REFRESH_RATE_HZ = 44; // E1.11:2008 Table 6 } diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 48e1957d..18ab8d72 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -43,7 +43,9 @@ sACNSentUniverse::sACNSentUniverse(int universe) : m_priorityMode(pmPER_SOURCE_PRIORITY), m_version(sACNProtocolRelease), m_checkTimeoutTimer(Q_NULLPTR), - m_synchronization(NOT_SYNCHRONIZED_VALUE) + m_synchronization(NOT_SYNCHRONIZED_VALUE), + m_minSendFreq(E131_DATA_KEEP_ALIVE_FREQUENCY), + m_maxSendFreq(E1_11::MAX_REFRESH_RATE_HZ) {} sACNSentUniverse::~sACNSentUniverse() @@ -70,8 +72,8 @@ void sACNSentUniverse::startSending(bool preview) if (preview) options += PREVIEW_DATA_OPTION; - uint max_tx_rate = - Preferences::getInstance()->GetTXRateOverride() ? std::numeric_limits::max() : E1_11::MAX_REFRESH_RATE_HZ; + m_maxSendFreq = + Preferences::getInstance()->GetTXRateOverride() ? std::numeric_limits::max() : E1_11::MAX_REFRESH_RATE_HZ; CIPAddr unicastAddress; if(!m_unicastAddress.isNull()) @@ -80,7 +82,7 @@ void sACNSentUniverse::startSending(bool preview) // DMX Data (0x00) server streamServer->CreateUniverse( m_cid, qPrintable(m_name), m_priority, m_synchronization, options, STARTCODE_DMX, - m_universe, m_slotCount, m_slotData, m_handle, SEND_INTERVAL_DMX, max_tx_rate, + m_universe, m_slotCount, m_slotData, m_handle, m_minSendFreq, m_maxSendFreq, unicastAddress, m_version); streamServer->setSecurePassword(m_handle, m_password); streamServer->SetUniverseDirty(m_handle); @@ -91,7 +93,7 @@ void sACNSentUniverse::startSending(bool preview) quint8 *pslots; streamServer->CreateUniverse( m_cid, qPrintable(m_name), m_priority, NOT_SYNCHRONIZED_VALUE, options, STARTCODE_PRIORITY, - m_universe, m_slotCount, pslots, m_priorityHandle, SEND_INTERVAL_PRIORITY, max_tx_rate, + m_universe, m_slotCount, pslots, m_priorityHandle, E1_11::MIN_REFRESH_RATE_HZ, m_maxSendFreq, unicastAddress, m_version); memcpy(pslots, m_perChannelPriorities, @@ -311,6 +313,19 @@ void sACNSentUniverse::setSecurePassword(const QString &password) emit passwordChange(); } +void sACNSentUniverse::setSendFrequency(float minimum, float maximum) +{ + if(minimum==m_minSendFreq && maximum==m_maxSendFreq) return; + CStreamServer *streamServer = CStreamServer::getInstance(); + if (!streamServer) + return; + m_minSendFreq = minimum; + m_maxSendFreq = maximum; + return streamServer->setSendFrequency(m_handle, m_minSendFreq, m_maxSendFreq); + + emit sendFrequencyChange(); +} + void sACNSentUniverse::doTimeout() { delete m_checkTimeoutTimer; @@ -344,6 +359,7 @@ CStreamServer::CStreamServer() : m_sendsock->bind(); m_thread = new QThread(); + m_thread->setPriority(QThread::LowestPriority); connect(m_thread, SIGNAL(started()), this, SLOT(TickLoop())); connect(m_thread, &QThread::finished, this, &QThread::deleteLater); this->moveToThread(m_thread); @@ -415,9 +431,7 @@ void CStreamServer::TickLoop() qDebug() << "sACNSender" << QThread::currentThreadId() << ": Starting"; while (!m_thread_stop) { - QThread::yieldCurrentThread(); - QCoreApplication::processEvents(); - QThread::msleep(1); + QThread::usleep(500); QMutexLocker locker(&m_writeMutex); int valid_count = 0; @@ -504,8 +518,7 @@ bool CStreamServer::CreateUniverse( const CID& source_cid, const char* source_name, quint8 priority, quint16 synchronization, quint8 options, quint8 start_code, quint16 universe, quint16 slot_count, quint8*& pslots, uint& handle, - uint send_intervalms, - uint send_max_rate, + float minimum_send_rate, float maximum_send_rate , CIPAddr unicastAddress, StreamingACNProtocolVersion version) { @@ -519,7 +532,7 @@ bool CStreamServer::CreateUniverse( { default: case sACNProtocolUnknown: - qFatal(qPrintable(QString("Attempting to set unknown protocol version (%1)").arg(version))); + qFatal("Attempting to set unknown protocol version (0x%x)", version); return false; case sACNProtocolDraft: @@ -566,8 +579,7 @@ bool CStreamServer::CreateUniverse( m_multiverse[handle].start_code = start_code; m_multiverse[handle].isdirty = false; m_multiverse[handle].num_terminates=0; - m_multiverse[handle].send_interval.SetInterval(send_intervalms); - m_multiverse[handle].min_interval.SetInterval(1000 / (std::max(send_max_rate, (decltype(send_max_rate))1))); + setSendFrequency(handle, minimum_send_rate, maximum_send_rate); m_multiverse[handle].version = version; m_multiverse[handle].cid = source_cid; @@ -718,3 +730,20 @@ void CStreamServer::setSecurePassword(uint handle, const QString &password) m_multiverse[handle].password = password; } +void CStreamServer::setSendFrequency(uint handle, float minimum, float maximum) +{ + if(handle < m_multiverse.size()) { + if (maximum <= 0) + maximum = E1_11::MAX_REFRESH_RATE_HZ; + + if (minimum <= 0 || minimum > maximum) + minimum = E1_11::MIN_REFRESH_RATE_HZ; + + m_multiverse[handle].send_interval.SetInterval(1000 / minimum); + m_multiverse[handle].min_interval.SetInterval(1000 / maximum); + + m_multiverse[handle].send_interval.Reset(); + m_multiverse[handle].min_interval.Reset(); + } +} + diff --git a/src/sacn/sacnsender.h b/src/sacn/sacnsender.h index 5a96209b..6d44232b 100644 --- a/src/sacn/sacnsender.h +++ b/src/sacn/sacnsender.h @@ -153,6 +153,14 @@ public slots: void setSecurePassword(const QString &password); const QString &getSecurePassword() { return m_password; } + /** + * @brief setSendFrequency - set minimum and maximum data send frequency + * @param minimum - minimum FPS + * @param maximum - maximum FPS + */ + void setSendFrequency(float minimum, float maximum); + std::pair getBackgrounRefreshFrequency() const { return std::pair(m_minSendFreq, m_maxSendFreq); } + signals: /** * @brief sendingTimeout is emitted when the source stops sending @@ -185,6 +193,11 @@ public slots: */ void passwordChange(); + /** + * @brief sendFrequencyChange is emitted when the source changes minimum or maximum data send frequency + */ + void sendFrequencyChange(); + private slots: void doTimeout(); private: @@ -221,12 +234,11 @@ private slots: quint16 m_synchronization; // Secure password QString m_password; + // Minimum and maximum send frequencies + float m_minSendFreq; + float m_maxSendFreq; }; -//These definitions are to be used with the send_intervalms parameter of CreateUniverse -#define SEND_INTERVAL_DMX 850 /*If no data has been sent in 850ms, send another DMX packet*/ -#define SEND_INTERVAL_PRIORITY 1000 /*By default, per-channel priority packets are sent once per second*/ - class CStreamServer : public QObject { Q_OBJECT @@ -258,8 +270,8 @@ class CStreamServer : public QObject const CID& source_cid, const char* source_name, quint8 priority, quint16 synchronization, quint8 options, quint8 start_code, quint16 universe, quint16 slot_count, quint8*& pslots, uint& handle, - uint send_intervalms = SEND_INTERVAL_DMX, - uint send_max_rate = E1_11::MAX_REFRESH_RATE_HZ, + float minimum_send_rate = E1_11::MIN_REFRESH_RATE_HZ, + float maximum_send_rate = E1_11::MAX_REFRESH_RATE_HZ, CIPAddr unicastAddress = CIPAddr(), StreamingACNProtocolVersion version = StreamingACNProtocolVersion::sACNProtocolRelease); @@ -295,6 +307,9 @@ class CStreamServer : public QObject void setSynchronizationAddress(uint handle, quint16 address); void setSecurePassword(uint handle, const QString &password); + // Set the minimum and maximum send frequency (FPS) + void setSendFrequency(uint handle, float minimum = E1_11::MIN_REFRESH_RATE_HZ, float maximum = E1_11::MAX_REFRESH_RATE_HZ); + //Use this to destroy a priority universe. void DEBUG_DESTROY_PRIORITY_UNIVERSE(uint handle); diff --git a/src/sacn/streamcommon.h b/src/sacn/streamcommon.h index 31303003..f45ce6db 100644 --- a/src/sacn/streamcommon.h +++ b/src/sacn/streamcommon.h @@ -167,6 +167,13 @@ // Timings #define E131_UNIVERSE_DISCOVERY_INTERVAL 10000 //10 Seconds #define E131_NETWORK_DATA_LOSS_TIMEOUT 2500 // 2.5 Seconds +#define E131_DATA_KEEP_ALIVE_INTERVAL_MIN 800 // 800 mS +#define E131_DATA_KEEP_ALIVE_INTERVAL_MAX 1000 // 1000 mS +// We'll go with min +25% of the difference between max and min +static constexpr float E131_DATA_KEEP_ALIVE_FREQUENCY = 1000 / + (static_cast(E131_DATA_KEEP_ALIVE_INTERVAL_MIN) + + (static_cast(E131_DATA_KEEP_ALIVE_INTERVAL_MAX) - static_cast(E131_DATA_KEEP_ALIVE_INTERVAL_MIN)) / 4); + /*** Functions ***/ diff --git a/src/ui/transmitwindow.cpp b/src/ui/transmitwindow.cpp index 26e9c2b4..89c48a02 100644 --- a/src/ui/transmitwindow.cpp +++ b/src/ui/transmitwindow.cpp @@ -197,6 +197,21 @@ transmitwindow::transmitwindow(int universe, QWidget *parent) : ui->gbPathwaySecurePassword->setVisible(ui->rbPathwaySecure->isChecked()); ui->lePathwaySecurePassword->setText(Preferences::getInstance()->GetPathwaySecureTxPassword()); + // Minimum FPS + if (!Preferences::getInstance()->GetTXRateOverride()) { + ui->sbMinFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ); + ui->sbMinFPS->setMaximum(E1_11::MAX_REFRESH_RATE_HZ); + ui->sbMaxFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ); + ui->sbMaxFPS->setMaximum(E1_11::MAX_REFRESH_RATE_HZ); + } else { + ui->sbMinFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ / 2); + ui->sbMinFPS->setMaximum(std::numeric_limitssbMinFPS->value())>::max()); + ui->sbMaxFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ / 2); + ui->sbMaxFPS->setMaximum(std::numeric_limitssbMaxFPS->value())>::max()); + } + ui->sbMinFPS->setValue(E131_DATA_KEEP_ALIVE_FREQUENCY); + ui->sbMaxFPS->setValue(E1_11::MAX_REFRESH_RATE_HZ); + setUniverseOptsEnabled(true); // Set up keypad @@ -407,6 +422,8 @@ void transmitwindow::on_btnStart_pressed() m_sender->setPriorityMode(pmPER_SOURCE_PRIORITY); m_sender->setPerSourcePriority(ui->sbPriority->value()); } + using namespace std::chrono_literals; + m_sender->setSendFrequency(ui->sbMinFPS->value(), ui->sbMaxFPS->value()); m_sender->startSending(ui->cbBlind->isChecked()); @@ -872,3 +889,17 @@ void transmitwindow::on_rbPathwaySecure_toggled(bool checked) ui->gbPathwaySecurePassword->setVisible(checked); } +void transmitwindow::on_sbMinFPS_editingFinished() +{ + // Don't go greater than MaxFPS + if (ui->sbMinFPS->value() > ui->sbMaxFPS->value()) + ui->sbMinFPS->setValue(ui->sbMaxFPS->value()); +} + +void transmitwindow::on_sbMaxFPS_editingFinished() +{ + // Don't go less than MinFPS + if (ui->sbMaxFPS->value() < ui->sbMinFPS->value()) + ui->sbMaxFPS->setValue(ui->sbMinFPS->value()); +} + diff --git a/src/ui/transmitwindow.h b/src/ui/transmitwindow.h index 94e9ac0b..52d21d60 100644 --- a/src/ui/transmitwindow.h +++ b/src/ui/transmitwindow.h @@ -80,6 +80,9 @@ private slots: void on_rbPathwaySecure_toggled(bool checked); + void on_sbMinFPS_editingFinished(); + void on_sbMaxFPS_editingFinished(); + private: enum TABS { diff --git a/ui/transmitwindow.ui b/ui/transmitwindow.ui index 01d5fa51..9efc3dc2 100644 --- a/ui/transmitwindow.ui +++ b/ui/transmitwindow.ui @@ -128,7 +128,7 @@ border-radius: 4px; - 270 + 300 0 @@ -428,6 +428,37 @@ border-radius: 4px; + + + + Min/Max FPS + + + + + + + + + Hz + + + 99.989999999999995 + + + + + + + Hz + + + 99.989999999999995 + + + + + From 147ec7f8b048bebb4201e61841e0e8e51ec04c02 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Sun, 13 Nov 2022 15:55:17 +0000 Subject: [PATCH 2/9] Increase resolution of tock class, and tick rate of sender tickLoop() --- src/sacn/ACNShare/tock.cpp | 93 +++++++++++++++++++++++++++++++- src/sacn/ACNShare/tock.h | 81 +++++++++++----------------- src/sacn/sacndiscovery.cpp | 2 +- src/sacn/sacnlistener.cpp | 20 +++---- src/sacn/sacnsender.cpp | 18 +++++-- src/sacn/sacnsynchronization.cpp | 2 +- 6 files changed, 149 insertions(+), 67 deletions(-) diff --git a/src/sacn/ACNShare/tock.cpp b/src/sacn/ACNShare/tock.cpp index ebc13723..74b27a2a 100644 --- a/src/sacn/ACNShare/tock.cpp +++ b/src/sacn/ACNShare/tock.cpp @@ -38,7 +38,7 @@ bool Tock_StartLib() //Gets a tock representing the current time tock Tock_GetTock() { - return tock(timer.elapsed()); + return tock(std::chrono::nanoseconds(timer.nsecsElapsed())); } //Shuts down the tock layer. @@ -46,3 +46,94 @@ void Tock_StopLib() { } + +tock::tock():v(0) {} + +template +tock::tock(std::chrono::duration duration) +{ + v = duration; +} + +tock::resolution_t tock::Get() const +{ + return v; +} + +void tock::Set(tock::resolution_t time) +{ + v = time; +} + +bool operator>(const tock& t1, const tock& t2) +{ + return t1.v.count() - t2.v.count() > 0; +} + +bool operator>=(const tock& t1, const tock& t2) +{ + return t1.v.count() - t2.v.count() >= 0; +} + +bool operator==(const tock& t1, const tock& t2) +{ + return t1.v.count() - t2.v.count() == 0; +} + +bool operator!=(const tock& t1, const tock& t2) +{ + return t1.v.count() - t2.v.count() != 0; +} + +bool operator<(const tock& t1, const tock& t2) +{ + return t2.v.count() - t1.v.count() > 0; +} + +bool operator<=(const tock& t1, const tock& t2) +{ + return t2.v.count() - t1.v.count() >= 0; +} + +ttimer::ttimer():interval(0) +{ + Reset(); +} + +template +ttimer::ttimer(std::chrono::duration interval) : + interval(interval) +{ + Reset(); +} + +void ttimer::SetInterval(tock::resolution_t interval) +{ + this->interval = interval; + Reset(); +} + +tock::resolution_t ttimer::GetInterval() const +{ + return interval; +} + +void ttimer::Reset() +{ + tockout.Set(Tock_GetTock().Get() + interval); +} + +bool ttimer::Expired() const +{ + return (Tock_GetTock().Get()) >= tockout.Get(); +} + +bool operator==(const ttimer& t1, const ttimer& t2) +{ + return ((t1.tockout == t2.tockout) && (t1.interval == t2.interval)); +} + +bool operator!=(const ttimer& t1, const ttimer& t2) +{ + return !(t1 == t2); +} diff --git a/src/sacn/ACNShare/tock.h b/src/sacn/ACNShare/tock.h index 56284d2d..aa474980 100644 --- a/src/sacn/ACNShare/tock.h +++ b/src/sacn/ACNShare/tock.h @@ -22,13 +22,13 @@ Provides a standard definition of a tock and a ttimer. - A tock is the number of milliseconds since a platform-specific date. + A tock is the number of nanoseconds since a platform-specific date. Tocks are never used directly, rather the difference between two tocks (latest - previous) are used to determine the passage of time. It is assumed that tocks always move forward, and that the base date is not different for any instance of the compiled tock, nor does the base date change. - Tocks are guaranteed to have a millisecond granularity. + Tocks are guaranteed to have a nanosecond granularity. Tock comparisons correctly handle clock rollover. A ttimer is a simple abstraction for typical timer usage, which is @@ -40,6 +40,7 @@ #define _TOCK_H_ #include +#include class tock; class ttimer; @@ -57,20 +58,26 @@ void Tock_StopLib(); //Shuts down the tock layer. class tock { public: + typedef std::chrono::nanoseconds resolution_t; + //construction and copying tock(); - tock(quint32 ms); - tock(const tock& t); - tock& operator=(const tock& t); + tock(const tock&) = default; + tock(tock&&) = default; + tock& operator=(const tock&) = default; + tock& operator=(tock&&) = default; + + template + tock(std::chrono::duration duration); - //Returns the number of milliseconds that this tock represents - quint32 Getms(); + //Returns the number of nanoseconds that this tock represents + resolution_t Get() const; - //Used sparingly, but sets the number of milliseconds that this tock represents - void Setms(quint32 ms); + //Used sparingly, but sets the number of nanoseconds that this tock represents + void Set(resolution_t time); protected: - qint32 v; //Signed, so the wraparound calculations will work + resolution_t v; friend bool operator>(const tock& t1, const tock& t2); friend bool operator>=(const tock& t1, const tock& t2); @@ -87,52 +94,28 @@ class ttimer public: //construction/setup ttimer(); //Will immediately time out if timeout isn't set - ttimer(qint32 ms); //The number of milliseconds before the timer will time out - void SetInterval(qint32 ms); //Sets a new timeout interval (in ms) and resets the timer - qint32 GetInterval(); //Returns the current timeout interval (in ms) + ttimer(const ttimer&) = default; + ttimer(ttimer&&) = default; + ttimer& operator=(const ttimer&) = default; + ttimer& operator=(ttimer&&) = default; + template + ttimer(std::chrono::duration interval); //The duration before the timer will time out + + void SetInterval(tock::resolution_t interval); //Sets a new timeout interval and resets the timer + + tock::resolution_t GetInterval() const; //Returns the current timeout interval void Reset(); //Resets the timer, using the current timeout interval - bool Expired() const; //Returns true if the timer has expired. - //Call Reset() to use this timer again for a new interval. + + //Returns true if the timer has expired + //Call Reset() to use this timer again for a new interval. + bool Expired() const; friend bool operator==(const ttimer& t1, const ttimer& t2); friend bool operator!=(const ttimer& t1, const ttimer& t2); protected: - qint32 interval; + tock::resolution_t interval; tock tockout; }; - - -//--------------------------------------------------------------------------------- -// Implementation - -/*ttimer implementation*/ -inline ttimer::ttimer():interval(0) {Reset();} -inline ttimer::ttimer(qint32 ms):interval(ms) {Reset();} -inline void ttimer::SetInterval(qint32 ms) {interval = ms; Reset();} -inline qint32 ttimer::GetInterval() {return interval;} -inline void ttimer::Reset() {tockout.Setms(Tock_GetTock().Getms() + interval);} -inline bool ttimer::Expired() const {return Tock_GetTock() > tockout;} -inline bool operator==(const ttimer& t1, const ttimer& t2) { return ((t1.tockout == t2.tockout) && (t1.interval == t2.interval)); } -inline bool operator!=(const ttimer& t1, const ttimer& t2) { return !(t1 == t2); } - -/*tock implementation*/ -inline tock::tock():v(0) {} -inline tock::tock(quint32 ms):v(ms) {} -inline tock::tock(const tock& t) {v = t.v;} -inline tock& tock::operator=(const tock& t) {v = t.v; return *this;} - -inline quint32 tock::Getms() {return v;} -inline void tock::Setms(quint32 ms) {v = ms;} - -inline bool operator>(const tock& t1, const tock& t2) {return t1.v - t2.v > 0;} -inline bool operator>=(const tock& t1, const tock& t2) {return t1.v - t2.v >= 0;} -inline bool operator==(const tock& t1, const tock& t2) {return t1.v - t2.v == 0;} -inline bool operator!=(const tock& t1, const tock& t2) {return t1.v - t2.v != 0;} -inline bool operator<(const tock& t1, const tock& t2) {return t2.v - t1.v > 0;} -inline bool operator<=(const tock& t1, const tock& t2) {return t2.v - t1.v >= 0;} -inline quint32 operator-(const tock& t1, const tock& t2) {return t1.v - t2.v;} - - #endif /*_TOCK_H*/ diff --git a/src/sacn/sacndiscovery.cpp b/src/sacn/sacndiscovery.cpp index 0eec09a4..cc57ecf8 100644 --- a/src/sacn/sacndiscovery.cpp +++ b/src/sacn/sacndiscovery.cpp @@ -280,7 +280,7 @@ void sACNDiscoveryRX::processPacket(quint8* pbuf, uint buflen) qDebug() << "DiscoveryRX : New universe - CID" << CID::CIDIntoQString(cid) << ", universe" << universe; emit newUniverse(CID::CIDIntoQString(cid), universe); } - m_discoveryList[cid]->Universe[universe].SetInterval(E131_UNIVERSE_DISCOVERY_INTERVAL); + m_discoveryList[cid]->Universe[universe].SetInterval(std::chrono::milliseconds(E131_UNIVERSE_DISCOVERY_INTERVAL)); } } else diff --git a/src/sacn/sacnlistener.cpp b/src/sacn/sacnlistener.cpp index 8cf6427e..381a10bb 100644 --- a/src/sacn/sacnlistener.cpp +++ b/src/sacn/sacnlistener.cpp @@ -341,14 +341,14 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH { // This is a source which is coming back online, so we need to repeat the steps // for initial source aquisition - ps->active.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); + ps->active.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); ps->lastseq = sequence; ps->src_cid = source_cid; ps->src_valid = true; ps->doing_dmx = (start_code == STARTCODE_DMX); ps->doing_per_channel = ps->waited_for_dd = false; newsourcenotify = false; - ps->priority_wait.SetInterval(WAIT_PRIORITY); + ps->priority_wait.SetInterval(std::chrono::milliseconds(WAIT_PRIORITY)); } if( @@ -365,10 +365,10 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH //to a value of 1, a receiver shall enter network //data loss condition. Any property values in //these packets shall be ignored" - (*it)->active.SetInterval(m_ssHLL); //We factor in the hold last look time here, rather than 0 + (*it)->active.SetInterval(std::chrono::milliseconds(m_ssHLL)); //We factor in the hold last look time here, rather than 0 if((*it)->doing_per_channel) - (*it)->priority_wait.SetInterval(m_ssHLL); //We factor in the hold last look time here, rather than 0 + (*it)->priority_wait.SetInterval(std::chrono::milliseconds(m_ssHLL)); //We factor in the hold last look time here, rather than 0 validpacket = false; break; @@ -379,7 +379,7 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH { //No matter how valid, we got something -- but we'll tweak the interval for any hll change (*it)->doing_dmx = true; - (*it)->active.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); + (*it)->active.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); } else if(start_code == STARTCODE_PRIORITY && (*it)->waited_for_dd) { @@ -421,14 +421,14 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH { (*it)->waited_for_dd = true; (*it)->doing_per_channel = true; - (*it)->priority_wait.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); + (*it)->priority_wait.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); newsourcenotify = true; } else if((*it)->priority_wait.Expired()) { (*it)->waited_for_dd = true; (*it)->doing_per_channel = false; - (*it)->priority_wait.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); //In case the source later decides to sent 0xdd packets + (*it)->priority_wait.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); //In case the source later decides to sent 0xdd packets newsourcenotify = true; } else @@ -455,7 +455,7 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH ps->ip = sender; ps->universe = universe; ps->synchronization = synchronization; - ps->active.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); + ps->active.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); ps->lastseq = sequence; ps->src_cid = source_cid; ps->src_valid = true; @@ -467,14 +467,14 @@ void sACNListener::processDatagram(QByteArray data, QHostAddress destination, QH ps->waited_for_dd = true; ps->doing_per_channel = (start_code == STARTCODE_PRIORITY); newsourcenotify = true; - ps->priority_wait.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL); + ps->priority_wait.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT + m_ssHLL)); } else { //If we aren't sampling, we want the earlier logic to set the state ps->doing_per_channel = ps->waited_for_dd = false; newsourcenotify = false; - ps->priority_wait.SetInterval(WAIT_PRIORITY); + ps->priority_wait.SetInterval(std::chrono::milliseconds(WAIT_PRIORITY)); } validpacket = newsourcenotify; diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 18ab8d72..4aa40009 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -33,6 +33,9 @@ #include "preferences.h" +using namespace std::chrono_literals; +constexpr auto tickLoopInterval = 500ms; + sACNSentUniverse::sACNSentUniverse(int universe) : m_isSending(false), m_handle(0), @@ -359,12 +362,12 @@ CStreamServer::CStreamServer() : m_sendsock->bind(); m_thread = new QThread(); - m_thread->setPriority(QThread::LowestPriority); connect(m_thread, SIGNAL(started()), this, SLOT(TickLoop())); connect(m_thread, &QThread::finished, this, &QThread::deleteLater); this->moveToThread(m_thread); m_thread->setObjectName(QString("Interface %1 TX").arg(m_sendsock->multicastInterface().name())); m_thread->start(); + m_thread->setPriority(QThread::LowestPriority); } CStreamServer::~CStreamServer() @@ -428,10 +431,12 @@ void CStreamServer::RemovePSeq(const CID &cid, quint16 universe) void CStreamServer::TickLoop() { - qDebug() << "sACNSender" << QThread::currentThreadId() << ": Starting"; + qDebug() << "sACNSender" << QThread::currentThreadId() + << ": Starting with tickLoopInterval" + << std::chrono::duration_cast(tickLoopInterval).count() << "ms"; while (!m_thread_stop) { - QThread::usleep(500); + QThread::usleep(std::chrono::duration_cast(tickLoopInterval).count()); QMutexLocker locker(&m_writeMutex); int valid_count = 0; @@ -739,8 +744,11 @@ void CStreamServer::setSendFrequency(uint handle, float minimum, float maximum) if (minimum <= 0 || minimum > maximum) minimum = E1_11::MIN_REFRESH_RATE_HZ; - m_multiverse[handle].send_interval.SetInterval(1000 / minimum); - m_multiverse[handle].min_interval.SetInterval(1000 / maximum); + typedef std::chrono::microseconds period_t; + m_multiverse[handle].send_interval.SetInterval( + period_t(static_cast(period_t::period().den / minimum))); + m_multiverse[handle].min_interval.SetInterval( + period_t(static_cast(period_t::period().den / maximum))); m_multiverse[handle].send_interval.Reset(); m_multiverse[handle].min_interval.Reset(); diff --git a/src/sacn/sacnsynchronization.cpp b/src/sacn/sacnsynchronization.cpp index 77466fae..b7419826 100644 --- a/src/sacn/sacnsynchronization.cpp +++ b/src/sacn/sacnsynchronization.cpp @@ -156,7 +156,7 @@ void sACNSynchronizationRX::processPacket(quint8* pbuf, uint buflen, QHostAddres } m_synchronizationSources[syncAddress][cid].sender = sender; - m_synchronizationSources[syncAddress][cid].dataLoss.SetInterval(E131_NETWORK_DATA_LOSS_TIMEOUT); + m_synchronizationSources[syncAddress][cid].dataLoss.SetInterval(std::chrono::milliseconds(E131_NETWORK_DATA_LOSS_TIMEOUT)); m_synchronizationSources[syncAddress][cid].fps->newFrame(); emit synchronize(syncAddress); From 54dbceed00189d6df68455ca016a4df117d9ed9e Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Sun, 13 Nov 2022 16:11:20 +0000 Subject: [PATCH 3/9] Improve send frequency range validation --- src/sacn/sacnsender.cpp | 8 ++++++-- src/ui/transmitwindow.cpp | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 4aa40009..6797edce 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -75,8 +75,12 @@ void sACNSentUniverse::startSending(bool preview) if (preview) options += PREVIEW_DATA_OPTION; - m_maxSendFreq = - Preferences::getInstance()->GetTXRateOverride() ? std::numeric_limits::max() : E1_11::MAX_REFRESH_RATE_HZ; + + std::clamp( + m_maxSendFreq, + m_minSendFreq, + Preferences::getInstance()->GetTXRateOverride() ? + std::numeric_limits::max() : E1_11::MAX_REFRESH_RATE_HZ); CIPAddr unicastAddress; if(!m_unicastAddress.isNull()) diff --git a/src/ui/transmitwindow.cpp b/src/ui/transmitwindow.cpp index 89c48a02..ea839c80 100644 --- a/src/ui/transmitwindow.cpp +++ b/src/ui/transmitwindow.cpp @@ -204,9 +204,9 @@ transmitwindow::transmitwindow(int universe, QWidget *parent) : ui->sbMaxFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ); ui->sbMaxFPS->setMaximum(E1_11::MAX_REFRESH_RATE_HZ); } else { - ui->sbMinFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ / 2); + ui->sbMinFPS->setMinimum(static_cast(E1_11::MIN_REFRESH_RATE_HZ) / 2); ui->sbMinFPS->setMaximum(std::numeric_limitssbMinFPS->value())>::max()); - ui->sbMaxFPS->setMinimum(E1_11::MIN_REFRESH_RATE_HZ / 2); + ui->sbMaxFPS->setMinimum(static_cast(E1_11::MIN_REFRESH_RATE_HZ) / 2); ui->sbMaxFPS->setMaximum(std::numeric_limitssbMaxFPS->value())>::max()); } ui->sbMinFPS->setValue(E131_DATA_KEEP_ALIVE_FREQUENCY); From 0c9d7e9e1089bd86edd6c7704726af8dc3113ed6 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Sun, 13 Nov 2022 21:21:24 +0000 Subject: [PATCH 4/9] Prevent other senders startup affect the initial sender frequency --- src/sacn/sacnsender.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 6797edce..55bdfa45 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -323,12 +323,12 @@ void sACNSentUniverse::setSecurePassword(const QString &password) void sACNSentUniverse::setSendFrequency(float minimum, float maximum) { if(minimum==m_minSendFreq && maximum==m_maxSendFreq) return; - CStreamServer *streamServer = CStreamServer::getInstance(); - if (!streamServer) - return; m_minSendFreq = minimum; m_maxSendFreq = maximum; - return streamServer->setSendFrequency(m_handle, m_minSendFreq, m_maxSendFreq); + if(isSending()) + { + CStreamServer::getInstance()->setSendFrequency(m_handle, m_minSendFreq, m_maxSendFreq); + } emit sendFrequencyChange(); } From f738b0b11312a90ff8d5be29a599e8e9cdf4af2c Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Mon, 21 Nov 2022 14:21:16 +0000 Subject: [PATCH 5/9] More send frequency range validation Forget to actually use the returned clamped m_maxSendFreq! --- src/sacn/sacnsender.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 55bdfa45..7dcc24ad 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -76,7 +76,7 @@ void sACNSentUniverse::startSending(bool preview) options += PREVIEW_DATA_OPTION; - std::clamp( + m_maxSendFreq = std::clamp( m_maxSendFreq, m_minSendFreq, Preferences::getInstance()->GetTXRateOverride() ? From f482d43bc00fdc4a56cb118e799b7ff132c0f003 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Mon, 21 Nov 2022 14:51:36 +0000 Subject: [PATCH 6/9] Replace with sleeping tickLoop, with lowest priority Dispense with a sleeping tickLoop, in favour of tight loop running at the lowest priority level. --- src/sacn/sacnsender.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 7dcc24ad..ff2b4625 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -33,8 +33,7 @@ #include "preferences.h" -using namespace std::chrono_literals; -constexpr auto tickLoopInterval = 500ms; +const auto tickLoopPriority = QThread::LowestPriority; sACNSentUniverse::sACNSentUniverse(int universe) : m_isSending(false), @@ -75,7 +74,6 @@ void sACNSentUniverse::startSending(bool preview) if (preview) options += PREVIEW_DATA_OPTION; - m_maxSendFreq = std::clamp( m_maxSendFreq, m_minSendFreq, @@ -371,7 +369,7 @@ CStreamServer::CStreamServer() : this->moveToThread(m_thread); m_thread->setObjectName(QString("Interface %1 TX").arg(m_sendsock->multicastInterface().name())); m_thread->start(); - m_thread->setPriority(QThread::LowestPriority); + m_thread->setPriority(tickLoopPriority); } CStreamServer::~CStreamServer() @@ -436,11 +434,12 @@ void CStreamServer::RemovePSeq(const CID &cid, quint16 universe) void CStreamServer::TickLoop() { qDebug() << "sACNSender" << QThread::currentThreadId() - << ": Starting with tickLoopInterval" - << std::chrono::duration_cast(tickLoopInterval).count() << "ms"; + << ": Starting with priority" + << this->thread()->priority(); + + assert(this->thread()->priority() <= QThread::LowestPriority); while (!m_thread_stop) { - QThread::usleep(std::chrono::duration_cast(tickLoopInterval).count()); QMutexLocker locker(&m_writeMutex); int valid_count = 0; From 68a6a477bc215cc7f05a3fe9f31aaab54892a694 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Mon, 21 Nov 2022 16:03:46 +0000 Subject: [PATCH 7/9] Updated E1.31:2016 6.6.2 (clause 1) logic Respect maximum FPS while still following standard requirements --- src/sacn/sacnsender.cpp | 48 +++++++++++++++++------------------------ src/sacn/sacnsender.h | 4 ++-- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index ff2b4625..86a4a163 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -460,38 +460,30 @@ void CStreamServer::TickLoop() DoDestruction(it->handle); } - //If valid, either a dirty, or send_interval will cause a send - if(it->psend && (it->isdirty || it->send_interval.Expired())) + //If valid, either a dirty, still about to supress rate, or send_interval will cause a send + if(it->psend && (it->isdirty || it->num_suppress_remaining || it->send_interval.Expired())) { // E1.31:2016 6.6.2 (clause 1) logic - quint8 sendCount = 1; - if(!it->isdirty && it->send_interval.Expired() && !it->suppresed && it->start_code == STARTCODE_DMX) - { - it->suppresed = true; - sendCount = 3; - } - else if (it->isdirty) - { - it->suppresed = false; - } + if(it->isdirty && it->start_code == STARTCODE_DMX) + it->num_suppress_remaining = 3; + else if(it->num_suppress_remaining) + --it->num_suppress_remaining; + + //Add the sequence number and send + quint8 *pseq = GetPSeq(it->cid, it->number); + SetStreamHeaderSequence(it->psend, *pseq, it->version == sACNProtocolDraft); + (*pseq)++; + + // Pathway Connectivity Secure DMX Protocol + if (it->version == sACNProtocolPathwaySecure) + PathwaySecure::ApplyStreamSecurity(it->psend, it->sendsize, it->cid, it->password); + + // Write to the wire + quint64 result = m_sendsock->writeDatagram( (char*)it->psend, it->sendsize, it->sendaddr, STREAM_IP_PORT); - for (decltype(sendCount) n = 0; n < sendCount; n++ ) + if(result!=it->sendsize) { - //Add the sequence number and send - quint8 *pseq = GetPSeq(it->cid, it->number); - SetStreamHeaderSequence(it->psend, *pseq, it->version == sACNProtocolDraft); - (*pseq)++; - - // Pathway Connectivity Secure DMX Protocol - if (it->version == sACNProtocolPathwaySecure) - PathwaySecure::ApplyStreamSecurity(it->psend, it->sendsize, it->cid, it->password); - - // Write to the wire - quint64 result = m_sendsock->writeDatagram( (char*)it->psend, it->sendsize, it->sendaddr, STREAM_IP_PORT); - if(result!=it->sendsize) - { - qDebug() << "Error sending datagram : " << m_sendsock->errorString(); - } + qDebug() << "Error sending datagram : " << m_sendsock->errorString(); } if(GetStreamTerminated(it->psend)) diff --git a/src/sacn/sacnsender.h b/src/sacn/sacnsender.h index 6d44232b..2b2389be 100644 --- a/src/sacn/sacnsender.h +++ b/src/sacn/sacnsender.h @@ -362,7 +362,7 @@ private slots: //If NULL, this is not an active universe (just a hole in the vector) uint sendsize; bool isdirty; - bool suppresed; //Transmission rate suppresed? + quint8 num_suppress_remaining; //Packets left before we enter Transmission rate supression (E1.31:2016 6.6.2 (clause 1) logic) ttimer send_interval; //Whether or not it's time to send a non-dirty packet ttimer min_interval; //Whether it's too soon to send a packet QHostAddress sendaddr; //The multicast address we're sending to @@ -372,7 +372,7 @@ private slots: //and the constructor universe():number(0),handle(0), num_terminates(0), psend(NULL),isdirty(false), - suppresed(false),version(sACNProtocolRelease), cid(), password("") {} + num_suppress_remaining(0),version(sACNProtocolRelease), cid(), password("") {} }; //The handle is the vector index From db986abfd5809a4a355f980aec2ab14f4f764f21 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Mon, 21 Nov 2022 21:32:38 +0000 Subject: [PATCH 8/9] Reduce sender CPU usage on Linux/macOS Threads works differently under Unix(alike), than under Windows; So also use a small sleep with tickLoop on these platforms --- src/sacn/sacnsender.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 86a4a163..271bc562 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -33,6 +33,12 @@ #include "preferences.h" +using namespace std::chrono_literals; +#if defined(Q_OS_UNIX) +constexpr auto tickLoopInterval = 1ms; +#elif defined(Q_OS_WIN) +constexpr auto tickLoopInterval = 0ms; // Don't use on Windows +#endif const auto tickLoopPriority = QThread::LowestPriority; sACNSentUniverse::sACNSentUniverse(int universe) : @@ -435,11 +441,16 @@ void CStreamServer::TickLoop() { qDebug() << "sACNSender" << QThread::currentThreadId() << ": Starting with priority" - << this->thread()->priority(); + << this->thread()->priority() + << " and tickLoopInterval" + << std::chrono::duration_cast(tickLoopInterval).count() << "ms"; - assert(this->thread()->priority() <= QThread::LowestPriority); + Q_ASSERT(this->thread()->priority() == tickLoopPriority); while (!m_thread_stop) { + if (tickLoopInterval.count()) + QThread::usleep(std::chrono::duration_cast(tickLoopInterval).count()); + QMutexLocker locker(&m_writeMutex); int valid_count = 0; From bf6757177533df27899fa4ff088f5b82e6dea585 Mon Sep 17 00:00:00 2001 From: Marcus Birkin Date: Tue, 22 Nov 2022 18:38:25 +0000 Subject: [PATCH 9/9] Remove valid_count left over from legacy streamserver --- src/sacn/sacnsender.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sacn/sacnsender.cpp b/src/sacn/sacnsender.cpp index 271bc562..db3b7f8b 100644 --- a/src/sacn/sacnsender.cpp +++ b/src/sacn/sacnsender.cpp @@ -453,12 +453,8 @@ void CStreamServer::TickLoop() QMutexLocker locker(&m_writeMutex); - int valid_count = 0; for(auto it = m_multiverse.begin(); it != m_multiverse.end(); ++it) { - if(it->psend) - ++valid_count; - //Too soon to send? //E1.31:2016 6.6.1 if (!it->min_interval.Expired())