jamulus/src/buffer.cpp

354 lines
11 KiB
C++
Raw Normal View History

2011-06-11 20:48:32 +02:00
/******************************************************************************\
2020-01-01 15:41:43 +01:00
* Copyright (c) 2004-2020
2011-06-11 20:48:32 +02:00
*
* Author(s):
* Volker Fischer
*
* Note: We are assuming here that put and get operations are secured by a mutex
* and accessing does not occur at the same time.
*
******************************************************************************
*
* 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 "buffer.h"
/* Network buffer implementation **********************************************/
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,
bPreserve );
2011-06-11 20:48:32 +02:00
// clear buffer if not preserved
2011-06-11 20:48:32 +02:00
if ( !bPreserve )
{
Clear();
2011-06-11 20:48:32 +02:00
}
}
bool CNetBuf::Put ( const CVector<uint8_t>& vecbyData,
const int iInSize )
{
bool bPutOK = true;
// check if there is not enough space available
2011-06-11 20:48:32 +02:00
if ( GetAvailSpace() < iInSize )
{
return false;
2011-06-11 20:48:32 +02:00
}
// copy new data in internal buffer (implemented in base class)
CBufferBase<uint8_t>::Put ( vecbyData, iInSize );
return bPutOK;
}
bool CNetBuf::Get ( CVector<uint8_t>& vecbyData,
const int iOutSize )
2011-06-11 20:48:32 +02:00
{
bool bGetOK = true; // init return value
// check size
if ( ( iOutSize == 0 ) || ( iOutSize != iBlockSize ) )
2011-06-11 20:48:32 +02:00
{
return false;
}
// check if there is not enough data available
if ( GetAvailData() < iOutSize )
2011-06-11 20:48:32 +02:00
{
return false;
2011-06-11 20:48:32 +02:00
}
// copy data from internal buffer in output buffer (implemented in base
// class)
CBufferBase<uint8_t>::Get ( vecbyData, iOutSize );
2011-06-11 20:48:32 +02:00
return bGetOK;
}
/* Network buffer with statistic calculations implementation ******************/
CNetBufWithStats::CNetBufWithStats() :
CNetBuf ( false ) // base class init: no simulation mode
{
// Define the sizes of the simulation buffers,
2011-06-11 20:48:32 +02:00
// must be NUM_STAT_SIMULATION_BUFFERS elements!
// Avoid the buffer length 1 because we do not have a solution for a
// sample rate offset correction. Caused by the jitter we usually get bad
// performance with just one buffer.
viBufSizesForSim[0] = 2;
viBufSizesForSim[1] = 3;
viBufSizesForSim[2] = 4;
viBufSizesForSim[3] = 5;
viBufSizesForSim[4] = 6;
viBufSizesForSim[5] = 7;
viBufSizesForSim[6] = 8;
viBufSizesForSim[7] = 9;
viBufSizesForSim[8] = 10;
viBufSizesForSim[9] = 11;
2011-06-11 20:48:32 +02:00
// set all simulation buffers in simulation mode
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
{
SimulationBuffer[i].SetIsSimulation ( true );
}
}
2013-02-23 18:49:44 +01:00
void CNetBufWithStats::GetErrorRates ( CVector<double>& vecErrRates,
double& dLimit,
double& dMaxUpLimit )
2013-02-23 18:49:44 +01:00
{
// get all the averages of the error statistic
vecErrRates.Init ( NUM_STAT_SIMULATION_BUFFERS );
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
{
vecErrRates[i] = ErrorRateStatistic[i].GetAverage();
}
// get the limits for the decisions
dLimit = ERROR_RATE_BOUND;
dMaxUpLimit = UP_MAX_ERROR_BOUND;
2013-02-23 18:49:44 +01:00
}
2011-06-11 20:48:32 +02:00
void CNetBufWithStats::Init ( const int iNewBlockSize,
const int iNewNumBlocks,
const bool bPreserve )
{
// call base class Init
CNetBuf::Init ( iNewBlockSize, iNewNumBlocks, bPreserve );
// 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, true );
}
// reset the initialization counter which controls the initialization
// phase length
ResetInitCounter();
2011-06-29 22:20:22 +02:00
// init auto buffer setting with a meaningful value, also init the
// IIR parameter with this value
2011-06-30 21:51:15 +02:00
iCurAutoBufferSizeSetting = 6;
2011-06-29 22:20:22 +02:00
dCurIIRFilterResult = iCurAutoBufferSizeSetting;
iCurDecidedResult = iCurAutoBufferSizeSetting;
2011-06-11 20:48:32 +02:00
}
}
void CNetBufWithStats::ResetInitCounter()
{
// start initialization phase of IIR filtering, use a quarter the size
// of the error rate statistic buffers which should be ok for a good
// initialization value (initialization phase should be as short as
// possible)
iInitCounter = MAX_STATISTIC_COUNT / 4;
}
2011-06-11 20:48:32 +02:00
bool CNetBufWithStats::Put ( const CVector<uint8_t>& vecbyData,
const int iInSize )
{
// call base class Put
const bool bPutOK = CNetBuf::Put ( vecbyData, iInSize );
// update statistics calculations
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
{
ErrorRateStatistic[i].Update (
!SimulationBuffer[i].Put ( vecbyData, iInSize ) );
}
return bPutOK;
}
bool CNetBufWithStats::Get ( CVector<uint8_t>& vecbyData,
const int iOutSize )
2011-06-11 20:48:32 +02:00
{
// call base class Get
const bool bGetOK = CNetBuf::Get ( vecbyData, iOutSize );
2011-06-11 20:48:32 +02:00
// update statistics calculations
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
{
ErrorRateStatistic[i].Update (
!SimulationBuffer[i].Get ( vecbyData, iOutSize ) );
2011-06-11 20:48:32 +02:00
}
// update auto setting
UpdateAutoSetting();
2011-06-11 20:48:32 +02:00
return bGetOK;
}
void CNetBufWithStats::UpdateAutoSetting()
2011-06-11 20:48:32 +02:00
{
int iCurDecision = 0; // dummy initialization
int iCurMaxUpDecision = 0; // dummy initialization
bool bDecisionFound;
// Get regular error rate decision -----------------------------------------
2011-06-11 20:48:32 +02:00
// Use a specified error bound to identify the best buffer size for the
// current network situation. Start with the smallest buffer and
// test for the error rate until the rate is below the bound.
bDecisionFound = false;
2011-06-11 20:48:32 +02:00
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS - 1; i++ )
{
if ( ( !bDecisionFound ) &&
2011-06-29 22:20:22 +02:00
( ErrorRateStatistic[i].GetAverage() <= ERROR_RATE_BOUND ) )
{
iCurDecision = viBufSizesForSim[i];
bDecisionFound = true;
}
}
if ( !bDecisionFound )
{
// in case no buffer is below bound, use largest buffer size
iCurDecision = viBufSizesForSim[NUM_STAT_SIMULATION_BUFFERS - 1];
}
// Get maximum upper error rate decision -----------------------------------
// Use a specified error bound to identify the maximum upper error rate
// to identify if we have a too low buffer setting which gives a very
// bad performance constantly. Start with the smallest buffer and
// test for the error rate until the rate is below the bound.
bDecisionFound = false;
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS - 1; i++ )
{
if ( ( !bDecisionFound ) &&
( ErrorRateStatistic[i].GetAverage() <= UP_MAX_ERROR_BOUND ) )
{
iCurMaxUpDecision = viBufSizesForSim[i];
bDecisionFound = true;
}
}
if ( !bDecisionFound )
{
// in case no buffer is below bound, use largest buffer size
iCurMaxUpDecision = viBufSizesForSim[NUM_STAT_SIMULATION_BUFFERS - 1];
// This is a worst case, something very bad had happened. Hopefully
// this was just temporary so that we initiate a new initialzation
// phase to get quickly back to normal buffer sizes (hopefully).
ResetInitCounter();
}
// Post calculation (filtering) --------------------------------------------
2011-06-29 22:20:22 +02:00
// Define different weigths for up and down direction. Up direction
// filtering shall be slower than for down direction since we assume
// that the lower value is the actual value which can be used for
// the current network condition. If the current error rate estimation
// is higher, it may be a temporary problem which should not change
// the current jitter buffer size significantly.
// For the initialization phase, use lower weight values to get faster
// adaptation.
double dWeightUp = IIR_WEIGTH_UP_NORMAL;
double dWeightDown = IIR_WEIGTH_DOWN_NORMAL;
const double dHysteresisValue = FILTER_DECISION_HYSTERESIS;
bool bUseFastAdaptation = false;
2011-06-29 22:20:22 +02:00
// check for initialization phase
if ( iInitCounter > 0 )
{
// decrease init counter
iInitCounter--;
// use the fast adaptation
bUseFastAdaptation = true;
}
// if the current detected buffer setting is below the maximum upper bound
// decision, then we enable a booster to go up to the minimum required
// number of buffer blocks (i.e. we use weights for fast adaptation)
if ( iCurAutoBufferSizeSetting < iCurMaxUpDecision )
{
bUseFastAdaptation = true;
}
if ( bUseFastAdaptation )
{
2011-06-29 22:20:22 +02:00
// overwrite weigth values with lower values
dWeightUp = IIR_WEIGTH_UP_FAST;
dWeightDown = IIR_WEIGTH_DOWN_FAST;
}
2011-06-29 22:20:22 +02:00
// apply non-linear IIR filter
2013-03-24 12:38:00 +01:00
MathUtils().UpDownIIR1 ( dCurIIRFilterResult,
2013-03-22 19:50:05 +01:00
static_cast<double> ( iCurDecision ),
dWeightUp,
dWeightDown );
2011-06-29 22:20:22 +02:00
/*
// TEST store important detection parameters in file for debugging
static FILE* pFile = fopen ( "test.dat", "w" );
static int icnt = 0;
if ( icnt == 50 )
{
fprintf ( pFile, "%d %e\n", iCurDecision, dCurIIRFilterResult );
fflush ( pFile );
icnt = 0;
}
else
{
icnt++;
}
*/
2011-06-29 22:20:22 +02:00
// apply a hysteresis
iCurAutoBufferSizeSetting =
2013-03-24 12:38:00 +01:00
MathUtils().DecideWithHysteresis ( dCurIIRFilterResult,
2011-06-29 22:20:22 +02:00
iCurDecidedResult,
dHysteresisValue );
2011-06-30 21:51:15 +02:00
// Initialization phase check and correction -------------------------------
// sometimes in the very first period after a connection we get a bad error
// rate result -> delete this from the initialization phase
if ( iInitCounter == MAX_STATISTIC_COUNT / 8 )
{
// check error rate of the largest buffer as the indicator
if ( ErrorRateStatistic[NUM_STAT_SIMULATION_BUFFERS - 1].
GetAverage() > ERROR_RATE_BOUND )
{
for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
{
ErrorRateStatistic[i].Reset();
}
}
}
2011-06-11 20:48:32 +02:00
}