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

This commit is contained in:
Volker Fischer 2011-05-17 15:39:33 +00:00
parent 1a99b76365
commit 56f528b13e
11 changed files with 326 additions and 128 deletions

View file

@ -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<uint8_t>::Init ( iNewBlockSize * iNewNumBlocks );
CBufferBase<uint8_t>::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<uint8_t>& 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<uint8_t>& vecbyData,
@ -249,8 +290,12 @@ bool CNetBufWithStats::Put ( const CVector<uint8_t>& 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<uint8_t>& 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
}
*/

View file

@ -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 TData> 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<TData> 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<TData>::BS_FULL;
}
else
{
if ( iCopyLen == 0 )
{
eBufState = CBufferBase<TData>::BS_EMPTY;
}
else
{
eBufState = CBufferBase<TData>::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<TData>::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<TData>::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<TData>::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<TData>::BS_EMPTY;
// set initialized flag
bIsInitialized = true;
}
virtual bool Put ( const CVector<TData>& 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<TData>::BS_FULL;
}
else
{
eBufState = CBufferBase<TData>::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<TData>::BS_EMPTY;
}
else
{
eBufState = CBufferBase<TData>::BS_OK;
}
}
CVector<TData> 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<uint8_t>
{
public:
virtual void Init ( const int iNewBlockSize, const int iNewNumBlocks );
CNetBuf ( const bool bNewIsSim = false ) :
CBufferBase<uint8_t> ( bNewIsSim ) {}
virtual void Init ( const int iNewBlockSize,
const int iNewNumBlocks,
const bool bPreserve = false );
int GetSize() { return iMemSize / iBlockSize; }
virtual bool Put ( const CVector<uint8_t>& 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<uint8_t>& vecbyData, const int iInSize );
virtual bool Get ( CVector<uint8_t>& 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) --------------------------------------

View file

@ -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<uint8_t> vecMessage )
void CChannel::OnJittBufSizeChange ( int iNewJitBufSize )
{
SetSockBufNumFrames ( iNewJitBufSize );
SetSockBufNumFrames ( iNewJitBufSize, true );
}
void CChannel::OnChangeChanGain ( int iChanID, double dNewGain )

View file

@ -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<double> vecdGains;
// network jitter-buffer
CNetBufWithStats SockBuf;
CNetBuf SockBuf;
int iCurSockBufNumFrames;
CCycleTimeVariance CycleTimeVariance;

View file

@ -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 );

View file

@ -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

View file

@ -546,7 +546,7 @@ void CClientSettingsDlg::OnDriverSetupClicked()
void CClientSettingsDlg::OnNetBufValueChanged ( int value )
{
pClient->SetSockBufNumFrames ( value );
pClient->SetSockBufNumFrames ( value, true );
UpdateJitterBufferFrame();
}

View file

@ -957,7 +957,7 @@ void CLlconClientDlg::customEvent ( QEvent* Event )
break;
case MS_SET_JIT_BUF_SIZE:
pClient->SetSockBufNumFrames ( iStatus );
pClient->SetSockBufNumFrames ( iStatus, true );
break;
}

View file

@ -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<short> vecsSendData ( ProcessData ( i,
vecvecsData,
vecvecdGains[i],
vecNumAudioChannels ) );
// get current number of CELT coded bytes
const int iCeltNumCodedBytes =

View file

@ -228,7 +228,6 @@ protected:
QString strServerNameWithPort;
CHighPrecisionTimer HighPrecisionTimer;
CVector<short> vecsSendData;
// server list
CServerListManager ServerListManager;

View file

@ -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 ) )