From 2db5156a191bf25a3dbd47e8f02b887516f0127a Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 21 Mar 2022 23:00:19 +0000 Subject: [PATCH 1/2] Add rate-limiting for channel gain change messages This avoids a backlog of messages being queued due to ACK latency, particularly if using a MIDI controller that send fine-grained level changes. After sending a gain change message, further gain changes will be updated locally for 300ms and then the latest value sent to the server. --- src/client.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/client.h | 13 +++++++++ 2 files changed, 88 insertions(+) diff --git a/src/client.cpp b/src/client.cpp index 203b1a0f36..6ef294e526 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -173,6 +173,11 @@ CClient::CClient ( const quint16 iPortNumber, // start timer so that elapsed time works PreciseTime.start(); + // set gain delay timer to single-shot and connect handler function + TimerGain.setSingleShot ( true ); + + QObject::connect ( &TimerGain, &QTimer::timeout, this, &CClient::OnTimerRemoteChanGain ); + // start the socket (it is important to start the socket after all // initializations and connections) Socket.Start(); @@ -335,15 +340,85 @@ void CClient::SetDoAutoSockBufSize ( const bool bValue ) CreateServerJitterBufferMessage(); } +// In order not to flood the server with gain change messages, particularly when using +// a MIDI controller, a timer is used to limit the rate at which such messages are generated. +// This avoids a potential long backlog of messages, since each must be ACKed before the next +// can be sent, and this ACK is subject to the latency of the server connection. +// +// When the first gain change message is requested after an idle period (i.e. the timer is not +// running), it will be sent immediately, and a 300ms timer started. +// +// If a gain change message is requested while the timer is still running, the new gain is not sent, +// but just stored in newGain[iId], and the minGainId and maxGainId updated to note the range of +// IDs that must be checked when the time expires (this will usually be a single channel +// unless channel grouping is being used). This avoids having to check all possible channels. +// +// When the timer fires, the channels minGainId <= iId < maxGainId are checked by comparing +// the last sent value in oldGain[iId] with any pending value in newGain[iId], and if they differ, +// the new value is sent, updating oldGain[iId] with the sent value. If any new values are +// sent, the timer is restarted so that further immediate updates will be pended. + void CClient::SetRemoteChanGain ( const int iId, const float fGain, const bool bIsMyOwnFader ) { + QMutexLocker locker ( &MutexGain ); + // if this gain is for my own channel, apply the value for the Mute Myself function if ( bIsMyOwnFader ) { fMuteOutStreamGain = fGain; } + if ( TimerGain.isActive() ) + { + // just update the new value for sending later; + // will compare with oldGain[iId] when the timer fires + newGain[iId] = fGain; + + // update range of channel IDs to check in the timer + if ( iId < minGainId ) + minGainId = iId; // first value to check + if ( iId >= maxGainId ) + maxGainId = iId + 1; // first value NOT to check + + return; + } + + // here the timer was not active: + // send the actual gain and reset the range of channel IDs to empty + oldGain[iId] = newGain[iId] = fGain; Channel.SetRemoteChanGain ( iId, fGain ); + + maxGainId = 0; + minGainId = MAX_NUM_CHANNELS; + + // start timer to delay sending further updates + TimerGain.start ( GAIN_DELAY_PERIOD_MS ); +} + +void CClient::OnTimerRemoteChanGain() +{ + QMutexLocker locker ( &MutexGain ); + bool bSent = false; + + for ( int iId = minGainId; iId < maxGainId; iId++ ) + { + if ( newGain[iId] != oldGain[iId] ) + { + // send new gain and record as old gain + float fGain = oldGain[iId] = newGain[iId]; + Channel.SetRemoteChanGain ( iId, fGain ); + bSent = true; + } + } + + // if a new gain has been sent, reset the range of channel IDs to empty and start timer + if ( bSent ) + { + maxGainId = 0; + minGainId = MAX_NUM_CHANNELS; + + TimerGain.start ( GAIN_DELAY_PERIOD_MS ); + } } bool CClient::SetServerAddr ( QString strNAddr ) diff --git a/src/client.h b/src/client.h index e32772a960..f0eb740dbd 100644 --- a/src/client.h +++ b/src/client.h @@ -73,6 +73,9 @@ // audio reverberation range #define AUD_REVERB_MAX 100 +// delay period between successive gain updates (ms) +#define GAIN_DELAY_PERIOD_MS 300 + // OPUS number of coded bytes per audio packet // TODO we have to use new numbers for OPUS to avoid that old CELT packets // are used in the OPUS decoder (which gives a bad noise output signal). @@ -237,6 +240,8 @@ class CClient : public QObject void SetRemoteChanGain ( const int iId, const float fGain, const bool bIsMyOwnFader ); + void OnTimerRemoteChanGain(); + void SetRemoteChanPan ( const int iId, const float fPan ) { Channel.SetRemoteChanPan ( iId, fPan ); } void SetInputBoost ( const int iNewBoost ) { iInputBoost = iNewBoost; } @@ -354,6 +359,14 @@ class CClient : public QObject // for ping measurement QElapsedTimer PreciseTime; + // for gain rate limiting + QMutex MutexGain; + QTimer TimerGain; + int minGainId; + int maxGainId; + float oldGain[MAX_NUM_CHANNELS]; + float newGain[MAX_NUM_CHANNELS]; + CSignalHandler* pSignalHandler; protected slots: From 68d56127275687a892157fd2594147087a3c0698 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 23 Mar 2022 13:19:49 +0000 Subject: [PATCH 2/2] Improve delay time based on ping to server --- src/client.cpp | 30 ++++++++++++++++++++++-------- src/client.h | 8 +++++--- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 6ef294e526..a1b34a8eeb 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -304,6 +304,8 @@ void CClient::OnCLPingReceived ( CHostAddress InetAddr, int iMs ) const int iCurDiff = EvaluatePingMessage ( iMs ); if ( iCurDiff >= 0 ) { + iCurPingTime = iCurDiff; // store for use by gain message sending + emit PingTimeReceived ( iCurDiff ); } } @@ -388,11 +390,7 @@ void CClient::SetRemoteChanGain ( const int iId, const float fGain, const bool b oldGain[iId] = newGain[iId] = fGain; Channel.SetRemoteChanGain ( iId, fGain ); - maxGainId = 0; - minGainId = MAX_NUM_CHANNELS; - - // start timer to delay sending further updates - TimerGain.start ( GAIN_DELAY_PERIOD_MS ); + StartDelayTimer(); } void CClient::OnTimerRemoteChanGain() @@ -414,10 +412,26 @@ void CClient::OnTimerRemoteChanGain() // if a new gain has been sent, reset the range of channel IDs to empty and start timer if ( bSent ) { - maxGainId = 0; - minGainId = MAX_NUM_CHANNELS; + StartDelayTimer(); + } +} + +// reset the range of channel IDs to check and start the delay timer +void CClient::StartDelayTimer() +{ + maxGainId = 0; + minGainId = MAX_NUM_CHANNELS; - TimerGain.start ( GAIN_DELAY_PERIOD_MS ); + // start timer to delay sending further updates + // use longer delay when connected to server with higher ping time, + // double the ping time in order to allow a bit of overhead for other messages + if ( iCurPingTime < DEFAULT_GAIN_DELAY_PERIOD_MS / 2 ) + { + TimerGain.start ( DEFAULT_GAIN_DELAY_PERIOD_MS ); + } + else + { + TimerGain.start ( iCurPingTime * 2 ); } } diff --git a/src/client.h b/src/client.h index f0eb740dbd..cdb21f6e6e 100644 --- a/src/client.h +++ b/src/client.h @@ -73,8 +73,9 @@ // audio reverberation range #define AUD_REVERB_MAX 100 -// delay period between successive gain updates (ms) -#define GAIN_DELAY_PERIOD_MS 300 +// default delay period between successive gain updates (ms) +// this will be increased to double the ping time if connected to a distant server +#define DEFAULT_GAIN_DELAY_PERIOD_MS 50 // OPUS number of coded bytes per audio packet // TODO we have to use new numbers for OPUS to avoid that old CELT packets @@ -239,8 +240,8 @@ class CClient : public QObject void SetMuteOutStream ( const bool bDoMute ) { bMuteOutStream = bDoMute; } void SetRemoteChanGain ( const int iId, const float fGain, const bool bIsMyOwnFader ); - void OnTimerRemoteChanGain(); + void StartDelayTimer(); void SetRemoteChanPan ( const int iId, const float fPan ) { Channel.SetRemoteChanPan ( iId, fPan ); } @@ -366,6 +367,7 @@ class CClient : public QObject int maxGainId; float oldGain[MAX_NUM_CHANNELS]; float newGain[MAX_NUM_CHANNELS]; + int iCurPingTime; CSignalHandler* pSignalHandler;