diff --git a/src/channel.h b/src/channel.h index 25988d15..2b3086f0 100755 --- a/src/channel.h +++ b/src/channel.h @@ -168,6 +168,9 @@ public: bool ChannelLevelsRequired() const { return bChannelLevelsRequired; } + double GetPrevLevel() const { return dPrevLevel; } + void SetPrevLevel ( const double nPL ) { dPrevLevel = nPL; } + protected: bool ProtocolIsEnabled(); @@ -180,6 +183,8 @@ protected: iNetwFrameSizeFact = FRAME_SIZE_FACTOR_PREFERRED; iNetwFrameSize = CELT_MINIMUM_NUM_BYTES; iNumAudioChannels = 1; // mono + + dPrevLevel = 0.0; } // connection parameters @@ -220,6 +225,7 @@ protected: QMutex MutexConvBuf; bool bChannelLevelsRequired; + double dPrevLevel; public slots: void OnSendProtMessage ( CVector vecMessage ); diff --git a/src/global.h b/src/global.h index 68a2880d..a1baa8a7 100755 --- a/src/global.h +++ b/src/global.h @@ -199,6 +199,9 @@ LED bar: lbr // list #define PING_UPDATE_TIME_SERVER_LIST_MS 2000 // ms +// defines the interval between Channel Level updates from the server +#define CHANNEL_LEVEL_UPDATE_INTERVAL 100 // number of frames + // time-out until a registered server is deleted from the server list if no // new registering was made in minutes #define SERVLIST_TIME_OUT_MINUTES 60 // minutes diff --git a/src/protocol.cpp b/src/protocol.cpp index 0dce5f30..f9c37aa5 100755 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -2039,6 +2039,11 @@ bool CProtocol::EvaluateCLChannelLevelListMes ( const CHostAddress& InetAdd // may have one too many entries, last being 0xF int iVecLen = iDataLen * 2; // one ushort per channel + if ( iVecLen > MAX_NUM_CHANNELS ) + { + return true; // return error code + } + CVector vecLevelList ( iVecLen ); for (int i = 0, j = 0; i < iDataLen; i++, j += 2 ) diff --git a/src/server.cpp b/src/server.cpp index d759bd6d..3a6192f1 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -335,6 +335,8 @@ CServer::CServer ( const int iNewMaxNumChan, // allocate worst case memory for the coded data vecbyCodedData.Init ( MAX_SIZE_BYTES_NETW_BUF ); + // allocate worst case memory for the channel levels + vecChannelLevels.Init ( iMaxNumChannels ); // enable history graph (if requested) if ( !strHistoryFileName.isEmpty() ) @@ -864,6 +866,7 @@ JitterMeas.Measure(); // some inits int iNumClients = 0; // init connected client counter bool bChannelIsNowDisconnected = false; + bool bSendChannelLevels = false; // Make put and get calls thread safe. Do not forget to unlock mutex // afterwards! @@ -1007,6 +1010,29 @@ JitterMeas.Measure(); // one client is connected. if ( iNumClients > 0 ) { + + // Low frequency updates + if ( iFrameCount > CHANNEL_LEVEL_UPDATE_INTERVAL ) + { + iFrameCount = 0; + + // Calculate channel levels if any client has requested them + for ( int i = 0; i < iNumClients; i++ ) + { + if ( vecChannels[ vecChanIDsCurConChan[i] ].ChannelLevelsRequired() ) + { + bSendChannelLevels = true; + + CreateLevelsForAllConChannels ( iNumClients, + vecNumAudioChannels, + vecvecsData, + vecChannelLevels ); + break; + } + } + } + iFrameCount++; + for ( int i = 0; i < iNumClients; i++ ) { // get actual ID of current channel @@ -1095,6 +1121,12 @@ opus_custom_encoder_ctl ( CurOpusEncoder, // update socket buffer size vecChannels[iCurChanID].UpdateSocketBufferSize(); + + // send channel levels + if ( bSendChannelLevels && vecChannels[iCurChanID].ChannelLevelsRequired() ) + { + ConnLessProtocol.CreateCLChannelLevelListMes ( vecChannels[iCurChanID].GetAddress(), vecChannelLevels, iNumClients ); + } } } else @@ -1574,3 +1606,69 @@ void CServer::customEvent ( QEvent* pEvent ) } } } + +/// @brief Compute frame peak level for each client +void CServer::CreateLevelsForAllConChannels ( const int iNumClients, + const CVector& vecNumAudioChannels, + const CVector > vecvecsData, + CVector& vecLevelsOut ) +{ + int i, j, k; + + // init return vector with zeros since we mix all channels on that vector + vecLevelsOut.Reset ( 0 ); + + for ( j = 0; j < iNumClients; j++ ) + { + // get a reference to the audio data + const CVector& vecsData = vecvecsData[j]; + + double dCurLevel = 0.0; + if ( vecNumAudioChannels[j] == 1 ) + { + // mono + for ( i = 0; i < SYSTEM_FRAME_SIZE_SAMPLES; i += 3 ) + { + dCurLevel = std::max ( dCurLevel, std::abs ( static_cast ( vecsData[i] ) ) ); + } + } + else + { + // stereo: apply stereo-to-mono attenuation + for ( i = 0, k = 0; i < SYSTEM_FRAME_SIZE_SAMPLES; i += 3, k += 6 ) + { + double sMix = ( static_cast ( vecsData[k] ) + vecsData[k + 1] ) / 2; + dCurLevel = std::max ( dCurLevel, std::abs ( sMix ) ); + } + } + + // smoothing + int iChId = vecChanIDsCurConChan [ j ]; + dCurLevel = std::max ( dCurLevel, vecChannels[ iChId ].GetPrevLevel() * 0.5 ); + vecChannels[ iChId ].SetPrevLevel ( dCurLevel ); + + // logarithmic measure + const double dNormChanLevel = dCurLevel / _MAXSHORT; + double dCurSigLevel; + if ( dNormChanLevel > 0 ) + { + dCurSigLevel = 20.0 * log10 ( dNormChanLevel ); + } + else + { + dCurSigLevel = -100000.0; // large negative value + } + + // map to signal level meter + dCurSigLevel -= LOW_BOUND_SIG_METER; + dCurSigLevel *= NUM_STEPS_LED_BAR / + ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ); + + if ( dCurSigLevel < 0 ) + { + dCurSigLevel = 0; + } + + vecLevelsOut[j] = static_cast ( ceil ( dCurSigLevel ) ); + } +} diff --git a/src/server.h b/src/server.h index 6494bd50..e04436e0 100755 --- a/src/server.h +++ b/src/server.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef USE_OPUS_SHARED_LIB # include "opus/opus_custom.h" #else @@ -225,6 +226,11 @@ protected: // if server mode is normal or double system frame size bool bUseDoubleSystemFrameSize; + void CreateLevelsForAllConChannels ( const int iNumClients, + const CVector& vecNumAudioChannels, + const CVector > vecvecsData, + CVector& vecLevelsOut ); + // do not use the vector class since CChannel does not have appropriate // copy constructor/operator CChannel vecChannels[MAX_NUM_CHANNELS]; @@ -255,12 +261,18 @@ protected: CVector vecsSendData; CVector vecbyCodedData; + // Channel levels + CVector vecChannelLevels; + // actual working objects CHighPrioSocket Socket; // logging CServerLogging Logging; + // channel level update frame interval counter + uint16_t iFrameCount; + // recording thread recorder::CJamRecorder JamRecorder; bool bEnableRecording;