From 56f528b13eac67232aad1526d58da8e28f4d1dd1 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Tue, 17 May 2011 15:39:33 +0000 Subject: [PATCH] bug fix in server, added simulation mode in buffer base class, added deactivated test code for simulation buffer statistics, avoid audio drop outs when the jitter buffer size is changed --- src/buffer.cpp | 94 ++++++++++-- src/buffer.h | 314 +++++++++++++++++++++++++++----------- src/channel.cpp | 7 +- src/channel.h | 7 +- src/client.cpp | 4 +- src/client.h | 5 +- src/clientsettingsdlg.cpp | 2 +- src/llconclientdlg.cpp | 2 +- src/server.cpp | 10 +- src/server.h | 1 - src/util.cpp | 8 +- 11 files changed, 326 insertions(+), 128 deletions(-) diff --git a/src/buffer.cpp b/src/buffer.cpp index af506903..19dfe221 100755 --- a/src/buffer.cpp +++ b/src/buffer.cpp @@ -29,17 +29,21 @@ /* Network buffer implementation **********************************************/ -void CNetBuf::Init ( const int iNewBlockSize, - const int iNewNumBlocks ) +void CNetBuf::Init ( const int iNewBlockSize, + const int iNewNumBlocks, + const bool bPreserve ) { // store block size value iBlockSize = iNewBlockSize; // total size -> size of one block times the number of blocks - CBufferBase::Init ( iNewBlockSize * iNewNumBlocks ); + CBufferBase::Init ( iNewBlockSize * iNewNumBlocks, bPreserve ); // use the "get" flag to make sure the buffer is cleared - Clear ( CT_GET ); + if ( !bPreserve ) + { + Clear ( CT_GET ); + } } bool CNetBuf::Put ( const CVector& vecbyData, @@ -180,7 +184,10 @@ void CNetBuf::Clear ( const EClearType eClearType ) if ( eClearType == CT_GET ) { // clear buffer since we had a buffer underrun - vecMemory.Reset ( 0 ); + if ( !bIsSimulation ) + { + vecMemory.Reset ( 0 ); + } // reset buffer pointers so that they are at maximum distance after // the get operation (assign new fill level value to the get pointer) @@ -233,14 +240,48 @@ void CNetBuf::Clear ( const EClearType eClearType ) /* Network buffer with statistic calculations implementation ******************/ +/* +CNetBufWithStats::CNetBufWithStats() : + CNetBuf ( false ) // base class init: no simulation mode +{ + // define the sizes of the simulation buffers, + // must be NUM_STAT_SIMULATION_BUFFERS elements! + viBufSizesForSim[0] = 2; + viBufSizesForSim[1] = 3; + viBufSizesForSim[2] = 4; + viBufSizesForSim[3] = 5; + viBufSizesForSim[4] = 6; + viBufSizesForSim[5] = 7; + viBufSizesForSim[6] = 8; + viBufSizesForSim[7] = 10; + viBufSizesForSim[8] = 12; + + // set all simulation buffers in simulation mode + for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ ) + { + SimulationBuffer[i].SetIsSimulation ( true ); + } +} + void CNetBufWithStats::Init ( const int iNewBlockSize, - const int iNewNumBlocks ) + const int iNewNumBlocks, + const bool bPreserve ) { // call base class Init - CNetBuf::Init ( iNewBlockSize, iNewNumBlocks ); + CNetBuf::Init ( iNewBlockSize, iNewNumBlocks, bPreserve ); - // init statistic - ErrorRateStatistic.Init ( MAX_STATISTIC_COUNT ); + // inits for statistics calculation + if ( !bPreserve ) + { + for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ ) + { + // init simulation buffers with the correct size + SimulationBuffer[i].Init ( iNewBlockSize, viBufSizesForSim[i] ); + + // init statistics + ErrorRateStatistic[i].Init ( MAX_STATISTIC_COUNT ); + } + } } bool CNetBufWithStats::Put ( const CVector& vecbyData, @@ -249,8 +290,12 @@ bool CNetBufWithStats::Put ( const CVector& vecbyData, // call base class Put const bool bPutOK = CNetBuf::Put ( vecbyData, iInSize ); - // update statistic - ErrorRateStatistic.Update ( !bPutOK ); + // update statistics calculations + for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ ) + { + ErrorRateStatistic[i].Update ( + !SimulationBuffer[i].Put ( vecbyData, iInSize ) ); + } return bPutOK; } @@ -260,8 +305,31 @@ bool CNetBufWithStats::Get ( CVector& vecbyData ) // call base class Get const bool bGetOK = CNetBuf::Get ( vecbyData ); - // update statistic - ErrorRateStatistic.Update ( !bGetOK ); + // update statistics calculations + for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ ) + { + ErrorRateStatistic[i].Update ( + !SimulationBuffer[i].Get ( vecbyData ) ); + } return bGetOK; } + +// TEST for debugging +void CNetBufWithStats::StoreAllSimAverages() +{ + FILE* pFile = fopen ( "c:\\temp\\test.dat", "w" ); + + for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS - 1; i++ ) + { + fprintf ( pFile, "%e, ", ErrorRateStatistic[i].GetAverage() ); + } + fprintf ( pFile, "%e", ErrorRateStatistic[NUM_STAT_SIMULATION_BUFFERS - 1].GetAverage() ); + + fprintf ( pFile, "\n" ); + fclose ( pFile ); + +// scilab: +// close;x=read('c:/temp/test.dat',-1,9);plot2d( [2, 3, 4, 5, 6, 7, 8, 10, 12], x, style=-1 , logflag = 'nl');plot2d([2 12],[1 1]*0.01);plot2d([2 12],[1 1]*0.005);x +} +*/ diff --git a/src/buffer.h b/src/buffer.h index eb12fee6..96f6db58 100755 --- a/src/buffer.h +++ b/src/buffer.h @@ -34,54 +34,188 @@ // blocks we have 30 s / 2.33 ms * 2 = 25714 #define MAX_STATISTIC_COUNT 25714 +// number of simulation network jitter buffers for evaluating the statistic +#define NUM_STAT_SIMULATION_BUFFERS 9 + /* Classes ********************************************************************/ // Buffer base class ----------------------------------------------------------- template class CBufferBase { public: - virtual void Init ( const int iNewMemSize ) + CBufferBase ( const bool bNIsSim = false ) : + bIsSimulation ( bNIsSim ), bIsInitialized ( false ) {} + + void SetIsSimulation ( const bool bNIsSim ) { bIsSimulation = bNIsSim; } + + virtual void Init ( const int iNewMemSize, + const bool bPreserve = false ) { + // in simulation mode the size is not changed during operation -> we do + // not have to implement special code for this case + // only enter the "preserve" branch, if object was already initialized + if ( bPreserve && ( !bIsSimulation ) && bIsInitialized ) + { + // copy old data in new vector using get pointer as zero per + // definition + + int iCurPos; + + // copy current data in temporary vector + CVector vecTempMemory ( vecMemory ); + + // resize actual buffer memory + vecMemory.Init ( iNewMemSize ); + + // get maximum number of data to be copied + int iCopyLen = GetAvailData(); + if ( iCopyLen > iNewMemSize ) + { + iCopyLen = iNewMemSize; + } + + // set correct buffer state + if ( iCopyLen >= iNewMemSize ) + { + eBufState = CBufferBase::BS_FULL; + } + else + { + if ( iCopyLen == 0 ) + { + eBufState = CBufferBase::BS_EMPTY; + } + else + { + eBufState = CBufferBase::BS_OK; + } + } + + if ( iGetPos < iPutPos ) + { + // "get" position is before "put" position -> no wrap around + for ( iCurPos = 0; iCurPos < iCopyLen; iCurPos++ ) + { + vecMemory[iCurPos] = vecTempMemory[iGetPos + iCurPos]; + } + + // update put pointer + if ( eBufState == CBufferBase::BS_FULL ) + { + iPutPos = 0; + } + else + { + iPutPos -= iGetPos; + } + } + else + { + // "put" position is before "get" position -> wrap around + bool bEnoughSpaceForSecondPart = true; + int iFirstPartLen = iMemSize - iGetPos; + + // check that first copy length is not larger then new memory + if ( iFirstPartLen > iCopyLen ) + { + iFirstPartLen = iCopyLen; + bEnoughSpaceForSecondPart = false; + } + + for ( iCurPos = 0; iCurPos < iFirstPartLen; iCurPos++ ) + { + vecMemory[iCurPos] = vecTempMemory[iGetPos + iCurPos]; + } + + if ( bEnoughSpaceForSecondPart ) + { + // calculate remaining copy length + const int iRemainingCopyLen = iCopyLen - iFirstPartLen; + + // perform copying of second part + for ( iCurPos = 0; iCurPos < iRemainingCopyLen; iCurPos++ ) + { + vecMemory[iCurPos + iFirstPartLen] = + vecTempMemory[iCurPos]; + } + } + + // update put pointer + if ( eBufState == CBufferBase::BS_FULL ) + { + iPutPos = 0; + } + else + { + iPutPos += iFirstPartLen; + } + } + + // update get position -> zero per definition + iGetPos = 0; + } + else + { + // allocate memory for actual data buffer + if ( !bIsSimulation ) + { + vecMemory.Init ( iNewMemSize ); + } + + // init buffer pointers and buffer state (empty buffer) + iGetPos = 0; + iPutPos = 0; + eBufState = CBufferBase::BS_EMPTY; + } + // store total memory size value iMemSize = iNewMemSize; - // allocate memory for actual data buffer - vecMemory.Init ( iNewMemSize ); - - // init buffer pointers and buffer state (empty buffer) - iGetPos = 0; - iPutPos = 0; - eBufState = CBufferBase::BS_EMPTY; + // set initialized flag + bIsInitialized = true; } virtual bool Put ( const CVector& vecData, const int iInSize ) { - // copy new data in internal buffer - int iCurPos = 0; - if ( iPutPos + iInSize > iMemSize ) + if ( bIsSimulation ) { - // remaining space size for second block - const int iRemSpace = iPutPos + iInSize - iMemSize; - - // data must be written in two steps because of wrap around - while ( iPutPos < iMemSize ) + // in this simulation only the buffer pointers and the buffer state + // is updated, no actual data is transferred + iPutPos += iInSize; + if ( iPutPos >= iMemSize ) { - vecMemory[iPutPos++] = vecData[iCurPos++]; - } - - for ( iPutPos = 0; iPutPos < iRemSpace; iPutPos++ ) - { - vecMemory[iPutPos] = vecData[iCurPos++]; + iPutPos -= iMemSize; } } else { - // data can be written in one step - const int iEnd = iPutPos + iInSize; - while ( iPutPos < iEnd ) + // copy new data in internal buffer + int iCurPos = 0; + if ( iPutPos + iInSize > iMemSize ) { - vecMemory[iPutPos++] = vecData[iCurPos++]; + // remaining space size for second block + const int iRemSpace = iPutPos + iInSize - iMemSize; + + // data must be written in two steps because of wrap around + while ( iPutPos < iMemSize ) + { + vecMemory[iPutPos++] = vecData[iCurPos++]; + } + + for ( iPutPos = 0; iPutPos < iRemSpace; iPutPos++ ) + { + vecMemory[iPutPos] = vecData[iCurPos++]; + } + } + else + { + // data can be written in one step + const int iEnd = iPutPos + iInSize; + while ( iPutPos < iEnd ) + { + vecMemory[iPutPos++] = vecData[iCurPos++]; + } } } @@ -103,31 +237,44 @@ public: // get size of data to be get from the buffer const int iInSize = vecData.Size(); - // copy data from internal buffer in output buffer - int iCurPos = 0; - if ( iGetPos + iInSize > iMemSize ) + if ( bIsSimulation ) { - // remaining data size for second block - const int iRemData = iGetPos + iInSize - iMemSize; - - // data must be read in two steps because of wrap around - while ( iGetPos < iMemSize ) + // in this simulation only the buffer pointers and the buffer state + // is updated, no actual data is transferred + iGetPos += iInSize; + if ( iGetPos >= iMemSize ) { - vecData[iCurPos++] = vecMemory[iGetPos++]; - } - - for ( iGetPos = 0; iGetPos < iRemData; iGetPos++ ) - { - vecData[iCurPos++] = vecMemory[iGetPos]; + iGetPos -= iMemSize; } } else { - // data can be read in one step - const int iEnd = iGetPos + iInSize; - while ( iGetPos < iEnd ) + // copy data from internal buffer in output buffer + int iCurPos = 0; + if ( iGetPos + iInSize > iMemSize ) { - vecData[iCurPos++] = vecMemory[iGetPos++]; + // remaining data size for second block + const int iRemData = iGetPos + iInSize - iMemSize; + + // data must be read in two steps because of wrap around + while ( iGetPos < iMemSize ) + { + vecData[iCurPos++] = vecMemory[iGetPos++]; + } + + for ( iGetPos = 0; iGetPos < iRemData; iGetPos++ ) + { + vecData[iCurPos++] = vecMemory[iGetPos]; + } + } + else + { + // data can be read in one step + const int iEnd = iGetPos + iInSize; + while ( iGetPos < iEnd ) + { + vecData[iCurPos++] = vecMemory[iGetPos++]; + } } } @@ -189,52 +336,13 @@ public: protected: enum EBufState { BS_OK, BS_FULL, BS_EMPTY }; - void PutSimulation ( const int iInSize ) - { - // in this simulation only the buffer pointers and the buffer state - // is updated, no actual data is transferred - iPutPos += iInSize; - if ( iPutPos >= iMemSize ) - { - iPutPos -= iMemSize; - } - - // set buffer state flag - if ( iPutPos == iGetPos ) - { - eBufState = CBufferBase::BS_FULL; - } - else - { - eBufState = CBufferBase::BS_OK; - } - } - - void GetSimulation ( const int iInSize ) - { - // in this simulation only the buffer pointers and the buffer state - // is updated, no actual data is transferred - iGetPos += iInSize; - if ( iGetPos >= iMemSize ) - { - iGetPos -= iMemSize; - } - - // set buffer state flag - if ( iPutPos == iGetPos ) - { - eBufState = CBufferBase::BS_EMPTY; - } - else - { - eBufState = CBufferBase::BS_OK; - } - } - CVector vecMemory; int iMemSize; - int iGetPos, iPutPos; + int iGetPos; + int iPutPos; EBufState eBufState; + bool bIsSimulation; + bool bIsInitialized; }; @@ -242,7 +350,13 @@ protected: class CNetBuf : public CBufferBase { public: - virtual void Init ( const int iNewBlockSize, const int iNewNumBlocks ); + CNetBuf ( const bool bNewIsSim = false ) : + CBufferBase ( bNewIsSim ) {} + + virtual void Init ( const int iNewBlockSize, + const int iNewNumBlocks, + const bool bPreserve = false ); + int GetSize() { return iMemSize / iBlockSize; } virtual bool Put ( const CVector& vecbyData, const int iInSize ); @@ -258,21 +372,37 @@ protected: }; +/* +// This is a test class which provides statistic for a certain number of +// simulation buffers -> was intended to improve the auto jitter buffer +// setting but we stick to the old auto mechanism because of some draw backs +// of the simulated jitter buffers like that if burst errors occur, we would +// have to exclude it to not to effect the error rate too much and others... + // Network buffer (jitter buffer) with statistic calculations ------------------ class CNetBufWithStats : public CNetBuf { public: - virtual void Init ( const int iNewBlockSize, const int iNewNumBlocks ); + CNetBufWithStats(); + + virtual void Init ( const int iNewBlockSize, + const int iNewNumBlocks, + const bool bPreserve = false ); virtual bool Put ( const CVector& vecbyData, const int iInSize ); virtual bool Get ( CVector& vecbyData ); - double GetErrorRate() { return ErrorRateStatistic.GetAverage(); } +// TEST +void StoreAllSimAverages(); protected: - // statistic - CErrorRate ErrorRateStatistic; + // statistic (do not use the vector class since the classes do not have + // appropriate copy constructor/operator) + CErrorRate ErrorRateStatistic[NUM_STAT_SIMULATION_BUFFERS]; + CNetBuf SimulationBuffer[NUM_STAT_SIMULATION_BUFFERS]; + int viBufSizesForSim[NUM_STAT_SIMULATION_BUFFERS]; }; +*/ // Conversion buffer (very simple buffer) -------------------------------------- diff --git a/src/channel.cpp b/src/channel.cpp index 553737d1..a6510d0e 100755 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -163,7 +163,8 @@ void CChannel::SetAudioStreamProperties ( const int iNewNetwFrameSize, CreateNetTranspPropsMessFromCurrentSettings(); } -bool CChannel::SetSockBufNumFrames ( const int iNewNumFrames ) +bool CChannel::SetSockBufNumFrames ( const int iNewNumFrames, + const bool bPreserve ) { QMutexLocker locker ( &Mutex ); // this operation must be done with mutex @@ -176,7 +177,7 @@ bool CChannel::SetSockBufNumFrames ( const int iNewNumFrames ) // the network block size is a multiple of the minimum network // block size - SockBuf.Init ( iNetwFrameSize, iNewNumFrames ); + SockBuf.Init ( iNetwFrameSize, iNewNumFrames, bPreserve ); return false; // -> no error } @@ -259,7 +260,7 @@ void CChannel::OnSendProtMessage ( CVector vecMessage ) void CChannel::OnJittBufSizeChange ( int iNewJitBufSize ) { - SetSockBufNumFrames ( iNewJitBufSize ); + SetSockBufNumFrames ( iNewJitBufSize, true ); } void CChannel::OnChangeChanGain ( int iChanID, double dNewGain ) diff --git a/src/channel.h b/src/channel.h index e0123c36..cf8c14ff 100755 --- a/src/channel.h +++ b/src/channel.h @@ -91,7 +91,8 @@ public: void SetRemoteChanGain ( const int iId, const double dGain ) { Protocol.CreateChanGainMes ( iId, dGain ); } - bool SetSockBufNumFrames ( const int iNewNumFrames ); + bool SetSockBufNumFrames ( const int iNewNumFrames, + const bool bPreserve = false ); int GetSockBufNumFrames() const { return iCurSockBufNumFrames; } int GetUploadRateKbps(); @@ -106,8 +107,6 @@ public: int GetNetwFrameSize() const { return iNetwFrameSize; } int GetNumAudioChannels() const { return iNumAudioChannels; } - double GetJitterBufferErrorRate() { return SockBuf.GetErrorRate(); } - // network protocol interface void CreateJitBufMes ( const int iJitBufSize ) { @@ -143,7 +142,7 @@ protected: CVector vecdGains; // network jitter-buffer - CNetBufWithStats SockBuf; + CNetBuf SockBuf; int iCurSockBufNumFrames; CCycleTimeVariance CycleTimeVariance; diff --git a/src/client.cpp b/src/client.cpp index ae71a2d6..b53d5233 100755 --- a/src/client.cpp +++ b/src/client.cpp @@ -421,11 +421,11 @@ void CClient::Stop() // stop audio interface Sound.Stop(); - // wait for approx. 300 ms to make sure no audio packet is still in the + // wait for approx. 100 ms to make sure no audio packet is still in the // network queue causing the channel to be reconnected right after having // received the disconnect message (seems not to gain much, disconnect is // still not working reliably) - QTime dieTime = QTime::currentTime().addMSecs ( 300 ); + QTime dieTime = QTime::currentTime().addMSecs ( 100 ); while ( QTime::currentTime() < dieTime ) { QCoreApplication::processEvents ( QEventLoop::AllEvents, 100 ); diff --git a/src/client.h b/src/client.h index 4f94acc1..76a33b89 100755 --- a/src/client.h +++ b/src/client.h @@ -134,13 +134,14 @@ public: void SetDoAutoSockBufSize ( const bool bValue ) { bDoAutoSockBufSize = bValue; } bool GetDoAutoSockBufSize() const { return bDoAutoSockBufSize; } - void SetSockBufNumFrames ( const int iNumBlocks ) + void SetSockBufNumFrames ( const int iNumBlocks, + const bool bPreserve = false ) { // only change parameter if new parameter is different from current one if ( Channel.GetSockBufNumFrames() != iNumBlocks ) { // set the new socket size (number of frames) - if ( !Channel.SetSockBufNumFrames ( iNumBlocks ) ) + if ( !Channel.SetSockBufNumFrames ( iNumBlocks, bPreserve ) ) { // if setting of socket buffer size was successful, // tell the server that size has changed diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 5e821ccb..2e03d722 100755 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -546,7 +546,7 @@ void CClientSettingsDlg::OnDriverSetupClicked() void CClientSettingsDlg::OnNetBufValueChanged ( int value ) { - pClient->SetSockBufNumFrames ( value ); + pClient->SetSockBufNumFrames ( value, true ); UpdateJitterBufferFrame(); } diff --git a/src/llconclientdlg.cpp b/src/llconclientdlg.cpp index 19120256..aa5de560 100755 --- a/src/llconclientdlg.cpp +++ b/src/llconclientdlg.cpp @@ -957,7 +957,7 @@ void CLlconClientDlg::customEvent ( QEvent* Event ) break; case MS_SET_JIT_BUF_SIZE: - pClient->SetSockBufNumFrames ( iStatus ); + pClient->SetSockBufNumFrames ( iStatus, true ); break; } diff --git a/src/server.cpp b/src/server.cpp index a6655217..7a3a5155 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -225,8 +225,6 @@ CServer::CServer ( const int iNewNumChan, vstrChatColors[4] = "maroon"; vstrChatColors[5] = "coral"; - vecsSendData.Init ( SYSTEM_FRAME_SIZE_SAMPLES ); - // init moving average buffer for response time evaluation CycleTimeVariance.Init ( SYSTEM_FRAME_SIZE_SAMPLES, SYSTEM_SAMPLE_RATE_HZ, TIME_MOV_AV_RESPONSE_SECONDS ); @@ -598,10 +596,10 @@ void CServer::OnTimer() // generate a sparate mix for each channel // actual processing of audio data -> mix - vecsSendData = ProcessData ( i, - vecvecsData, - vecvecdGains[i], - vecNumAudioChannels ); + CVector vecsSendData ( ProcessData ( i, + vecvecsData, + vecvecdGains[i], + vecNumAudioChannels ) ); // get current number of CELT coded bytes const int iCeltNumCodedBytes = diff --git a/src/server.h b/src/server.h index bc6af7a3..1e318b97 100755 --- a/src/server.h +++ b/src/server.h @@ -228,7 +228,6 @@ protected: QString strServerNameWithPort; CHighPrecisionTimer HighPrecisionTimer; - CVector vecsSendData; // server list CServerListManager ServerListManager; diff --git a/src/util.cpp b/src/util.cpp index fba8034f..1ecfb9a0 100755 --- a/src/util.cpp +++ b/src/util.cpp @@ -467,9 +467,11 @@ bool LlconNetwUtil::ParseNetworkAddress ( QString strAddress, /******************************************************************************\ * Global Functions Implementation * \******************************************************************************/ -void DebugError ( const QString& pchErDescr, const QString& pchPar1Descr, - const double dPar1, const QString& pchPar2Descr, - const double dPar2 ) +void DebugError ( const QString& pchErDescr, + const QString& pchPar1Descr, + const double dPar1, + const QString& pchPar2Descr, + const double dPar2 ) { QFile File ( "DebugError.dat" ); if ( File.open ( QIODevice::Append ) )