diff --git a/src/channel.cpp b/src/channel.cpp index a2427662..b4146876 100755 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -1,686 +1,691 @@ -/******************************************************************************\ - * Copyright (c) 2004-2011 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * -\******************************************************************************/ - -#include "channel.h" - - -// CChannel implementation ***************************************************** -CChannel::CChannel ( const bool bNIsServer ) : - vecdGains ( MAX_NUM_CHANNELS, (double) 1.0 ), - bDoAutoSockBufSize ( true ), - bIsEnabled ( false ), - bIsServer ( bNIsServer ), - iNetwFrameSizeFact ( FRAME_SIZE_FACTOR_PREFERRED ), - iNetwFrameSize ( 20 ), // must be > 0 and should be close to a valid size - iNumAudioChannels ( 1 ) // mono -{ - // initial value for connection time out counter, we calculate the total - // number of samples here and subtract the number of samples of the block - // which we take out of the buffer to be independent of block sizes - iConTimeOutStartVal = CON_TIME_OUT_SEC_MAX * SYSTEM_SAMPLE_RATE_HZ; - - // init time-out for the buffer with zero -> no connection - iConTimeOut = 0; - - // init the socket buffer - SetSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ); - - // initialize cycle time variance measurement with defaults - CycleTimeVariance.Init ( SYSTEM_FRAME_SIZE_SAMPLES, - SYSTEM_SAMPLE_RATE_HZ, TIME_MOV_AV_RESPONSE_SECONDS ); - - // initialize channel name - ResetName(); - - - // Connections ------------------------------------------------------------- - QObject::connect ( &Protocol, - SIGNAL ( MessReadyForSending ( CVector ) ), - this, SLOT ( OnSendProtMessage ( CVector ) ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ChangeJittBufSize ( int ) ), - this, SLOT ( OnJittBufSizeChange ( int ) ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ReqJittBufSize() ), - SIGNAL ( ReqJittBufSize() ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ReqChanName() ), - SIGNAL ( ReqChanName() ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ReqConnClientsList() ), - SIGNAL ( ReqConnClientsList() ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ConClientListMesReceived ( CVector ) ), - SIGNAL ( ConClientListMesReceived ( CVector ) ) ); - - QObject::connect( &Protocol, SIGNAL ( ChangeChanGain ( int, double ) ), - this, SLOT ( OnChangeChanGain ( int, double ) ) ); - - QObject::connect( &Protocol, SIGNAL ( ChangeChanName ( QString ) ), - this, SLOT ( OnChangeChanName ( QString ) ) ); - - QObject::connect( &Protocol, - SIGNAL ( ChatTextReceived ( QString ) ), - SIGNAL ( ChatTextReceived ( QString ) ) ); - - QObject::connect ( &Protocol, - SIGNAL ( DetectedCLMessage ( CVector, int ) ), - SIGNAL ( DetectedCLMessage ( CVector, int ) ) ); - - QObject::connect ( &Protocol, - SIGNAL ( NetTranspPropsReceived ( CNetworkTransportProps ) ), - this, SLOT ( OnNetTranspPropsReceived ( CNetworkTransportProps ) ) ); - - QObject::connect ( &Protocol, - SIGNAL ( ReqNetTranspProps() ), - this, SLOT ( OnReqNetTranspProps() ) ); -} - -bool CChannel::ProtocolIsEnabled() -{ - // for the server, only enable protocol if the channel is connected, i.e., - // successfully audio packets are received from a client - // for the client, enable protocol if the channel is enabled, i.e., the - // connection button was hit by the user - if ( bIsServer ) - { - return IsConnected(); - } - else - { - return bIsEnabled; - } -} - -void CChannel::SetEnable ( const bool bNEnStat ) -{ - QMutexLocker locker ( &Mutex ); - - // set internal parameter - bIsEnabled = bNEnStat; - - // if channel is not enabled, reset time out count and protocol - if ( !bNEnStat ) - { - iConTimeOut = 0; - Protocol.Reset(); - } -} - -void CChannel::SetAudioStreamProperties ( const int iNewNetwFrameSize, - const int iNewNetwFrameSizeFact, - const int iNewNumAudioChannels ) -{ - // this function is intended for the server (not the client) - QMutexLocker locker ( &Mutex ); - - // store new values - iNumAudioChannels = iNewNumAudioChannels; - iNetwFrameSize = iNewNetwFrameSize; - iNetwFrameSizeFact = iNewNetwFrameSizeFact; - - // init socket buffer - SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames ); - - // init conversion buffer - ConvBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); - - // reset cycle time variance measurement - CycleTimeVariance.Reset(); - - // tell the server that audio coding has changed - CreateNetTranspPropsMessFromCurrentSettings(); -} - -bool CChannel::SetSockBufNumFrames ( const int iNewNumFrames, - const bool bPreserve ) -{ - QMutexLocker locker ( &Mutex ); // this operation must be done with mutex - - // first check for valid input parameter range - if ( ( iNewNumFrames >= MIN_NET_BUF_SIZE_NUM_BL ) && - ( iNewNumFrames <= MAX_NET_BUF_SIZE_NUM_BL ) ) - { - // only apply parameter if new parameter is different from current one - if ( iCurSockBufNumFrames != iNewNumFrames ) - { - // store new value - iCurSockBufNumFrames = iNewNumFrames; - - // the network block size is a multiple of the minimum network - // block size - SockBuf.Init ( iNetwFrameSize, iNewNumFrames, bPreserve ); - - // only in case we are the server and auto jitter buffer setting is - // enabled, we have to report the current setting to the client - if ( bIsServer && bDoAutoSockBufSize ) - { - // we cannot call the "CreateJitBufMes" function directly since - // this would give us problems with different threads (e.g. the - // timer thread) and the protocol mechanism (problem with - // qRegisterMetaType(), etc.) - emit ServerAutoSockBufSizeChange ( iNewNumFrames ); - } - - return false; // -> no error - } - } - - return true; // set error flag -} - -void CChannel::SetDoAutoSockBufSize ( const bool bValue ) -{ - QMutexLocker locker ( &Mutex ); - - // only act on new value if it is different from the current one - if ( bDoAutoSockBufSize != bValue ) - { - if ( bValue ) - { - // in case auto socket buffer size was just enabled, reset statistic - CycleTimeVariance.Reset(); - } - - // store new setting - bDoAutoSockBufSize = bValue; - } -} - -void CChannel::SetGain ( const int iChanID, - const double dNewGain ) -{ - QMutexLocker locker ( &Mutex ); - - // set value (make sure channel ID is in range) - if ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) ) - { - vecdGains[iChanID] = dNewGain; - } -} - -double 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 vecdGains[iChanID]; - } - else - { - return 0; - } -} - -void CChannel::SetName ( const QString strNewName ) -{ - bool bNameHasChanged = false; - - Mutex.lock(); - { - // apply value (if different from previous name) - if ( sName.compare ( strNewName ) ) - { - sName = strNewName; - bNameHasChanged = true; - } - } - Mutex.unlock(); - - // fire message that name has changed - if ( bNameHasChanged ) - { - // the "emit" has to be done outside the mutexed region - emit NameHasChanged(); - } -} -QString CChannel::GetName() -{ - // make sure the string is not written at the same time when it is - // read here -> use mutex to secure access - QMutexLocker locker ( &Mutex ); - - return sName; -} - -void CChannel::OnSendProtMessage ( CVector vecMessage ) -{ - // only send messages if protocol is enabled, otherwise delete complete - // queue - if ( ProtocolIsEnabled() ) - { - // emit message to actually send the data - emit MessReadyForSending ( vecMessage ); - } - else - { - // delete send message queue - Protocol.Reset(); - } -} - -void CChannel::OnJittBufSizeChange ( int iNewJitBufSize ) -{ - // for server apply setting, for client emit message - if ( bIsServer ) - { - // first check for special case: auto setting - if ( iNewJitBufSize == AUTO_NET_BUF_SIZE_FOR_PROTOCOL ) - { - SetDoAutoSockBufSize ( true ); - } - else - { - // manual setting is received, turn OFF auto setting and apply new value - SetDoAutoSockBufSize ( false ); - SetSockBufNumFrames ( iNewJitBufSize, true ); - } - } - else - { - emit JittBufSizeChanged ( iNewJitBufSize ); - } -} - -void CChannel::OnChangeChanGain ( int iChanID, - double dNewGain ) -{ - SetGain ( iChanID, dNewGain ); -} - -void CChannel::OnChangeChanName ( QString strName ) -{ - SetName ( strName ); -} - -bool CChannel::GetAddress ( CHostAddress& RetAddr ) -{ - QMutexLocker locker ( &Mutex ); - - if ( IsConnected() ) - { - RetAddr = InetAddr; - return true; - } - else - { - RetAddr = CHostAddress(); - return false; - } -} - -void CChannel::OnNetTranspPropsReceived ( CNetworkTransportProps NetworkTransportProps ) -{ - // only the server shall act on network transport properties message - if ( bIsServer ) - { - QMutexLocker locker ( &Mutex ); - - // store received parameters - iNumAudioChannels = NetworkTransportProps.iNumAudioChannels; - iNetwFrameSizeFact = NetworkTransportProps.iBlockSizeFact; - iNetwFrameSize = - NetworkTransportProps.iBaseNetworkPacketSize; - - // update socket buffer (the network block size is a multiple of the - // minimum network frame size - SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames ); - - // init conversion buffer - ConvBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); - - // reset statistic - CycleTimeVariance.Reset(); - } -} - -void CChannel::OnReqNetTranspProps() -{ - CreateNetTranspPropsMessFromCurrentSettings(); -} - -void CChannel::CreateNetTranspPropsMessFromCurrentSettings() -{ - CNetworkTransportProps NetworkTransportProps ( - iNetwFrameSize, - iNetwFrameSizeFact, - iNumAudioChannels, - SYSTEM_SAMPLE_RATE_HZ, - CT_CELT, // always CELT coding - 0, // version of the codec - 0 ); - - // send current network transport properties - Protocol.CreateNetwTranspPropsMes ( NetworkTransportProps ); -} - -void CChannel::Disconnect() -{ - // we only have to disconnect the channel if it is actually connected - if ( IsConnected() ) - { - // set time out counter to a small value > 0 so that the next time a - // received audio block is queried, the disconnection is performed - // (assuming that no audio packet is received in the meantime) - iConTimeOut = 1; // a small number > 0 - } -} - -EPutDataStat CChannel::PutData ( const CVector& vecbyData, - int iNumBytes ) -{ - EPutDataStat eRet = PS_GEN_ERROR; - - // init flags - bool bIsProtocolPacket = false; - bool bIsAudioPacket = false; - bool bNewConnection = false; - - if ( bIsEnabled ) - { - // first check if this is protocol data - // only use protocol data if protocol mechanism is enabled - if ( ProtocolIsEnabled() ) - { - // parse the message assuming this is a protocol message - if ( !Protocol.ParseMessage ( vecbyData, iNumBytes ) ) - { - // set status flags - eRet = PS_PROT_OK; - bIsProtocolPacket = true; - } - } - else - { - // In case we are the server and the current channel is not - // connected, we do not evaluate protocal messages but these - // messages could start the server which is not desired, especially - // not for the disconnect messages. - // We now do not start the server if a valid protocol message - // was received but only start the server on audio packets - if ( Protocol.IsProtocolMessage ( vecbyData, iNumBytes ) ) - { - // set status flags - eRet = PS_PROT_OK_MESS_NOT_EVALUATED; - bIsProtocolPacket = true; - } - } - - // only try to parse audio if it was not a protocol packet - if ( !bIsProtocolPacket ) - { - Mutex.lock(); - { - - -// TODO only process data if network properties protocol message has been arrived - - - // only process audio if packet has correct size - if ( iNumBytes == ( iNetwFrameSize * iNetwFrameSizeFact ) ) - { - // set audio packet flag - bIsAudioPacket = true; - - // store new packet in jitter buffer - if ( SockBuf.Put ( vecbyData, iNumBytes ) ) - { - eRet = PS_AUDIO_OK; - } - else - { - eRet = PS_AUDIO_ERR; - } - - // update cycle time variance measurement (this is only - // used in case auto socket buffer size is enabled) - if ( bDoAutoSockBufSize ) - { - CycleTimeVariance.Update(); - } - } - else - { - // the protocol parsing failed and this was no audio block, - // we treat this as protocol error (unkown packet) - eRet = PS_PROT_ERR; - } - - // all network packets except of valid llcon protocol messages - // regardless if they are valid or invalid audio packets lead to - // a state change to a connected channel - // this is because protocol messages can only be sent on a - // connected channel and the client has to inform the server - // about the audio packet properties via the protocol - - // check if channel was not connected, this is a new connection - bNewConnection = !IsConnected(); - - // reset time-out counter - ResetTimeOutCounter(); - } - Mutex.unlock(); - } - - if ( bNewConnection ) - { - // if this is a new connection and the current network packet is - // neither an audio or protocol packet, we have to query the - // network transport properties for the audio packets - // (this is only required for server since we defined that the - // server has to send with the same properties as sent by - // the client) - -// TODO check the conditions: !bIsProtocolPacket should always be true -// since we can only get here if bNewConnection, should we really put -// !bIsAudioPacket in here, because shouldn't we always query the audio -// properties on a new connection? - - if ( bIsServer && ( !bIsProtocolPacket ) && ( !bIsAudioPacket ) ) - { - Protocol.CreateReqNetwTranspPropsMes(); - } - - // reset cycle time variance measurement - CycleTimeVariance.Reset(); - - // inform other objects that new connection was established - emit NewConnection(); - } - } - - return eRet; -} - -EGetDataStat CChannel::GetData ( CVector& vecbyData ) -{ - QMutexLocker locker ( &Mutex ); - - EGetDataStat eGetStatus; - - const bool bSockBufState = SockBuf.Get ( vecbyData ); - - // decrease time-out counter - if ( iConTimeOut > 0 ) - { - // subtract the number of samples of the current block since the - // time out counter is based on samples not on blocks (definition: - // always one atomic block is get by using the GetData() function - // where the atomic block size is "SYSTEM_FRAME_SIZE_SAMPLES") - -// TODO this code only works with the above assumption -> better -// implementation so that we are not depending on assumptions - - iConTimeOut -= SYSTEM_FRAME_SIZE_SAMPLES; - - if ( iConTimeOut <= 0 ) - { - // channel is just disconnected - eGetStatus = GS_CHAN_NOW_DISCONNECTED; - iConTimeOut = 0; // make sure we do not have negative values - - // emit message - emit Disconnected(); - } - else - { - if ( bSockBufState ) - { - // everything is ok - eGetStatus = GS_BUFFER_OK; - } - else - { - // channel is not yet disconnected but no data in buffer - eGetStatus = GS_BUFFER_UNDERRUN; - } - } - } - else - { - // channel is disconnected - eGetStatus = GS_CHAN_NOT_CONNECTED; - } - - return eGetStatus; -} - -CVector CChannel::PrepSendPacket ( const CVector& vecbyNPacket ) -{ - QMutexLocker locker ( &Mutex ); - - // if the block is not ready we have to initialize with zero length to - // tell the following network send routine that nothing should be sent - CVector vecbySendBuf ( 0 ); - - // use conversion buffer to convert sound card block size in network - // block size - if ( ConvBuf.Put ( vecbyNPacket ) ) - { - // a packet is ready - vecbySendBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); - vecbySendBuf = ConvBuf.Get(); - } - - return vecbySendBuf; -} - -int CChannel::GetUploadRateKbps() -{ - const int iAudioSizeOut = iNetwFrameSizeFact * SYSTEM_FRAME_SIZE_SAMPLES; - - // we assume that the UDP packet which is transported via IP has an - // additional header size of - // 8 (UDP) + 20 (IP without optional fields) = 28 bytes - return ( iNetwFrameSize * iNetwFrameSizeFact + 28 /* header */ ) * - 8 /* bits per byte */ * - SYSTEM_SAMPLE_RATE_HZ / iAudioSizeOut / 1000; -} - -void CChannel::UpdateSocketBufferSize ( const double dLocalStdDev ) -{ - // just update the socket buffer size if auto setting is enabled, otherwise - // do nothing - if ( bDoAutoSockBufSize ) - { - -// TEST -SetSockBufNumFrames ( SockBuf.GetAutoSetting(), true ); - -/* -#ifdef _WIN32 -// TEST -static FILE* pFile = fopen ( "c:\\temp\\test.dat", "w" ); -fprintf ( pFile, "%d\n", SockBuf.GetAutoSetting() ); -fflush ( pFile ); -#endif -*/ - -/* - // We use the time response measurement for the automatic setting. - // Assumptions: - // - the audio interface/network jitter is assumed to be Gaussian - // - the buffer size is set to 3.3 times the standard deviation of - // the jitter (~98% of the jitter should be fit in the - // buffer) - // - introduce a hysteresis to avoid switching the buffer sizes all the - // time in case the time response measurement is close to a bound - // - only use time response measurement results if averaging buffer is - // completely filled - // - we need at least a jitter buffer size of the audio packet duration - // -> add audio buffer duration - const double dHysteresis = 0.2; - - // accumulate the standard deviations of input network stream and - // internal timer, - // add 0.5 to "round up" -> ceil, - // divide by MIN_SERVER_BLOCK_DURATION_MS because this is the size of - // one block in the jitter buffer - const double dEstCurBufSet = ( SYSTEM_BLOCK_DURATION_MS_FLOAT + - 3.3 * ( CycleTimeVariance.GetStdDev() + dLocalStdDev ) ) / - SYSTEM_BLOCK_DURATION_MS_FLOAT + 0.5; -*/ -/* -// TEST -//if (bIsServer) { -static FILE* pFile = fopen ( "c:\\temp\\test.dat", "w" ); -fprintf ( pFile, "%e %e %e\n", CycleTimeVariance.GetStdDev(), dLocalStdDev, dEstCurBufSet ); -fflush ( pFile ); -// close;x=read('c:/temp/test.dat',-1,3);plot(x) -//} -*/ -/* - // upper/lower hysteresis decision - const int iUpperHystDec = LlconMath().round ( dEstCurBufSet - dHysteresis ); - const int iLowerHystDec = LlconMath().round ( dEstCurBufSet + dHysteresis ); - - // if both decisions are equal than use the result - if ( iUpperHystDec == iLowerHystDec ) - { - // updatet the socket buffer size with the new value - SetSockBufNumFrames ( iUpperHystDec, true ); - } - else - { - // we are in the middle of the decision region, use - // previous setting for determing the new decision - if ( !( ( GetSockBufNumFrames() == iUpperHystDec ) || - ( GetSockBufNumFrames() == iLowerHystDec ) ) ) - { - // The old result is not near the new decision, - // use per definition the upper decision. - // updatet the socket buffer size with the new value - SetSockBufNumFrames ( iUpperHystDec, true ); - } - } -*/ - } -} +/******************************************************************************\ + * Copyright (c) 2004-2011 + * + * Author(s): + * Volker Fischer + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + +#include "channel.h" + + +// CChannel implementation ***************************************************** +CChannel::CChannel ( const bool bNIsServer ) : + vecdGains ( MAX_NUM_CHANNELS, (double) 1.0 ), + bDoAutoSockBufSize ( true ), + bIsEnabled ( false ), + bIsServer ( bNIsServer ), + iNetwFrameSizeFact ( FRAME_SIZE_FACTOR_PREFERRED ), + iNetwFrameSize ( 20 ), // must be > 0 and should be close to a valid size + iNumAudioChannels ( 1 ) // mono +{ + // initial value for connection time out counter, we calculate the total + // number of samples here and subtract the number of samples of the block + // which we take out of the buffer to be independent of block sizes + iConTimeOutStartVal = CON_TIME_OUT_SEC_MAX * SYSTEM_SAMPLE_RATE_HZ; + + // init time-out for the buffer with zero -> no connection + iConTimeOut = 0; + + // init the socket buffer + SetSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ); + + // initialize cycle time variance measurement with defaults + CycleTimeVariance.Init ( SYSTEM_FRAME_SIZE_SAMPLES, + SYSTEM_SAMPLE_RATE_HZ, TIME_MOV_AV_RESPONSE_SECONDS ); + + // initialize channel name + ResetName(); + + + // Connections ------------------------------------------------------------- + QObject::connect ( &Protocol, + SIGNAL ( MessReadyForSending ( CVector ) ), + this, SLOT ( OnSendProtMessage ( CVector ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ChangeJittBufSize ( int ) ), + this, SLOT ( OnJittBufSizeChange ( int ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ReqJittBufSize() ), + SIGNAL ( ReqJittBufSize() ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ReqChanName() ), + SIGNAL ( ReqChanName() ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ReqConnClientsList() ), + SIGNAL ( ReqConnClientsList() ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ConClientListMesReceived ( CVector ) ), + SIGNAL ( ConClientListMesReceived ( CVector ) ) ); + + QObject::connect( &Protocol, SIGNAL ( ChangeChanGain ( int, double ) ), + this, SLOT ( OnChangeChanGain ( int, double ) ) ); + + QObject::connect( &Protocol, SIGNAL ( ChangeChanName ( QString ) ), + this, SLOT ( OnChangeChanName ( QString ) ) ); + + QObject::connect( &Protocol, + SIGNAL ( ChatTextReceived ( QString ) ), + SIGNAL ( ChatTextReceived ( QString ) ) ); + +// #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### +QObject::connect( &Protocol, + SIGNAL ( PingReceived ( int ) ), + SIGNAL ( PingReceived ( int ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( DetectedCLMessage ( CVector, int ) ), + SIGNAL ( DetectedCLMessage ( CVector, int ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( NetTranspPropsReceived ( CNetworkTransportProps ) ), + this, SLOT ( OnNetTranspPropsReceived ( CNetworkTransportProps ) ) ); + + QObject::connect ( &Protocol, + SIGNAL ( ReqNetTranspProps() ), + this, SLOT ( OnReqNetTranspProps() ) ); +} + +bool CChannel::ProtocolIsEnabled() +{ + // for the server, only enable protocol if the channel is connected, i.e., + // successfully audio packets are received from a client + // for the client, enable protocol if the channel is enabled, i.e., the + // connection button was hit by the user + if ( bIsServer ) + { + return IsConnected(); + } + else + { + return bIsEnabled; + } +} + +void CChannel::SetEnable ( const bool bNEnStat ) +{ + QMutexLocker locker ( &Mutex ); + + // set internal parameter + bIsEnabled = bNEnStat; + + // if channel is not enabled, reset time out count and protocol + if ( !bNEnStat ) + { + iConTimeOut = 0; + Protocol.Reset(); + } +} + +void CChannel::SetAudioStreamProperties ( const int iNewNetwFrameSize, + const int iNewNetwFrameSizeFact, + const int iNewNumAudioChannels ) +{ + // this function is intended for the server (not the client) + QMutexLocker locker ( &Mutex ); + + // store new values + iNumAudioChannels = iNewNumAudioChannels; + iNetwFrameSize = iNewNetwFrameSize; + iNetwFrameSizeFact = iNewNetwFrameSizeFact; + + // init socket buffer + SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames ); + + // init conversion buffer + ConvBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); + + // reset cycle time variance measurement + CycleTimeVariance.Reset(); + + // tell the server that audio coding has changed + CreateNetTranspPropsMessFromCurrentSettings(); +} + +bool CChannel::SetSockBufNumFrames ( const int iNewNumFrames, + const bool bPreserve ) +{ + QMutexLocker locker ( &Mutex ); // this operation must be done with mutex + + // first check for valid input parameter range + if ( ( iNewNumFrames >= MIN_NET_BUF_SIZE_NUM_BL ) && + ( iNewNumFrames <= MAX_NET_BUF_SIZE_NUM_BL ) ) + { + // only apply parameter if new parameter is different from current one + if ( iCurSockBufNumFrames != iNewNumFrames ) + { + // store new value + iCurSockBufNumFrames = iNewNumFrames; + + // the network block size is a multiple of the minimum network + // block size + SockBuf.Init ( iNetwFrameSize, iNewNumFrames, bPreserve ); + + // only in case we are the server and auto jitter buffer setting is + // enabled, we have to report the current setting to the client + if ( bIsServer && bDoAutoSockBufSize ) + { + // we cannot call the "CreateJitBufMes" function directly since + // this would give us problems with different threads (e.g. the + // timer thread) and the protocol mechanism (problem with + // qRegisterMetaType(), etc.) + emit ServerAutoSockBufSizeChange ( iNewNumFrames ); + } + + return false; // -> no error + } + } + + return true; // set error flag +} + +void CChannel::SetDoAutoSockBufSize ( const bool bValue ) +{ + QMutexLocker locker ( &Mutex ); + + // only act on new value if it is different from the current one + if ( bDoAutoSockBufSize != bValue ) + { + if ( bValue ) + { + // in case auto socket buffer size was just enabled, reset statistic + CycleTimeVariance.Reset(); + } + + // store new setting + bDoAutoSockBufSize = bValue; + } +} + +void CChannel::SetGain ( const int iChanID, + const double dNewGain ) +{ + QMutexLocker locker ( &Mutex ); + + // set value (make sure channel ID is in range) + if ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) ) + { + vecdGains[iChanID] = dNewGain; + } +} + +double 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 vecdGains[iChanID]; + } + else + { + return 0; + } +} + +void CChannel::SetName ( const QString strNewName ) +{ + bool bNameHasChanged = false; + + Mutex.lock(); + { + // apply value (if different from previous name) + if ( sName.compare ( strNewName ) ) + { + sName = strNewName; + bNameHasChanged = true; + } + } + Mutex.unlock(); + + // fire message that name has changed + if ( bNameHasChanged ) + { + // the "emit" has to be done outside the mutexed region + emit NameHasChanged(); + } +} +QString CChannel::GetName() +{ + // make sure the string is not written at the same time when it is + // read here -> use mutex to secure access + QMutexLocker locker ( &Mutex ); + + return sName; +} + +void CChannel::OnSendProtMessage ( CVector vecMessage ) +{ + // only send messages if protocol is enabled, otherwise delete complete + // queue + if ( ProtocolIsEnabled() ) + { + // emit message to actually send the data + emit MessReadyForSending ( vecMessage ); + } + else + { + // delete send message queue + Protocol.Reset(); + } +} + +void CChannel::OnJittBufSizeChange ( int iNewJitBufSize ) +{ + // for server apply setting, for client emit message + if ( bIsServer ) + { + // first check for special case: auto setting + if ( iNewJitBufSize == AUTO_NET_BUF_SIZE_FOR_PROTOCOL ) + { + SetDoAutoSockBufSize ( true ); + } + else + { + // manual setting is received, turn OFF auto setting and apply new value + SetDoAutoSockBufSize ( false ); + SetSockBufNumFrames ( iNewJitBufSize, true ); + } + } + else + { + emit JittBufSizeChanged ( iNewJitBufSize ); + } +} + +void CChannel::OnChangeChanGain ( int iChanID, + double dNewGain ) +{ + SetGain ( iChanID, dNewGain ); +} + +void CChannel::OnChangeChanName ( QString strName ) +{ + SetName ( strName ); +} + +bool CChannel::GetAddress ( CHostAddress& RetAddr ) +{ + QMutexLocker locker ( &Mutex ); + + if ( IsConnected() ) + { + RetAddr = InetAddr; + return true; + } + else + { + RetAddr = CHostAddress(); + return false; + } +} + +void CChannel::OnNetTranspPropsReceived ( CNetworkTransportProps NetworkTransportProps ) +{ + // only the server shall act on network transport properties message + if ( bIsServer ) + { + QMutexLocker locker ( &Mutex ); + + // store received parameters + iNumAudioChannels = NetworkTransportProps.iNumAudioChannels; + iNetwFrameSizeFact = NetworkTransportProps.iBlockSizeFact; + iNetwFrameSize = + NetworkTransportProps.iBaseNetworkPacketSize; + + // update socket buffer (the network block size is a multiple of the + // minimum network frame size + SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames ); + + // init conversion buffer + ConvBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); + + // reset statistic + CycleTimeVariance.Reset(); + } +} + +void CChannel::OnReqNetTranspProps() +{ + CreateNetTranspPropsMessFromCurrentSettings(); +} + +void CChannel::CreateNetTranspPropsMessFromCurrentSettings() +{ + CNetworkTransportProps NetworkTransportProps ( + iNetwFrameSize, + iNetwFrameSizeFact, + iNumAudioChannels, + SYSTEM_SAMPLE_RATE_HZ, + CT_CELT, // always CELT coding + 0, // version of the codec + 0 ); + + // send current network transport properties + Protocol.CreateNetwTranspPropsMes ( NetworkTransportProps ); +} + +void CChannel::Disconnect() +{ + // we only have to disconnect the channel if it is actually connected + if ( IsConnected() ) + { + // set time out counter to a small value > 0 so that the next time a + // received audio block is queried, the disconnection is performed + // (assuming that no audio packet is received in the meantime) + iConTimeOut = 1; // a small number > 0 + } +} + +EPutDataStat CChannel::PutData ( const CVector& vecbyData, + int iNumBytes ) +{ + EPutDataStat eRet = PS_GEN_ERROR; + + // init flags + bool bIsProtocolPacket = false; + bool bIsAudioPacket = false; + bool bNewConnection = false; + + if ( bIsEnabled ) + { + // first check if this is protocol data + // only use protocol data if protocol mechanism is enabled + if ( ProtocolIsEnabled() ) + { + // parse the message assuming this is a protocol message + if ( !Protocol.ParseMessage ( vecbyData, iNumBytes ) ) + { + // set status flags + eRet = PS_PROT_OK; + bIsProtocolPacket = true; + } + } + else + { + // In case we are the server and the current channel is not + // connected, we do not evaluate protocal messages but these + // messages could start the server which is not desired, especially + // not for the disconnect messages. + // We now do not start the server if a valid protocol message + // was received but only start the server on audio packets + if ( Protocol.IsProtocolMessage ( vecbyData, iNumBytes ) ) + { + // set status flags + eRet = PS_PROT_OK_MESS_NOT_EVALUATED; + bIsProtocolPacket = true; + } + } + + // only try to parse audio if it was not a protocol packet + if ( !bIsProtocolPacket ) + { + Mutex.lock(); + { + + +// TODO only process data if network properties protocol message has been arrived + + + // only process audio if packet has correct size + if ( iNumBytes == ( iNetwFrameSize * iNetwFrameSizeFact ) ) + { + // set audio packet flag + bIsAudioPacket = true; + + // store new packet in jitter buffer + if ( SockBuf.Put ( vecbyData, iNumBytes ) ) + { + eRet = PS_AUDIO_OK; + } + else + { + eRet = PS_AUDIO_ERR; + } + + // update cycle time variance measurement (this is only + // used in case auto socket buffer size is enabled) + if ( bDoAutoSockBufSize ) + { + CycleTimeVariance.Update(); + } + } + else + { + // the protocol parsing failed and this was no audio block, + // we treat this as protocol error (unkown packet) + eRet = PS_PROT_ERR; + } + + // all network packets except of valid llcon protocol messages + // regardless if they are valid or invalid audio packets lead to + // a state change to a connected channel + // this is because protocol messages can only be sent on a + // connected channel and the client has to inform the server + // about the audio packet properties via the protocol + + // check if channel was not connected, this is a new connection + bNewConnection = !IsConnected(); + + // reset time-out counter + ResetTimeOutCounter(); + } + Mutex.unlock(); + } + + if ( bNewConnection ) + { + // if this is a new connection and the current network packet is + // neither an audio or protocol packet, we have to query the + // network transport properties for the audio packets + // (this is only required for server since we defined that the + // server has to send with the same properties as sent by + // the client) + +// TODO check the conditions: !bIsProtocolPacket should always be true +// since we can only get here if bNewConnection, should we really put +// !bIsAudioPacket in here, because shouldn't we always query the audio +// properties on a new connection? + + if ( bIsServer && ( !bIsProtocolPacket ) && ( !bIsAudioPacket ) ) + { + Protocol.CreateReqNetwTranspPropsMes(); + } + + // reset cycle time variance measurement + CycleTimeVariance.Reset(); + + // inform other objects that new connection was established + emit NewConnection(); + } + } + + return eRet; +} + +EGetDataStat CChannel::GetData ( CVector& vecbyData ) +{ + QMutexLocker locker ( &Mutex ); + + EGetDataStat eGetStatus; + + const bool bSockBufState = SockBuf.Get ( vecbyData ); + + // decrease time-out counter + if ( iConTimeOut > 0 ) + { + // subtract the number of samples of the current block since the + // time out counter is based on samples not on blocks (definition: + // always one atomic block is get by using the GetData() function + // where the atomic block size is "SYSTEM_FRAME_SIZE_SAMPLES") + +// TODO this code only works with the above assumption -> better +// implementation so that we are not depending on assumptions + + iConTimeOut -= SYSTEM_FRAME_SIZE_SAMPLES; + + if ( iConTimeOut <= 0 ) + { + // channel is just disconnected + eGetStatus = GS_CHAN_NOW_DISCONNECTED; + iConTimeOut = 0; // make sure we do not have negative values + + // emit message + emit Disconnected(); + } + else + { + if ( bSockBufState ) + { + // everything is ok + eGetStatus = GS_BUFFER_OK; + } + else + { + // channel is not yet disconnected but no data in buffer + eGetStatus = GS_BUFFER_UNDERRUN; + } + } + } + else + { + // channel is disconnected + eGetStatus = GS_CHAN_NOT_CONNECTED; + } + + return eGetStatus; +} + +CVector CChannel::PrepSendPacket ( const CVector& vecbyNPacket ) +{ + QMutexLocker locker ( &Mutex ); + + // if the block is not ready we have to initialize with zero length to + // tell the following network send routine that nothing should be sent + CVector vecbySendBuf ( 0 ); + + // use conversion buffer to convert sound card block size in network + // block size + if ( ConvBuf.Put ( vecbyNPacket ) ) + { + // a packet is ready + vecbySendBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact ); + vecbySendBuf = ConvBuf.Get(); + } + + return vecbySendBuf; +} + +int CChannel::GetUploadRateKbps() +{ + const int iAudioSizeOut = iNetwFrameSizeFact * SYSTEM_FRAME_SIZE_SAMPLES; + + // we assume that the UDP packet which is transported via IP has an + // additional header size of + // 8 (UDP) + 20 (IP without optional fields) = 28 bytes + return ( iNetwFrameSize * iNetwFrameSizeFact + 28 /* header */ ) * + 8 /* bits per byte */ * + SYSTEM_SAMPLE_RATE_HZ / iAudioSizeOut / 1000; +} + +void CChannel::UpdateSocketBufferSize ( const double dLocalStdDev ) +{ + // just update the socket buffer size if auto setting is enabled, otherwise + // do nothing + if ( bDoAutoSockBufSize ) + { + +// TEST +SetSockBufNumFrames ( SockBuf.GetAutoSetting(), true ); + +/* +#ifdef _WIN32 +// TEST +static FILE* pFile = fopen ( "c:\\temp\\test.dat", "w" ); +fprintf ( pFile, "%d\n", SockBuf.GetAutoSetting() ); +fflush ( pFile ); +#endif +*/ + +/* + // We use the time response measurement for the automatic setting. + // Assumptions: + // - the audio interface/network jitter is assumed to be Gaussian + // - the buffer size is set to 3.3 times the standard deviation of + // the jitter (~98% of the jitter should be fit in the + // buffer) + // - introduce a hysteresis to avoid switching the buffer sizes all the + // time in case the time response measurement is close to a bound + // - only use time response measurement results if averaging buffer is + // completely filled + // - we need at least a jitter buffer size of the audio packet duration + // -> add audio buffer duration + const double dHysteresis = 0.2; + + // accumulate the standard deviations of input network stream and + // internal timer, + // add 0.5 to "round up" -> ceil, + // divide by MIN_SERVER_BLOCK_DURATION_MS because this is the size of + // one block in the jitter buffer + const double dEstCurBufSet = ( SYSTEM_BLOCK_DURATION_MS_FLOAT + + 3.3 * ( CycleTimeVariance.GetStdDev() + dLocalStdDev ) ) / + SYSTEM_BLOCK_DURATION_MS_FLOAT + 0.5; +*/ +/* +// TEST +//if (bIsServer) { +static FILE* pFile = fopen ( "c:\\temp\\test.dat", "w" ); +fprintf ( pFile, "%e %e %e\n", CycleTimeVariance.GetStdDev(), dLocalStdDev, dEstCurBufSet ); +fflush ( pFile ); +// close;x=read('c:/temp/test.dat',-1,3);plot(x) +//} +*/ +/* + // upper/lower hysteresis decision + const int iUpperHystDec = LlconMath().round ( dEstCurBufSet - dHysteresis ); + const int iLowerHystDec = LlconMath().round ( dEstCurBufSet + dHysteresis ); + + // if both decisions are equal than use the result + if ( iUpperHystDec == iLowerHystDec ) + { + // updatet the socket buffer size with the new value + SetSockBufNumFrames ( iUpperHystDec, true ); + } + else + { + // we are in the middle of the decision region, use + // previous setting for determing the new decision + if ( !( ( GetSockBufNumFrames() == iUpperHystDec ) || + ( GetSockBufNumFrames() == iLowerHystDec ) ) ) + { + // The old result is not near the new decision, + // use per definition the upper decision. + // updatet the socket buffer size with the new value + SetSockBufNumFrames ( iUpperHystDec, true ); + } + } +*/ + } +} diff --git a/src/channel.h b/src/channel.h index c685709c..b33742ce 100755 --- a/src/channel.h +++ b/src/channel.h @@ -124,6 +124,7 @@ public: void CreateReqJitBufMes() { Protocol.CreateReqJitBufMes(); } void CreateReqConnClientsList() { Protocol.CreateReqConnClientsList(); } void CreateChatTextMes ( const QString& strChatText ) { Protocol.CreateChatTextMes ( strChatText ); } + void CreatePingMes ( const int iMs ) { Protocol.CreatePingMes ( iMs ); } void CreateConClientListMes ( const CVector& vecChanInfo ) { @@ -194,6 +195,7 @@ signals: void NameHasChanged(); void ReqChanName(); void ChatTextReceived ( QString strChatText ); + void PingReceived ( int iMs ); void ReqNetTranspProps(); void Disconnected(); void DetectedCLMessage ( CVector vecbyData, diff --git a/src/protocol.cpp b/src/protocol.cpp index ed17467a..f820481c 100755 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -518,6 +518,11 @@ if ( rand() < ( RAND_MAX / 2 ) ) return false; bRet = EvaluateChatTextMes ( vecData ); break; +// #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### +case PROTMESSID_PING_MS: + bRet = EvaluatePingMes ( vecData ); + break; + case PROTMESSID_NETW_TRANSPORT_PROPS: bRet = EvaluateNetwTranspPropsMes ( vecData ); break; @@ -914,6 +919,38 @@ bool CProtocol::EvaluateChatTextMes ( const CVector& vecData ) return false; // no error } + +// #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### +void CProtocol::CreatePingMes ( const int iMs ) +{ + int iPos = 0; // init position pointer + + // build data vector (4 bytes long) + CVector vecData ( 4 ); + + // transmit time (4 bytes) + PutValOnStream ( vecData, iPos, static_cast ( iMs ), 4 ); + + CreateAndSendMessage ( PROTMESSID_PING_MS, vecData ); +} + +bool CProtocol::EvaluatePingMes ( const CVector& vecData ) +{ + int iPos = 0; // init position pointer + + // check size + if ( vecData.Size() != 4 ) + { + return true; // return error code + } + + // invoke message action + emit PingReceived ( static_cast ( GetValFromStream ( vecData, iPos, 4 ) ) ); + + return false; // no error +} + + void CProtocol::CreateNetwTranspPropsMes ( const CNetworkTransportProps& NetTrProps ) { int iPos = 0; // init position pointer diff --git a/src/protocol.h b/src/protocol.h index 6854e727..0572eb0f 100755 --- a/src/protocol.h +++ b/src/protocol.h @@ -47,7 +47,7 @@ #define PROTMESSID_REQ_CONN_CLIENTS_LIST 16 // request connected client list #define PROTMESSID_CHANNEL_NAME 17 // set channel name for fader tag #define PROTMESSID_CHAT_TEXT 18 // contains a chat text -#define PROTMESSID_PING_MS 19 // OLD (not used anymore) +#define PROTMESSID_PING_MS 19 // for measuring ping time #define PROTMESSID_NETW_TRANSPORT_PROPS 20 // properties for network transport #define PROTMESSID_REQ_NETW_TRANSPORT_PROPS 21 // request properties for network transport #define PROTMESSID_DISCONNECTION 22 // OLD (not used anymore) @@ -93,6 +93,7 @@ public: void CreateChanNameMes ( const QString strName ); void CreateReqChanNameMes(); void CreateChatTextMes ( const QString strChatText ); + void CreatePingMes ( const int iMs ); void CreateNetwTranspPropsMes ( const CNetworkTransportProps& NetTrProps ); void CreateReqNetwTranspPropsMes(); @@ -203,6 +204,7 @@ protected: bool EvaluateChanNameMes ( const CVector& vecData ); bool EvaluateReqChanNameMes(); bool EvaluateChatTextMes ( const CVector& vecData ); + bool EvaluatePingMes ( const CVector& vecData ); bool EvaluateNetwTranspPropsMes ( const CVector& vecData ); bool EvaluateReqNetwTranspPropsMes(); @@ -250,6 +252,7 @@ signals: void ChangeChanName ( QString strName ); void ReqChanName(); void ChatTextReceived ( QString strChatText ); + void PingReceived ( int iMs ); void NetTranspPropsReceived ( CNetworkTransportProps NetworkTransportProps ); void ReqNetTranspProps(); diff --git a/src/server.cpp b/src/server.cpp index 3f953435..44ff55dd 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -412,6 +412,22 @@ CServer::CServer ( const int iNewNumChan, QObject::connect ( &vecChannels[9], SIGNAL ( ServerAutoSockBufSizeChange ( int ) ), this, SLOT ( OnServerAutoSockBufSizeChangeCh9 ( int ) ) ); QObject::connect ( &vecChannels[10], SIGNAL ( ServerAutoSockBufSizeChange ( int ) ), this, SLOT ( OnServerAutoSockBufSizeChangeCh10 ( int ) ) ); QObject::connect ( &vecChannels[11], SIGNAL ( ServerAutoSockBufSizeChange ( int ) ), this, SLOT ( OnServerAutoSockBufSizeChangeCh11 ( int ) ) ); + + +// #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### +QObject::connect ( &vecChannels[0], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh0 ( int ) ) ); +QObject::connect ( &vecChannels[1], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh1 ( int ) ) ); +QObject::connect ( &vecChannels[2], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh2 ( int ) ) ); +QObject::connect ( &vecChannels[3], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh3 ( int ) ) ); +QObject::connect ( &vecChannels[4], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh4 ( int ) ) ); +QObject::connect ( &vecChannels[5], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh5 ( int ) ) ); +QObject::connect ( &vecChannels[6], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh6 ( int ) ) ); +QObject::connect ( &vecChannels[7], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh7 ( int ) ) ); +QObject::connect ( &vecChannels[8], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh8 ( int ) ) ); +QObject::connect ( &vecChannels[9], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh9 ( int ) ) ); +QObject::connect ( &vecChannels[10], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh10 ( int ) ) ); +QObject::connect ( &vecChannels[11], SIGNAL ( PingReceived ( int ) ), this, SLOT ( OnPingReceivedCh11 ( int ) ) ); + } void CServer::OnSendProtMessage ( int iChID, CVector vecMessage ) diff --git a/src/server.h b/src/server.h index 8aaa152b..dd2904e9 100755 --- a/src/server.h +++ b/src/server.h @@ -380,6 +380,22 @@ public slots: void OnServerAutoSockBufSizeChangeCh9 ( int iNNumFra ) { vecChannels[9].CreateJitBufMes ( iNNumFra ); } void OnServerAutoSockBufSizeChangeCh10 ( int iNNumFra ) { vecChannels[10].CreateJitBufMes ( iNNumFra ); } void OnServerAutoSockBufSizeChangeCh11 ( int iNNumFra ) { vecChannels[11].CreateJitBufMes ( iNNumFra ); } + + +// #### COMPATIBILITY OLD VERSION, TO BE REMOVED #### +void OnPingReceivedCh0 ( int iMs ) { vecChannels[0].CreatePingMes ( iMs ); } +void OnPingReceivedCh1 ( int iMs ) { vecChannels[1].CreatePingMes ( iMs ); } +void OnPingReceivedCh2 ( int iMs ) { vecChannels[2].CreatePingMes ( iMs ); } +void OnPingReceivedCh3 ( int iMs ) { vecChannels[3].CreatePingMes ( iMs ); } +void OnPingReceivedCh4 ( int iMs ) { vecChannels[4].CreatePingMes ( iMs ); } +void OnPingReceivedCh5 ( int iMs ) { vecChannels[5].CreatePingMes ( iMs ); } +void OnPingReceivedCh6 ( int iMs ) { vecChannels[6].CreatePingMes ( iMs ); } +void OnPingReceivedCh7 ( int iMs ) { vecChannels[7].CreatePingMes ( iMs ); } +void OnPingReceivedCh8 ( int iMs ) { vecChannels[8].CreatePingMes ( iMs ); } +void OnPingReceivedCh9 ( int iMs ) { vecChannels[9].CreatePingMes ( iMs ); } +void OnPingReceivedCh10 ( int iMs ) { vecChannels[10].CreatePingMes ( iMs ); } +void OnPingReceivedCh11 ( int iMs ) { vecChannels[11].CreatePingMes ( iMs ); } + }; #endif /* !defined ( SERVER_HOIHGE7LOKIH83JH8_3_43445KJIUHF1912__INCLUDED_ ) */