1 /******************************************************************************\
2  * Copyright (c) 2004-2020
3  *
4  * Author(s):
5  *  Volker Fischer
6  *
7  * Note: We are assuming here that put and get operations are secured by a mutex
8  *       and accessing does not occur at the same time.
9  *
10  ******************************************************************************
11  *
12  * This program is free software; you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation; either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program; if not, write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
25  *
26 \******************************************************************************/
27 
28 #include "buffer.h"
29 
30 /* Network buffer implementation **********************************************/
Init(const int iNewBlockSize,const int iNewNumBlocks,const bool bNUseSequenceNumber,const bool bPreserve)31 void CNetBuf::Init ( const int iNewBlockSize, const int iNewNumBlocks, const bool bNUseSequenceNumber, const bool bPreserve )
32 {
33     // store the sequence number activation flag
34     bUseSequenceNumber = bNUseSequenceNumber;
35 
36     // in simulation mode the size is not changed during operation -> we do
37     // not have to implement special code for this case
38     // only enter the "preserve" branch, if object was already initialized
39     // and the block sizes are the same
40     if ( bPreserve && ( !bIsSimulation ) && bIsInitialized && ( iBlockSize == iNewBlockSize ) )
41     {
42         // extract all data from buffer in temporary storage
43         CVector<CVector<uint8_t>> vecvecTempMemory = vecvecMemory; // allocate worst case memory by copying
44 
45         if ( !bNUseSequenceNumber )
46         {
47             int iPreviousDataCnt = 0;
48 
49             while ( Get ( vecvecTempMemory[iPreviousDataCnt], iBlockSize ) )
50             {
51                 iPreviousDataCnt++;
52             }
53 
54             // now resize the buffer to the new size (buffer is empty after this operation)
55             Resize ( iNewNumBlocks, iNewBlockSize );
56 
57             // copy the previous data back in the buffer (make sure we only copy as much
58             // data back as the new buffer size can hold)
59             int iDataCnt = 0;
60 
61             while ( ( iDataCnt < iPreviousDataCnt ) && Put ( vecvecTempMemory[iDataCnt], iBlockSize ) )
62             {
63                 iDataCnt++;
64             }
65         }
66         else
67         {
68             // store current complete buffer state in temporary memory
69             CVector<int>  veciTempBlockValid ( iNumBlocksMemory );
70             const uint8_t iOldSequenceNumberAtGetPos = iSequenceNumberAtGetPos;
71             const int     iOldNumBlocksMemory        = iNumBlocksMemory;
72             const int     iOldBlockGetPos            = iBlockGetPos;
73             int           iCurBlockPos               = 0;
74 
75             while ( iBlockGetPos < iNumBlocksMemory )
76             {
77                 veciTempBlockValid[iCurBlockPos] = veciBlockValid[iBlockGetPos];
78                 vecvecTempMemory[iCurBlockPos++] = vecvecMemory[iBlockGetPos++];
79             }
80 
81             for ( iBlockGetPos = 0; iBlockGetPos < iOldBlockGetPos; iBlockGetPos++ )
82             {
83                 veciTempBlockValid[iCurBlockPos] = veciBlockValid[iBlockGetPos];
84                 vecvecTempMemory[iCurBlockPos++] = vecvecMemory[iBlockGetPos];
85             }
86 
87             // now resize the buffer to the new size
88             Resize ( iNewNumBlocks, iNewBlockSize );
89 
90             // write back the temporary data in new memory
91             iSequenceNumberAtGetPos = iOldSequenceNumberAtGetPos;
92             iBlockGetPos            = 0; // per definition
93 
94             for ( int iCurPos = 0; iCurPos < std::min ( iNewNumBlocks, iOldNumBlocksMemory ); iCurPos++ )
95             {
96                 veciBlockValid[iCurPos] = veciTempBlockValid[iCurPos];
97                 vecvecMemory[iCurPos]   = vecvecTempMemory[iCurPos];
98             }
99         }
100     }
101     else
102     {
103         Resize ( iNewNumBlocks, iNewBlockSize );
104     }
105 
106     // set initialized flag
107     bIsInitialized = true;
108 }
109 
Resize(const int iNewNumBlocks,const int iNewBlockSize)110 void CNetBuf::Resize ( const int iNewNumBlocks, const int iNewBlockSize )
111 {
112     // allocate memory for actual data buffer
113     vecvecMemory.Init ( iNewNumBlocks );
114     veciBlockValid.Init ( iNewNumBlocks, 0 ); // initialize with zeros = invalid
115 
116     if ( !bIsSimulation )
117     {
118         for ( int iBlock = 0; iBlock < iNewNumBlocks; iBlock++ )
119         {
120             vecvecMemory[iBlock].Init ( iNewBlockSize );
121         }
122     }
123 
124     // init buffer pointers and buffer state (empty buffer) and store buffer properties
125     iBlockGetPos     = 0;
126     iBlockPutPos     = 0;
127     eBufState        = BS_EMPTY;
128     iBlockSize       = iNewBlockSize;
129     iNumBlocksMemory = iNewNumBlocks;
130 }
131 
Put(const CVector<uint8_t> & vecbyData,int iInSize)132 bool CNetBuf::Put ( const CVector<uint8_t>& vecbyData, int iInSize )
133 {
134     // if the sequence number is used, we need a complete different way of applying
135     // the new network packet
136     if ( bUseSequenceNumber )
137     {
138         // check that the input size is a multiple of the block size
139         if ( ( iInSize % ( iBlockSize + iNumBytesSeqNum ) ) != 0 )
140         {
141             return false;
142         }
143 
144         // to get the number of input blocks we assume that the number of bytes for
145         // the sequence number is much smaller than the number of coded audio bytes
146         const int iNumBlocks = /* floor */ ( iInSize / iBlockSize );
147 
148         // copy new data in internal buffer
149         for ( int iBlock = 0; iBlock < iNumBlocks; iBlock++ )
150         {
151             // extract sequence number of current received block (per definition
152             // the sequence number is appended after the coded audio data)
153             const int iCurrentSequenceNumber = vecbyData[iBlock * ( iBlockSize + iNumBytesSeqNum ) + iBlockSize];
154 
155             // calculate the sequence number difference and take care of wrap
156             int iSeqNumDiff = iCurrentSequenceNumber - static_cast<int> ( iSequenceNumberAtGetPos );
157 
158             if ( iSeqNumDiff < -128 )
159             {
160                 iSeqNumDiff += 256;
161             }
162             else if ( iSeqNumDiff >= 128 )
163             {
164                 iSeqNumDiff -= 256;
165             }
166 
167             // The 1-byte sequence number wraps around at a count of 256. So, if a packet is delayed
168             // further than this we cannot detect it. But it does not matter since such a packet is
169             // more than 100 ms delayed so we have a bad network situation anyway. Therefore we
170             // assume that the sequence number difference between the received and local counter is
171             // correct. The idea of the following code is that we always move our "buffer window" so
172             // that the received packet fits into the buffer. By doing this we are robust against
173             // sample rate offsets between client/server or buffer glitches in the audio driver since
174             // we adjust the window. The downside is that we never throw away single packets which arrive
175             // too late so we throw away valid packets when we move the "buffer window" to the delayed
176             // packet and then back to the correct place when the next normal packet is received. But
177             // tests showed that the new buffer strategy does not perform worse than the old jitter
178             // buffer which did not use any sequence number at all.
179             if ( iSeqNumDiff < 0 )
180             {
181                 // the received packet comes too late so we shift the "buffer window" to the past
182                 // until the received packet is the very first packet in the buffer
183                 for ( int i = iSeqNumDiff; i < 0; i++ )
184                 {
185                     // insert an invalid block at the shifted position
186                     veciBlockValid[iBlockGetPos] = 0; // invalidate
187 
188                     // we decrease the local sequence number and get position and take care of wrap
189                     iSequenceNumberAtGetPos--;
190                     iBlockGetPos--;
191 
192                     if ( iBlockGetPos < 0 )
193                     {
194                         iBlockGetPos += iNumBlocksMemory;
195                     }
196                 }
197 
198                 // insert the new packet at the beginning of the buffer since it was delayed
199                 iBlockPutPos = iBlockGetPos;
200             }
201             else if ( iSeqNumDiff >= iNumBlocksMemory )
202             {
203                 // the received packet comes too early so we move the "buffer window" in the
204                 // future until the received packet is the last packet in the buffer
205                 for ( int i = 0; i < iSeqNumDiff - iNumBlocksMemory + 1; i++ )
206                 {
207                     // insert an invalid block at the shifted position
208                     veciBlockValid[iBlockGetPos] = 0; // invalidate
209 
210                     // we increase the local sequence number and get position and take care of wrap
211                     iSequenceNumberAtGetPos++;
212                     iBlockGetPos++;
213 
214                     if ( iBlockGetPos >= iNumBlocksMemory )
215                     {
216                         iBlockGetPos -= iNumBlocksMemory;
217                     }
218                 }
219 
220                 // insert the new packet at the end of the buffer since it is too early (since
221                 // we add an offset to the get position, we have to take care of wrapping)
222                 iBlockPutPos = iBlockGetPos + iNumBlocksMemory - 1;
223 
224                 if ( iBlockPutPos >= iNumBlocksMemory )
225                 {
226                     iBlockPutPos -= iNumBlocksMemory;
227                 }
228             }
229             else
230             {
231                 // this is the regular case: the received packet fits into the buffer so
232                 // we will write it at the correct position based on the sequence number
233                 iBlockPutPos = iBlockGetPos + iSeqNumDiff;
234 
235                 if ( iBlockPutPos >= iNumBlocksMemory )
236                 {
237                     iBlockPutPos -= iNumBlocksMemory;
238                 }
239             }
240 
241             // for simulation buffer only update pointer, no data copying
242             if ( !bIsSimulation )
243             {
244                 // copy one block of data in buffer
245                 std::copy ( vecbyData.begin() + iBlock * ( iBlockSize + iNumBytesSeqNum ),
246                             vecbyData.begin() + iBlock * ( iBlockSize + iNumBytesSeqNum ) + iBlockSize,
247                             vecvecMemory[iBlockPutPos].begin() );
248             }
249 
250             // valid packet added, set flag
251             veciBlockValid[iBlockPutPos] = 1;
252         }
253     }
254     else
255     {
256         // check if there is not enough space available and that the input size is a
257         // multiple of the block size
258         if ( ( GetAvailSpace() < iInSize ) || ( ( iInSize % iBlockSize ) != 0 ) )
259         {
260             return false;
261         }
262 
263         // copy new data in internal buffer
264         const int iNumBlocks = iInSize / iBlockSize;
265 
266         for ( int iBlock = 0; iBlock < iNumBlocks; iBlock++ )
267         {
268             // for simultion buffer only update pointer, no data copying
269             if ( !bIsSimulation )
270             {
271                 // copy one block of data in buffer
272                 std::copy ( vecbyData.begin() + iBlock * iBlockSize,
273                             vecbyData.begin() + iBlock * iBlockSize + iBlockSize,
274                             vecvecMemory[iBlockPutPos].begin() );
275             }
276 
277             // set the put position one block further
278             iBlockPutPos++;
279 
280             // take care about wrap around of put pointer
281             if ( iBlockPutPos == iNumBlocksMemory )
282             {
283                 iBlockPutPos = 0;
284             }
285         }
286 
287         // set buffer state flag
288         if ( iBlockPutPos == iBlockGetPos )
289         {
290             eBufState = BS_FULL;
291         }
292         else
293         {
294             eBufState = BS_OK;
295         }
296     }
297 
298     return true;
299 }
300 
Get(CVector<uint8_t> & vecbyData,const int iOutSize)301 bool CNetBuf::Get ( CVector<uint8_t>& vecbyData, const int iOutSize )
302 {
303     bool bReturn = true;
304 
305     // check requested output size and available buffer data
306     if ( ( iOutSize == 0 ) || ( iOutSize != iBlockSize ) || ( GetAvailData() < iOutSize ) )
307     {
308         return false;
309     }
310 
311     // if using sequence numbers, we do not use the block put position
312     // at all but only determine the state from the "valid block" indicator
313     if ( bUseSequenceNumber )
314     {
315         bReturn = ( veciBlockValid[iBlockGetPos] > 0 );
316 
317         // invalidate the block we are now taking from the buffer
318         veciBlockValid[iBlockGetPos] = 0; // zero means invalid
319     }
320 
321     // for simultion buffer or invalid block only update pointer, no data copying
322     if ( !bIsSimulation && bReturn )
323     {
324         // copy data from internal buffer in output buffer
325         std::copy ( vecvecMemory[iBlockGetPos].begin(), vecvecMemory[iBlockGetPos].begin() + iBlockSize, vecbyData.begin() );
326     }
327 
328     // set the get position and sequence number one block further
329     iBlockGetPos++;
330     iSequenceNumberAtGetPos++; // wraps around automatically
331 
332     // take care about wrap around of get pointer
333     if ( iBlockGetPos == iNumBlocksMemory )
334     {
335         iBlockGetPos = 0;
336     }
337 
338     // set buffer state flag
339     if ( iBlockPutPos == iBlockGetPos )
340     {
341         eBufState = BS_EMPTY;
342     }
343     else
344     {
345         eBufState = BS_OK;
346     }
347 
348     return bReturn;
349 }
350 
GetAvailSpace() const351 int CNetBuf::GetAvailSpace() const
352 {
353     // calculate available space in buffer
354     int iAvBlocks = iBlockGetPos - iBlockPutPos;
355 
356     // check for special case and wrap around
357     if ( iAvBlocks < 0 )
358     {
359         iAvBlocks += iNumBlocksMemory; // wrap around
360     }
361     else
362     {
363         if ( ( iAvBlocks == 0 ) && ( eBufState == BS_EMPTY ) )
364         {
365             iAvBlocks = iNumBlocksMemory;
366         }
367     }
368 
369     return iAvBlocks * iBlockSize;
370 }
371 
GetAvailData() const372 int CNetBuf::GetAvailData() const
373 {
374     // in case of using sequence numbers, we always return data from the
375     // buffer per definition
376     int iAvBlocks = iNumBlocksMemory;
377 
378     if ( !bUseSequenceNumber )
379     {
380         // calculate available data in buffer
381         iAvBlocks = iBlockPutPos - iBlockGetPos;
382 
383         // check for special case and wrap around
384         if ( iAvBlocks < 0 )
385         {
386             iAvBlocks += iNumBlocksMemory; // wrap around
387         }
388         else
389         {
390             if ( ( iAvBlocks == 0 ) && ( eBufState == BS_FULL ) )
391             {
392                 iAvBlocks = iNumBlocksMemory;
393             }
394         }
395     }
396 
397     return iAvBlocks * iBlockSize;
398 }
399 
400 /* Network buffer with statistic calculations implementation ******************/
CNetBufWithStats()401 CNetBufWithStats::CNetBufWithStats() :
402     CNetBuf ( false ), // base class init: no simulation mode
403     iMaxStatisticCount ( MAX_STATISTIC_COUNT ),
404     bUseDoubleSystemFrameSize ( false ),
405     dAutoFilt_WightUpNormal ( IIR_WEIGTH_UP_NORMAL ),
406     dAutoFilt_WightDownNormal ( IIR_WEIGTH_DOWN_NORMAL ),
407     dAutoFilt_WightUpFast ( IIR_WEIGTH_UP_FAST ),
408     dAutoFilt_WightDownFast ( IIR_WEIGTH_DOWN_FAST ),
409     dErrorRateBound ( ERROR_RATE_BOUND ),
410     dUpMaxErrorBound ( UP_MAX_ERROR_BOUND )
411 {
412     // Define the sizes of the simulation buffers,
413     // must be NUM_STAT_SIMULATION_BUFFERS elements!
414     // Avoid the buffer length 1 because we do not have a solution for a
415     // sample rate offset correction. Caused by the jitter we usually get bad
416     // performance with just one buffer.
417     viBufSizesForSim[0] = 2;
418     viBufSizesForSim[1] = 3;
419     viBufSizesForSim[2] = 4;
420     viBufSizesForSim[3] = 5;
421     viBufSizesForSim[4] = 6;
422     viBufSizesForSim[5] = 7;
423     viBufSizesForSim[6] = 8;
424     viBufSizesForSim[7] = 9;
425     viBufSizesForSim[8] = 10;
426     viBufSizesForSim[9] = 11;
427 
428     // set all simulation buffers in simulation mode
429     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
430     {
431         SimulationBuffer[i].SetIsSimulation ( true );
432     }
433 }
434 
GetErrorRates(CVector<double> & vecErrRates,double & dLimit,double & dMaxUpLimit)435 void CNetBufWithStats::GetErrorRates ( CVector<double>& vecErrRates, double& dLimit, double& dMaxUpLimit )
436 {
437     // get all the averages of the error statistic
438     vecErrRates.Init ( NUM_STAT_SIMULATION_BUFFERS );
439 
440     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
441     {
442         vecErrRates[i] = ErrorRateStatistic[i].GetAverage();
443     }
444 
445     // get the limits for the decisions
446     dLimit      = dErrorRateBound;
447     dMaxUpLimit = dUpMaxErrorBound;
448 }
449 
Init(const int iNewBlockSize,const int iNewNumBlocks,const bool bNUseSequenceNumber,const bool bPreserve)450 void CNetBufWithStats::Init ( const int iNewBlockSize, const int iNewNumBlocks, const bool bNUseSequenceNumber, const bool bPreserve )
451 {
452     // call base class Init
453     CNetBuf::Init ( iNewBlockSize, iNewNumBlocks, bNUseSequenceNumber, bPreserve );
454 
455     // inits for statistics calculation
456     if ( !bPreserve )
457     {
458         // set the auto filter weights and max statistic count
459         if ( bUseDoubleSystemFrameSize )
460         {
461             dAutoFilt_WightUpNormal   = IIR_WEIGTH_UP_NORMAL_DOUBLE_FRAME_SIZE;
462             dAutoFilt_WightDownNormal = IIR_WEIGTH_DOWN_NORMAL_DOUBLE_FRAME_SIZE;
463             dAutoFilt_WightUpFast     = IIR_WEIGTH_UP_FAST_DOUBLE_FRAME_SIZE;
464             dAutoFilt_WightDownFast   = IIR_WEIGTH_DOWN_FAST_DOUBLE_FRAME_SIZE;
465             iMaxStatisticCount        = MAX_STATISTIC_COUNT_DOUBLE_FRAME_SIZE;
466             dErrorRateBound           = ERROR_RATE_BOUND_DOUBLE_FRAME_SIZE;
467             dUpMaxErrorBound          = UP_MAX_ERROR_BOUND_DOUBLE_FRAME_SIZE;
468         }
469         else
470         {
471             dAutoFilt_WightUpNormal   = IIR_WEIGTH_UP_NORMAL;
472             dAutoFilt_WightDownNormal = IIR_WEIGTH_DOWN_NORMAL;
473             dAutoFilt_WightUpFast     = IIR_WEIGTH_UP_FAST;
474             dAutoFilt_WightDownFast   = IIR_WEIGTH_DOWN_FAST;
475             iMaxStatisticCount        = MAX_STATISTIC_COUNT;
476             dErrorRateBound           = ERROR_RATE_BOUND;
477             dUpMaxErrorBound          = UP_MAX_ERROR_BOUND;
478         }
479 
480         for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
481         {
482             // init simulation buffers with the correct size
483             SimulationBuffer[i].Init ( iNewBlockSize, viBufSizesForSim[i], bNUseSequenceNumber );
484 
485             // init statistics
486             ErrorRateStatistic[i].Init ( iMaxStatisticCount, true );
487         }
488 
489         // reset the initialization counter which controls the initialization
490         // phase length
491         ResetInitCounter();
492 
493         // init auto buffer setting with a meaningful value, also init the
494         // IIR parameter with this value
495         iCurAutoBufferSizeSetting = 6;
496         dCurIIRFilterResult       = iCurAutoBufferSizeSetting;
497         iCurDecidedResult         = iCurAutoBufferSizeSetting;
498     }
499 }
500 
ResetInitCounter()501 void CNetBufWithStats::ResetInitCounter()
502 {
503     // start initialization phase of IIR filtering, use a quarter the size
504     // of the error rate statistic buffers which should be ok for a good
505     // initialization value (initialization phase should be as short as
506     // possible)
507     iInitCounter = iMaxStatisticCount / 4;
508 }
509 
Put(const CVector<uint8_t> & vecbyData,const int iInSize)510 bool CNetBufWithStats::Put ( const CVector<uint8_t>& vecbyData, const int iInSize )
511 {
512     // call base class Put
513     const bool bPutOK = CNetBuf::Put ( vecbyData, iInSize );
514 
515     // update statistics calculations
516     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
517     {
518         ErrorRateStatistic[i].Update ( !SimulationBuffer[i].Put ( vecbyData, iInSize ) );
519     }
520 
521     return bPutOK;
522 }
523 
Get(CVector<uint8_t> & vecbyData,const int iOutSize)524 bool CNetBufWithStats::Get ( CVector<uint8_t>& vecbyData, const int iOutSize )
525 {
526     // call base class Get
527     const bool bGetOK = CNetBuf::Get ( vecbyData, iOutSize );
528 
529     // update statistics calculations
530     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
531     {
532         ErrorRateStatistic[i].Update ( !SimulationBuffer[i].Get ( vecbyData, iOutSize ) );
533     }
534 
535     // update auto setting
536     UpdateAutoSetting();
537 
538     return bGetOK;
539 }
540 
UpdateAutoSetting()541 void CNetBufWithStats::UpdateAutoSetting()
542 {
543     int  iCurDecision      = 0; // dummy initialization
544     int  iCurMaxUpDecision = 0; // dummy initialization
545     bool bDecisionFound;
546 
547     // Get regular error rate decision -----------------------------------------
548     // Use a specified error bound to identify the best buffer size for the
549     // current network situation. Start with the smallest buffer and
550     // test for the error rate until the rate is below the bound.
551     bDecisionFound = false;
552 
553     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS - 1; i++ )
554     {
555         if ( ( !bDecisionFound ) && ( ErrorRateStatistic[i].GetAverage() <= dErrorRateBound ) )
556         {
557             iCurDecision   = viBufSizesForSim[i];
558             bDecisionFound = true;
559         }
560     }
561 
562     if ( !bDecisionFound )
563     {
564         // in case no buffer is below bound, use largest buffer size
565         iCurDecision = viBufSizesForSim[NUM_STAT_SIMULATION_BUFFERS - 1];
566     }
567 
568     // Get maximum upper error rate decision -----------------------------------
569     // Use a specified error bound to identify the maximum upper error rate
570     // to identify if we have a too low buffer setting which gives a very
571     // bad performance constantly. Start with the smallest buffer and
572     // test for the error rate until the rate is below the bound.
573     bDecisionFound = false;
574 
575     for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS - 1; i++ )
576     {
577         if ( ( !bDecisionFound ) && ( ErrorRateStatistic[i].GetAverage() <= dUpMaxErrorBound ) )
578         {
579             iCurMaxUpDecision = viBufSizesForSim[i];
580             bDecisionFound    = true;
581         }
582     }
583 
584     if ( !bDecisionFound )
585     {
586         // in case no buffer is below bound, use largest buffer size
587         iCurMaxUpDecision = viBufSizesForSim[NUM_STAT_SIMULATION_BUFFERS - 1];
588 
589         // This is a worst case, something very bad had happened. Hopefully
590         // this was just temporary so that we initiate a new initialization
591         // phase to get quickly back to normal buffer sizes (hopefully).
592         ResetInitCounter();
593     }
594 
595     // Post calculation (filtering) --------------------------------------------
596     // Define different weights for up and down direction. Up direction
597     // filtering shall be slower than for down direction since we assume
598     // that the lower value is the actual value which can be used for
599     // the current network condition. If the current error rate estimation
600     // is higher, it may be a temporary problem which should not change
601     // the current jitter buffer size significantly.
602     // For the initialization phase, use lower weight values to get faster
603     // adaptation.
604     double       dWeightUp, dWeightDown;
605     const double dHysteresisValue   = FILTER_DECISION_HYSTERESIS;
606     bool         bUseFastAdaptation = false;
607 
608     // check for initialization phase
609     if ( iInitCounter > 0 )
610     {
611         // decrease init counter
612         iInitCounter--;
613 
614         // use the fast adaptation
615         bUseFastAdaptation = true;
616     }
617 
618     // if the current detected buffer setting is below the maximum upper bound
619     // decision, then we enable a booster to go up to the minimum required
620     // number of buffer blocks (i.e. we use weights for fast adaptation)
621     if ( iCurAutoBufferSizeSetting < iCurMaxUpDecision )
622     {
623         bUseFastAdaptation = true;
624     }
625 
626     if ( bUseFastAdaptation )
627     {
628         dWeightUp   = dAutoFilt_WightUpFast;
629         dWeightDown = dAutoFilt_WightDownFast;
630     }
631     else
632     {
633         dWeightUp   = dAutoFilt_WightUpNormal;
634         dWeightDown = dAutoFilt_WightDownNormal;
635     }
636 
637     // apply non-linear IIR filter
638     MathUtils().UpDownIIR1 ( dCurIIRFilterResult, static_cast<double> ( iCurDecision ), dWeightUp, dWeightDown );
639 
640     // clang-format off
641 /*
642 // TEST store important detection parameters in file for debugging
643 static FILE* pFile = fopen ( "test.dat", "w" );
644 static int icnt = 0;
645 if ( icnt == 50 )
646 {
647     fprintf ( pFile, "%d %e\n", iCurDecision, dCurIIRFilterResult );
648     fflush ( pFile );
649     icnt = 0;
650 }
651 else
652 {
653     icnt++;
654 }
655 */
656     // clang-format on
657 
658     // apply a hysteresis
659     iCurAutoBufferSizeSetting = MathUtils().DecideWithHysteresis ( dCurIIRFilterResult, iCurDecidedResult, dHysteresisValue );
660 
661     // Initialization phase check and correction -------------------------------
662     // sometimes in the very first period after a connection we get a bad error
663     // rate result -> delete this from the initialization phase
664     if ( iInitCounter == iMaxStatisticCount / 8 )
665     {
666         // check error rate of the largest buffer as the indicator
667         if ( ErrorRateStatistic[NUM_STAT_SIMULATION_BUFFERS - 1].GetAverage() > dErrorRateBound )
668         {
669             for ( int i = 0; i < NUM_STAT_SIMULATION_BUFFERS; i++ )
670             {
671                 ErrorRateStatistic[i].Reset();
672             }
673         }
674     }
675 }
676