From 6a64db402c1918c32facb8f065c55d652463a02a Mon Sep 17 00:00:00 2001 From: ann0see <20726856+ann0see@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:25:00 +0200 Subject: [PATCH 1/2] Replace per-pair gain/pan mutex locking with compacted fill Acquire the channel mutex once per channel and copy the gain/pan values of all connected channels (compacted into the caller's channel order, with bounds guard) instead of acquiring it twice per channel pair (O(N^2) lock/unlock operations per server frame). The values are written directly into vecvecfGains/vecvecfPannings, no intermediate snapshot buffers are needed. Drop the now unused CChannel::GetGain()/GetPan(). Co-authored-by: mcfnord --- src/channel.cpp | 44 +++++++++++++++++++++----------------------- src/channel.h | 6 +++--- src/server.cpp | 18 ++++++++---------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/channel.cpp b/src/channel.cpp index d0afcab64e..98be3de26e 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -315,21 +315,6 @@ void CChannel::SetGain ( const int iChanID, const float fNewGain ) } } -float CChannel::GetGain ( const int iChanID ) -{ - QMutexLocker locker ( &Mutex ); - - // get value (make sure channel ID is in range) - if ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) ) - { - return vecfGains[iChanID]; - } - else - { - return 0; - } -} - void CChannel::SetPan ( const int iChanID, const float fNewPan ) { QMutexLocker locker ( &Mutex ); @@ -342,18 +327,31 @@ void CChannel::SetPan ( const int iChanID, const float fNewPan ) } } -float CChannel::GetPan ( const int iChanID ) +/// @brief Copies a consistent snapshot of gain and panning settings for the requested channel IDs under Mutex. +/// @param vecChanIDs Channel IDs to read, in the order used for the output vectors. +/// @param iNumClients Number of leading entries to read and populate. All three vectors must contain at least this many entries. +/// @param vecGains Receives each requested channel's gain, or zero for an out-of-range channel ID. +/// @param vecPannings Receives each requested channel's panning, or zero for an out-of-range channel ID. +void CChannel::GetGainsAndPannings ( const CVector& vecChanIDs, const int iNumClients, CVector& vecGains, CVector& vecPannings ) { QMutexLocker locker ( &Mutex ); - // get value (make sure channel ID is in range) - if ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) ) + // copy the gain and pan values of the connected channels + for ( int j = 0; j < iNumClients; j++ ) { - return vecfPannings[iChanID]; - } - else - { - return 0; + const int iChanID = vecChanIDs[j]; + + if ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) ) + { + vecGains[j] = vecfGains[iChanID]; + vecPannings[j] = vecfPannings[iChanID]; + } + else + { + // should not happen + vecGains[j] = 0; + vecPannings[j] = 0; + } } } diff --git a/src/channel.h b/src/channel.h index f924daa034..18b824415d 100644 --- a/src/channel.h +++ b/src/channel.h @@ -121,11 +121,11 @@ class CChannel : public QObject void CreateMuteStateHasChangedMes ( const int iChanID, const bool bIsMuted ) { Protocol.CreateMuteStateHasChangedMes ( iChanID, bIsMuted ); } void SetGain ( const int iChanID, const float fNewGain ); - float GetGain ( const int iChanID ); float GetFadeInGain() { return static_cast ( iFadeInCnt ) / iFadeInCntMax; } - void SetPan ( const int iChanID, const float fNewPan ); - float GetPan ( const int iChanID ); + void SetPan ( const int iChanID, const float fNewPan ); + + void GetGainsAndPannings ( const CVector& vecChanIDs, const int iNumClients, CVector& vecGains, CVector& vecPannings ); void SetRemoteChanGain ( const int iId, const float fGain ) { Protocol.CreateChanGainMes ( iId, fGain ); } diff --git a/src/server.cpp b/src/server.cpp index a49eab776c..296e9b1974 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -896,15 +896,16 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) CurOpusDecoder = nullptr; } - // get gains of all connected channels + // get gains and pannings of all connected channels, compacted to the + // order of "vecChanIDsCurConChan". + // The second index of "vecvecdGains" does not represent + // the channel ID! Therefore we have to use + // "vecChanIDsCurConChan" to query the IDs of the currently + // connected channels + vecChannels[iCurChanID].GetGainsAndPannings ( vecChanIDsCurConChan, iNumClients, vecvecfGains[iChanCnt], vecvecfPannings[iChanCnt] ); + for ( int j = 0; j < iNumClients; j++ ) { - // The second index of "vecvecdGains" does not represent - // the channel ID! Therefore we have to use - // "vecChanIDsCurConChan" to query the IDs of the currently - // connected channels - vecvecfGains[iChanCnt][j] = vecChannels[iCurChanID].GetGain ( vecChanIDsCurConChan[j] ); - // consider audio fade-in vecvecfGains[iChanCnt][j] *= vecChannels[vecChanIDsCurConChan[j]].GetFadeInGain(); @@ -914,9 +915,6 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) { vecvecfGains[iChanCnt][j] *= vecChannels[iCurChanID].GetFadeInGain(); } - - // panning - vecvecfPannings[iChanCnt][j] = vecChannels[iCurChanID].GetPan ( vecChanIDsCurConChan[j] ); } // If the server frame size is smaller than the received OPUS frame size, we need a conversion From df07df67b4acd74c5a9a071ee4bef22b63da81d2 Mon Sep 17 00:00:00 2001 From: ann0see <20726856+ann0see@users.noreply.github.com> Date: Mon, 14 Sep 2026 20:13:38 +0200 Subject: [PATCH 2/2] Fix typo --- src/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.cpp b/src/server.cpp index 296e9b1974..d24e5a142d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -898,7 +898,7 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) // get gains and pannings of all connected channels, compacted to the // order of "vecChanIDsCurConChan". - // The second index of "vecvecdGains" does not represent + // The second index of "vecvecfGains" does not represent // the channel ID! Therefore we have to use // "vecChanIDsCurConChan" to query the IDs of the currently // connected channels