diff --git a/lib_dec/jbm_jb4_circularbuffer.c b/lib_dec/jbm_jb4_circularbuffer.c index da95e41353f8ea0e97de6ee828c9a0d0e2af15a2..058dd46ca5457ebad26a9389b1c8a1228fcbe23d 100644 --- a/lib_dec/jbm_jb4_circularbuffer.c +++ b/lib_dec/jbm_jb4_circularbuffer.c @@ -44,6 +44,485 @@ #include "jbm_jb4_circularbuffer.h" +#ifdef IVAS_FLOAT_FIXED +/** Calculates percentile by selecting greatest elements. + * This function partial sorts all given elements in the given buffer. + * @param[in,out] elements ascending sorted buffer of selected greatest elements + * @param[in,out] size size of elements buffer + * @param[in] capacity maximum number of elements to buffer + * @param[in] newElement element to insert in buffer IF great enough */ +static void JB4_CIRCULARBUFFER_calcPercentile( JB4_CIRCULARBUFFER_ELEMENT *elements, UWord16 *size, const UWord16 capacity, JB4_CIRCULARBUFFER_ELEMENT newElement ); + +/** circular buffer (FIFO) with fixed capacity */ +struct JB4_CIRCULARBUFFER +{ + /** elements of circular buffer */ + JB4_CIRCULARBUFFER_ELEMENT *data; + /** maximum allowed number of elements plus one free element (to decide between full/empty buffer) */ + UWord16 capacity; + /** position of next enque operation */ + UWord16 writePos; + /** position of next deque operation */ + UWord16 readPos; +}; + + +/* Creates a circular buffer (FIFO) */ +ivas_error JB4_CIRCULARBUFFER_Create( + JB4_CIRCULARBUFFER_HANDLE *ph ) +{ + JB4_CIRCULARBUFFER_HANDLE h; + + IF ( ( h = malloc( sizeof( struct JB4_CIRCULARBUFFER ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JBM\n" ) ); + } + + h->data = NULL; + h->capacity = 0; + move16(); + h->writePos = 0; + move16(); + h->readPos = 0; + move16(); + + *ph = h; + + return IVAS_ERR_OK; +} + + +/* Destroys the circular buffer (FIFO) */ +void JB4_CIRCULARBUFFER_Destroy( + JB4_CIRCULARBUFFER_HANDLE *ph ) +{ + JB4_CIRCULARBUFFER_HANDLE h; + + IF ( !ph ) + { + return; + } + h = *ph; + IF ( !h ) + { + return; + } + + IF ( h->data ) + { + free( h->data ); + } + free( h ); + *ph = NULL; + + return; +} + + +/* Initializes a circular buffer (FIFO) with a fixed maximum allowed number of elements */ +Word16 JB4_CIRCULARBUFFER_Init( + JB4_CIRCULARBUFFER_HANDLE h, + UWord16 capacity ) +{ + /* keep one element free to be able to decide between full/empty buffer */ + ++capacity; + + IF ( ( h->data = malloc( capacity * sizeof( JB4_CIRCULARBUFFER_ELEMENT ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JBM\n" ) ); + } + + h->capacity = capacity; + move16(); + h->writePos = 0; + move16(); + h->readPos = 0; + move16(); + + return IVAS_ERR_OK; +} + + +Word16 JB4_CIRCULARBUFFER_Enque( + JB4_CIRCULARBUFFER_HANDLE h, + JB4_CIRCULARBUFFER_ELEMENT element ) +{ + IF ( JB4_CIRCULARBUFFER_IsFull( h ) ) + { + return -1; + } + + h->data[h->writePos] = element; + ++h->writePos; + IF ( h->writePos == h->capacity ) + { + h->writePos = 0; + move16(); + } + + return 0; +} + + +Word16 JB4_CIRCULARBUFFER_Deque( + JB4_CIRCULARBUFFER_HANDLE h, + JB4_CIRCULARBUFFER_ELEMENT *pElement ) +{ + IF ( JB4_CIRCULARBUFFER_IsEmpty( h ) ) + { + return -1; + } + + *pElement = h->data[h->readPos]; + ++h->readPos; + IF ( h->readPos == h->capacity ) + { + h->readPos = 0; + move16(); + } + + return 0; +} + + +/* Returns the first element. */ +JB4_CIRCULARBUFFER_ELEMENT JB4_CIRCULARBUFFER_Front( + const JB4_CIRCULARBUFFER_HANDLE h ) +{ + JB4_CIRCULARBUFFER_ELEMENT ret; + + ret = h->data[h->readPos]; + move32(); + + return ret; +} + +/* Returns the last element. */ +JB4_CIRCULARBUFFER_ELEMENT JB4_CIRCULARBUFFER_Back( + const JB4_CIRCULARBUFFER_HANDLE h ) +{ + JB4_CIRCULARBUFFER_ELEMENT ret; + + IF ( h->writePos != 0U ) + { + ret = h->data[h->writePos - 1]; + move32(); + } + ELSE + { + ret = h->data[h->capacity - 1]; + move32(); + } + + + return ret; +} + + +Word16 JB4_CIRCULARBUFFER_IsEmpty( + const JB4_CIRCULARBUFFER_HANDLE h ) +{ + Word16 ret; + + IF ( h->readPos == h->writePos ) + { + ret = 1; + move16(); + } + ELSE + { + ret = 0; + move16(); + } + + return ret; +} + + +Word16 JB4_CIRCULARBUFFER_IsFull( + const JB4_CIRCULARBUFFER_HANDLE h ) +{ + Word16 ret; + + IF ( ( ( h->writePos + 1 ) % h->capacity ) == h->readPos ) + { + ret = 1; + move16(); + } + ELSE + { + ret = 0; + move16(); + } + + return ret; +} + + +UWord16 JB4_CIRCULARBUFFER_Size( + const JB4_CIRCULARBUFFER_HANDLE h ) +{ + UWord16 ret; + + IF ( h->readPos <= h->writePos ) + { + ret = h->writePos - h->readPos; + } + ELSE + { + /* wrap around */ + ret = h->writePos + h->capacity - h->readPos; + } + + + return ret; +} + + +/* Calculates statistics over all elements: min element */ +void JB4_CIRCULARBUFFER_Min( + const JB4_CIRCULARBUFFER_HANDLE h, + JB4_CIRCULARBUFFER_ELEMENT *pMin ) +{ + UWord16 i; + JB4_CIRCULARBUFFER_ELEMENT minEle; + + /* init output variable */ + minEle = h->data[h->readPos]; + move32(); + + IF ( h->readPos <= h->writePos ) + { + /* no wrap around */ + /* calc statistics for [readPos;writePos[ */ + FOR ( i = h->readPos; i != h->writePos; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + } + } + ELSE + { + /* wrap around */ + /* calc statistics for [readPos;capacity[ */ + FOR ( i = h->readPos; i != h->capacity; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + } + /* calc statistics for [0;writePos[ */ + FOR ( i = 0; i != h->writePos; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + } + } + + *pMin = minEle; + move32(); +} + + +/* Calculates statistics over all elements: max element */ +void JB4_CIRCULARBUFFER_Max( + const JB4_CIRCULARBUFFER_HANDLE h, + JB4_CIRCULARBUFFER_ELEMENT *pMax ) +{ + UWord16 i; + JB4_CIRCULARBUFFER_ELEMENT maxEle; + + /* init output variable */ + maxEle = h->data[h->readPos]; + move32(); + IF ( h->readPos <= h->writePos ) + { + /* no wrap around */ + /* calc statistics for [readPos;writePos[ */ + FOR ( i = h->readPos; i != h->writePos; ++i ) + { + IF ( GT_32( h->data[i], maxEle ) ) + { + maxEle = h->data[i]; + move32(); + } + } + } + ELSE + { + /* wrap around */ + /* calc statistics for [readPos;capacity[ */ + FOR ( i = h->readPos; i != h->capacity; ++i ) + { + IF ( GT_32( h->data[i], maxEle ) ) + { + maxEle = h->data[i]; + move32(); + } + } + /* calc statistics for [0;writePos[ */ + FOR ( i = 0; i != h->writePos; ++i ) + { + IF ( GT_32( h->data[i], maxEle ) ) + { + maxEle = h->data[i]; + move32(); + } + } + } + + *pMax = maxEle; + move32(); + + return; +} + +#define JBM_MAX_CIRCULAR_ELEMENTS 100 + +/* Calculates statistics over a considered fraction of all elements: min element and percentile */ +void JB4_CIRCULARBUFFER_MinAndPercentile( + const JB4_CIRCULARBUFFER_HANDLE h, + UWord16 nElementsToIgnore, + JB4_CIRCULARBUFFER_ELEMENT *pMin, + JB4_CIRCULARBUFFER_ELEMENT *pPercentile ) +{ + UWord16 i; + JB4_CIRCULARBUFFER_ELEMENT maxElements[JBM_MAX_CIRCULAR_ELEMENTS]; + UWord16 maxElementsSize; + UWord16 maxElementsCapacity; + JB4_CIRCULARBUFFER_ELEMENT minEle; + + /* init output variables */ + minEle = h->data[h->readPos]; + + /* To calculate the percentile, a number of elements with the highest values are collected in maxElements in + * ascending sorted order. This array has a size of nElementsToIgnore plus one. This additional element is the + * lowest of all maxElements, and is called the percentile of all elements. */ + + maxElementsSize = 0; + move16(); + maxElementsCapacity = nElementsToIgnore + 1; + move16(); + assert( maxElementsCapacity <= JBM_MAX_CIRCULAR_ELEMENTS ); + IF ( h->readPos <= h->writePos ) + { + /* no wrap around */ + /* calc statistics for [readPos;writePos[ */ + FOR ( i = h->readPos; i != h->writePos; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + JB4_CIRCULARBUFFER_calcPercentile( maxElements, &maxElementsSize, maxElementsCapacity, h->data[i] ); + } + } + ELSE + { + /* wrap around */ + /* calc statistics for [readPos;capacity[ */ + FOR ( i = h->readPos; i != h->capacity; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + JB4_CIRCULARBUFFER_calcPercentile( maxElements, &maxElementsSize, maxElementsCapacity, h->data[i] ); + } + /* calc statistics for [0;writePos[ */ + FOR ( i = 0; i != h->writePos; ++i ) + { + IF ( LT_32( h->data[i], minEle ) ) + { + minEle = h->data[i]; + move32(); + } + JB4_CIRCULARBUFFER_calcPercentile( maxElements, &maxElementsSize, maxElementsCapacity, h->data[i] ); + } + } + + *pPercentile = maxElements[0]; + move32(); + *pMin = minEle; + move32(); + + return; +} + + +/* Calculates percentile by selecting greatest elements. */ +static void JB4_CIRCULARBUFFER_calcPercentile( + JB4_CIRCULARBUFFER_ELEMENT *elements, + UWord16 *size, + const UWord16 capacity, + JB4_CIRCULARBUFFER_ELEMENT newElement ) +{ + UWord16 i, j; + + /* insert newElement IF elements buffer is not yet full */ + IF ( *size < capacity ) + { + FOR ( i = 0; i != *size; ++i ) + { + IF ( LE_32( newElement, elements[i] ) ) + { + /* insert newElement at index i */ + FOR ( j = *size; j > i; --j ) + { + elements[j] = elements[j - 1]; + move32(); + } + elements[i] = newElement; + move32(); + ++*size; + return; + } + } + /* newElement is maximum, just append it */ + elements[*size] = newElement; + ++*size; + return; + } + + /* check IF newElement is too small to be inserted in elements buffer */ + IF ( LE_32( newElement, elements[0] ) ) + { + return; + } + + /* select position to insert newElement to elements */ + FOR ( i = *size - 1; i != 0; --i ) + { + IF ( GE_32( newElement, elements[i] ) ) + { + /* insert newElement at index i */ + FOR ( j = 0; j < i; j++ ) + { + elements[j] = elements[1 + j]; + move32(); + } + elements[i] = newElement; + move32(); + return; + } + } + /* newElement is just greater than first on in elements buffer */ + elements[0] = newElement; + move32(); + + return; +} +#else /** Calculates percentile by selecting greatest elements. * This function partial sorts all given elements in the given buffer. * @param[in,out] elements ascending sorted buffer of selected greatest elements @@ -484,3 +963,4 @@ static void JB4_CIRCULARBUFFER_calcPercentile( return; } +#endif /* IVAS_FLOAT_FIXED */ diff --git a/lib_dec/jbm_jb4_circularbuffer.h b/lib_dec/jbm_jb4_circularbuffer.h index e14144b329e0984730809384e565db6c9b42b6ae..6b12df32151b1dd3c9c7385e5c9d286d6f2f11b8 100644 --- a/lib_dec/jbm_jb4_circularbuffer.h +++ b/lib_dec/jbm_jb4_circularbuffer.h @@ -40,6 +40,41 @@ #include "prot.h" #include "cnst.h" +#ifdef IVAS_FLOAT_FIXED +/** handle for circular buffer (FIFO) with fixed capacity */ +typedef struct JB4_CIRCULARBUFFER *JB4_CIRCULARBUFFER_HANDLE; + +/** type of circular buffer elements */ +typedef Word32 JB4_CIRCULARBUFFER_ELEMENT; + + +ivas_error JB4_CIRCULARBUFFER_Create( JB4_CIRCULARBUFFER_HANDLE *ph ); + +void JB4_CIRCULARBUFFER_Destroy( JB4_CIRCULARBUFFER_HANDLE *ph ); + +Word16 JB4_CIRCULARBUFFER_Init( JB4_CIRCULARBUFFER_HANDLE h, UWord16 capacity ); + +Word16 JB4_CIRCULARBUFFER_Enque( JB4_CIRCULARBUFFER_HANDLE h, JB4_CIRCULARBUFFER_ELEMENT element ); + +Word16 JB4_CIRCULARBUFFER_Deque( JB4_CIRCULARBUFFER_HANDLE h, JB4_CIRCULARBUFFER_ELEMENT *pElement ); + +JB4_CIRCULARBUFFER_ELEMENT JB4_CIRCULARBUFFER_Front( const JB4_CIRCULARBUFFER_HANDLE h ); + +JB4_CIRCULARBUFFER_ELEMENT JB4_CIRCULARBUFFER_Back( const JB4_CIRCULARBUFFER_HANDLE h ); + +Word16 JB4_CIRCULARBUFFER_IsEmpty( const JB4_CIRCULARBUFFER_HANDLE h ); + +Word16 JB4_CIRCULARBUFFER_IsFull( const JB4_CIRCULARBUFFER_HANDLE h ); + +UWord16 JB4_CIRCULARBUFFER_Size( const JB4_CIRCULARBUFFER_HANDLE h ); + +void JB4_CIRCULARBUFFER_Min( const JB4_CIRCULARBUFFER_HANDLE h, JB4_CIRCULARBUFFER_ELEMENT *pMin ); + +void JB4_CIRCULARBUFFER_Max( const JB4_CIRCULARBUFFER_HANDLE h, JB4_CIRCULARBUFFER_ELEMENT *pMax ); + +void JB4_CIRCULARBUFFER_MinAndPercentile( const JB4_CIRCULARBUFFER_HANDLE h, UWord16 nElementsToIgnore, JB4_CIRCULARBUFFER_ELEMENT *pMin, JB4_CIRCULARBUFFER_ELEMENT *pPercentile ); + +#else /** handle for circular buffer (FIFO) with fixed capacity */ typedef struct JB4_CIRCULARBUFFER *JB4_CIRCULARBUFFER_HANDLE; @@ -73,4 +108,6 @@ void JB4_CIRCULARBUFFER_Max( const JB4_CIRCULARBUFFER_HANDLE h, JB4_CIRCULARBUFF void JB4_CIRCULARBUFFER_MinAndPercentile( const JB4_CIRCULARBUFFER_HANDLE h, uint16_t nElementsToIgnore, JB4_CIRCULARBUFFER_ELEMENT *pMin, JB4_CIRCULARBUFFER_ELEMENT *pPercentile ); +#endif /* IVAS_FLOAT_FIXED*/ #endif /* JBM_JB4_CIRCULARBUFFER_H */ + diff --git a/lib_dec/jbm_jb4_inputbuffer.c b/lib_dec/jbm_jb4_inputbuffer.c index 852c959466ae8d21f2872bbde12373f50933d1f7..77398edf032efc67365572a0465855d4097b63db 100644 --- a/lib_dec/jbm_jb4_inputbuffer.c +++ b/lib_dec/jbm_jb4_inputbuffer.c @@ -47,6 +47,354 @@ #define WMC_TOOL_SKIP +#ifdef IVAS_FLOAT_FIXED +/** input buffer with fixed capacity */ +struct JB4_INPUTBUFFER +{ + /** elements of input buffer */ + JB4_INPUTBUFFER_ELEMENT *data; + /** maximum allowed number of elements plus one free element (to decide between full/empty buffer) */ + UWord16 capacity; + /** position of next enque operation */ + UWord16 writePos; + /** position of next deque operation */ + UWord16 readPos; + /** function to compare two elements */ + Word16 ( *compareFunction )( const JB4_INPUTBUFFER_ELEMENT first, const JB4_INPUTBUFFER_ELEMENT second, bool *replaceWithNewElementIfEqual ); +}; + + +/* Creates a input buffer */ +ivas_error JB4_INPUTBUFFER_Create( + JB4_INPUTBUFFER_HANDLE *ph ) +{ + JB4_INPUTBUFFER_HANDLE h; + + IF ( ( h = malloc( sizeof( struct JB4_INPUTBUFFER ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JBM\n" ) ); + } + + h->data = NULL; + h->capacity = 0; + move16(); + h->writePos = 0; + move16(); + h->readPos = 0; + move16(); + h->compareFunction = NULL; + + *ph = h; + + return IVAS_ERR_OK; +} + + +/* Destroys the input buffer */ +void JB4_INPUTBUFFER_Destroy( + JB4_INPUTBUFFER_HANDLE *ph ) +{ + JB4_INPUTBUFFER_HANDLE h; + + IF ( !ph ) + { + return; + } + h = *ph; + IF ( !h ) + { + return; + } + + IF ( h->data ) + { + free( h->data ); + } + + free( h ); + *ph = NULL; + + return; +} + + +/* Initializes a input buffer with a fixed maximum allowed number of elements */ +ivas_error JB4_INPUTBUFFER_Init( + JB4_INPUTBUFFER_HANDLE h, + UWord16 capacity, + Word16 ( *compareFunction )( const JB4_INPUTBUFFER_ELEMENT first, const JB4_INPUTBUFFER_ELEMENT second, bool *replaceWithNewElementIfEqual ) ) +{ + + /* keep one element free to be able to decide between full/empty buffer */ + ++capacity; + IF ( ( h->data = malloc( capacity * sizeof( JB4_INPUTBUFFER_ELEMENT ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JBM\n" ) ); + } + + h->capacity = capacity; + move16(); + h->writePos = 0; + move16(); + h->readPos = 0; + move16(); + h->compareFunction = compareFunction; + move16(); + + return IVAS_ERR_OK; +} + + +Word16 JB4_INPUTBUFFER_Enque( + JB4_INPUTBUFFER_HANDLE h, + JB4_INPUTBUFFER_ELEMENT element, + JB4_INPUTBUFFER_ELEMENT *replacedElement ) +{ + UWord16 j, size; + Word16 low, high, middle, diff; + UWord16 insertPos; + UWord16 canMoveRight; + UWord16 canMoveLeft; + bool replace; + + *replacedElement = NULL; + + size = JB4_INPUTBUFFER_Size( h ); + IF ( size >= h->capacity - 1 ) + { + return -1; + } + + /* appending the first element is straight forward */ + IF ( size == 0U ) + { + h->data[h->writePos] = element; + ++h->writePos; + IF ( h->writePos == h->capacity ) + { + h->writePos = 0; + move16(); + } + return 0; + } + + /* there's a high probability that the new element can be appended at the back */ + IF ( GT_32( h->compareFunction( element, JB4_INPUTBUFFER_Back( h ), &replace ), 0 ) ) + { + h->data[h->writePos] = element; + ++h->writePos; + IF ( h->writePos == h->capacity ) + { + h->writePos = 0; + move16(); + } + return 0; + } + + /* out of order: use binary search to get the position to insert */ + low = 0; + move16(); + high = size - 1; + WHILE ( LE_16( low, high ) ) + { + middle = add( low, shr( sub( high, low ), 1 ) ); + diff = h->compareFunction( element, JB4_INPUTBUFFER_Element( h, middle ), &replace ); + IF ( LT_16( diff, 0 ) ) + { + high = sub( middle, 1 ); + } + ELSE IF ( GT_16( diff, 0 ) ) + { + low = add( middle, 1 ); + } + ELSE /* an element with same index is already stored */ + { + IF ( replace != 0 ) + { + *replacedElement = h->data[( h->readPos + middle ) % h->capacity]; + h->data[( h->readPos + middle ) % h->capacity] = element; + return 0; + } + return 1; + } + } + + + insertPos = ( h->readPos + low ) % h->capacity; + IF ( h->readPos < h->writePos ) + { + canMoveRight = 1; + move16(); + canMoveLeft = h->readPos > 0; + move16(); + } + ELSE + { + canMoveRight = insertPos < h->writePos; + move16(); + canMoveLeft = insertPos > h->writePos; + move16(); + } + + assert( canMoveRight != 0 || canMoveLeft != 0 ); + + IF ( canMoveRight ) + { + /* move higher elements to the right and insert at insertPos */ + FOR ( j = h->writePos; j > insertPos; --j ) + { + h->data[j] = h->data[j - 1]; + } + + h->data[insertPos] = element; + ++h->writePos; + IF ( h->writePos == h->capacity ) + { + h->writePos = 0; + move16(); + } + } + ELSE + { + /* move lower elements to the left and insert before insertPos */ + FOR ( j = 0; j < low; j++ ) + { + h->data[h->readPos - 1 + j] = h->data[h->readPos + j]; + } + + h->data[insertPos - 1] = element; + --h->readPos; + assert( (Word16) h->readPos >= 0 ); + } + + return 0; +} + + +Word16 JB4_INPUTBUFFER_Deque( + JB4_INPUTBUFFER_HANDLE h, + JB4_INPUTBUFFER_ELEMENT *pElement ) +{ + IF ( JB4_INPUTBUFFER_IsEmpty( h ) ) + { + return -1; + } + + *pElement = h->data[h->readPos]; + ++h->readPos; + IF ( h->readPos == h->capacity ) + { + h->readPos = 0; + move16(); + } + + return 0; +} + + +/* Returns the first element. */ +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Front( + const JB4_INPUTBUFFER_HANDLE h ) +{ + JB4_INPUTBUFFER_ELEMENT ret; + + + ret = h->data[h->readPos]; + + return ret; +} + + +/* Returns the last element. */ +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Back( + const JB4_INPUTBUFFER_HANDLE h ) +{ + JB4_INPUTBUFFER_ELEMENT ret; + + IF ( h->writePos != 0U ) + { + ret = h->data[h->writePos - 1]; + } + ELSE + { + ret = h->data[h->capacity - 1]; + } + + return ret; +} + + +/* Returns the element with the given index (0 means front element). */ +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Element( + const JB4_INPUTBUFFER_HANDLE h, + UWord16 index ) +{ + JB4_INPUTBUFFER_ELEMENT ret; + + /* return h->data[(h->readPos + index) % h->capacity] without error handling */ + IF ( h->readPos + index < h->capacity ) + { + ret = h->data[h->readPos + index]; + } + ELSE + { + /* wrap around */ + ret = h->data[h->readPos + index - h->capacity]; + } + + return ret; +} + + +Word16 JB4_INPUTBUFFER_IsEmpty( + const JB4_INPUTBUFFER_HANDLE h ) +{ + Word16 ret; + + ret = h->readPos == h->writePos; + move16(); + + return ret; +} + + +Word16 JB4_INPUTBUFFER_IsFull( + const JB4_INPUTBUFFER_HANDLE h ) +{ + Word16 ret; + + ret = 0; + move16(); + IF ( JB4_INPUTBUFFER_Size( h ) == h->capacity - 1 ) + { + ret = 1; + move16(); + } + + return ret; +} + + +UWord16 JB4_INPUTBUFFER_Size( + const JB4_INPUTBUFFER_HANDLE h ) +{ + UWord16 ret; + + IF ( h->readPos <= h->writePos ) + { + ret = h->writePos - h->readPos; + move16(); + } + ELSE + { + /* wrap around */ + ret = h->writePos + h->capacity - h->readPos; + move16(); + } + + return ret; +} +#else /** input buffer with fixed capacity */ struct JB4_INPUTBUFFER { @@ -372,5 +720,6 @@ uint16_t JB4_INPUTBUFFER_Size( return ret; } +#endif /* IVAS_FLOAT_FIXED */ #undef WMC_TOOL_SKIP diff --git a/lib_dec/jbm_jb4_inputbuffer.h b/lib_dec/jbm_jb4_inputbuffer.h index 6087ecb20717b76c316e2349ca79a0e95131629e..1256f370b893bf6aa06f3ffca9324d1901a613ee 100644 --- a/lib_dec/jbm_jb4_inputbuffer.h +++ b/lib_dec/jbm_jb4_inputbuffer.h @@ -44,6 +44,34 @@ #include "options.h" +#ifdef IVAS_FLOAT_FIXED +typedef struct JB4_INPUTBUFFER *JB4_INPUTBUFFER_HANDLE; + +typedef void *JB4_INPUTBUFFER_ELEMENT; + +ivas_error JB4_INPUTBUFFER_Create( JB4_INPUTBUFFER_HANDLE *ph ); + +void JB4_INPUTBUFFER_Destroy( JB4_INPUTBUFFER_HANDLE *ph ); + +ivas_error JB4_INPUTBUFFER_Init( JB4_INPUTBUFFER_HANDLE h, UWord16 capacity, Word16 ( *compareFunction )( const JB4_INPUTBUFFER_ELEMENT newElement, const JB4_INPUTBUFFER_ELEMENT arrayElement, bool *replaceWithNewElementIfEqual ) ); + +Word16 JB4_INPUTBUFFER_Enque( JB4_INPUTBUFFER_HANDLE h, JB4_INPUTBUFFER_ELEMENT element, JB4_INPUTBUFFER_ELEMENT *replacedElement ); + +Word16 JB4_INPUTBUFFER_Deque( JB4_INPUTBUFFER_HANDLE h, JB4_INPUTBUFFER_ELEMENT *pElement ); + +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Front( const JB4_INPUTBUFFER_HANDLE h ); + +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Back( const JB4_INPUTBUFFER_HANDLE h ); + +JB4_INPUTBUFFER_ELEMENT JB4_INPUTBUFFER_Element( const JB4_INPUTBUFFER_HANDLE h, UWord16 index ); + +Word16 JB4_INPUTBUFFER_IsEmpty( const JB4_INPUTBUFFER_HANDLE h ); + +Word16 JB4_INPUTBUFFER_IsFull( const JB4_INPUTBUFFER_HANDLE h ); + +UWord16 JB4_INPUTBUFFER_Size( const JB4_INPUTBUFFER_HANDLE h ); + +#else typedef struct JB4_INPUTBUFFER *JB4_INPUTBUFFER_HANDLE; typedef void *JB4_INPUTBUFFER_ELEMENT; @@ -70,4 +98,5 @@ int16_t JB4_INPUTBUFFER_IsFull( const JB4_INPUTBUFFER_HANDLE h ); uint16_t JB4_INPUTBUFFER_Size( const JB4_INPUTBUFFER_HANDLE h ); +#endif /* IVAS_FLOAT_FIXED */ #endif /* JBM_JB4_INPUTBUFFER_H */ diff --git a/lib_dec/jbm_jb4_jmf.c b/lib_dec/jbm_jb4_jmf.c index 99abd75e5adc4689aedaae1985113f2befe4ddf7..376db885d28a7cd77738d05cb6b2e1519c6965fc 100644 --- a/lib_dec/jbm_jb4_jmf.c +++ b/lib_dec/jbm_jb4_jmf.c @@ -50,7 +50,320 @@ #include "jbm_jb4_circularbuffer.h" /* instrumentation */ +#ifdef IVAS_FLOAT_FIXED +/** jitter measure fifo - a fifo used for windowed measure of network status */ +struct JB4_JMF +{ + /** scale of system time and RTP time stamps */ + Word16 timeScale; + /** the window size of the fifo as time in sysTimeScale */ + UWord16 maxWindowDuration; + /** considered fraction in 1/1000 units, e.g. 900 ignores 10% of the highest samples */ + UWord16 consideredFraction; + + /** fifo containing the delay entries (ordered by receive time) */ + JB4_CIRCULARBUFFER_HANDLE fifo; + /** fifo containing the offset entries (ordered by receive time) */ + JB4_CIRCULARBUFFER_HANDLE offsetFifo; + /** fifo containing the RTP times of the values in offsetFifo (ordered by receive time) */ + JB4_CIRCULARBUFFER_HANDLE timeStampFifo; + /** flag if the first packet was already pushed */ + Word16 firstPacketPushed; + /** last packets system time in microseconds */ + Word32 lastSysTime; + /** RTP time stamp of the last pushed packet */ + Word32 lastRtpTimeStamp; + /** last packets calculated delay value */ + Word32 lastDelay; + /** number of elements to ignore for percentile calculation - value set within init */ + Word16 nElementsToIgnore; +}; + + +/** helper function to add an entry at back of the buffer */ +static void JB4_JMF_pushBack( JB4_JMF_HANDLE h, const Word32 delay, const Word32 offset, const UWord32 time ); + +/** helper function to remove an entry from the front of the buffer */ +static void JB4_JMF_popFront( JB4_JMF_HANDLE h ); + + +ivas_error JB4_JMF_Create( + JB4_JMF_HANDLE *ph ) +{ + JB4_JMF_HANDLE h; + ivas_error error; + + IF ( ( h = malloc( sizeof( struct JB4_JMF ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JBM\n" ) ); + } + + IF ( NE_32( ( error = JB4_CIRCULARBUFFER_Create( &h->fifo ) ), IVAS_ERR_OK ) ) + { + return error; + } + IF ( NE_32( ( JB4_CIRCULARBUFFER_Create( &h->offsetFifo ) ), IVAS_ERR_OK ) ) + { + return error; + } + IF ( NE_32( ( JB4_CIRCULARBUFFER_Create( &h->timeStampFifo ) ), IVAS_ERR_OK ) ) + { + return error; + } + + h->timeScale = 1000; + move16(); + h->consideredFraction = 1000; + move16(); + h->firstPacketPushed = 0; + move16(); + h->lastSysTime = 0; + move32(); + h->lastRtpTimeStamp = 0; + move32(); + h->lastDelay = 0; + move32(); + h->nElementsToIgnore = 0; + move16(); + + *ph = h; + + return IVAS_ERR_OK; +} + + +void JB4_JMF_Destroy( + JB4_JMF_HANDLE *ph ) +{ + JB4_JMF_HANDLE h; + + IF ( !ph ) + { + return; + } + h = *ph; + IF ( !h ) + { + return; + } + + JB4_CIRCULARBUFFER_Destroy( &h->fifo ); + JB4_CIRCULARBUFFER_Destroy( &h->offsetFifo ); + JB4_CIRCULARBUFFER_Destroy( &h->timeStampFifo ); + + free( h ); + *ph = NULL; + + return; +} + + +/* function to set the window size of the fifo and the fraction which will be considered */ +Word16 JB4_JMF_Init( + JB4_JMF_HANDLE h, + const Word16 timeScale, + const UWord16 windowSize, + const UWord16 windowDuration, + const UWord16 consideredFraction ) +{ + + /* check parameters */ + IF ( windowSize != 0U && consideredFraction * windowSize / 1000 < 2 ) + { + return -1; + } + IF ( consideredFraction > 1000 ) + { + return -1; + } + + /* store values */ + h->timeScale = timeScale; + move16(); + h->maxWindowDuration = windowDuration; + move16(); + h->consideredFraction = consideredFraction; + move16(); + + JB4_CIRCULARBUFFER_Init( h->fifo, windowSize ); + JB4_CIRCULARBUFFER_Init( h->offsetFifo, windowSize ); + JB4_CIRCULARBUFFER_Init( h->timeStampFifo, windowSize ); + + h->nElementsToIgnore = (UWord16) ( windowSize * ( 1000 - consideredFraction ) / 1000 ); + + return 0; +} + + +/* function to calculate delay for the current packet */ +Word16 JB4_JMF_PushPacket( + JB4_JMF_HANDLE h, + const UWord32 sysTime, + const UWord32 rtpTimeStamp ) +{ + Word32 rtpTimeDiff, sysTimeDiff; + Word32 offset, delay; + + /* check if this is the first entry */ + IF ( EQ_16( h->firstPacketPushed, 0 ) ) + { + h->firstPacketPushed = 1; + move16(); + h->lastSysTime = sysTime; + move32(); + h->lastRtpTimeStamp = rtpTimeStamp; + move32(); + return 0; + } + + rtpTimeDiff = (Word32) ( rtpTimeStamp - h->lastRtpTimeStamp ); + move32(); + sysTimeDiff = sysTime - h->lastSysTime; + move32(); + offset = sysTime - rtpTimeStamp; + move32(); + + /* get the delay (yes, signed!!!!) */ + delay = sysTimeDiff - rtpTimeDiff + h->lastDelay; + move32(); + + /* remember old values */ + h->lastSysTime = sysTime; + move32(); + h->lastRtpTimeStamp = rtpTimeStamp; + move32(); + /* reset delay if absolute value is greater than 60s + * to avoid overflow caused by clockdrift */ + IF ( delay > 60 * h->timeScale || delay < -60 * h->timeScale ) + { + h->lastDelay = 0; + move32(); + } + ELSE + { + h->lastDelay = delay; + move32(); + } + + JB4_JMF_pushBack( h, delay, offset, rtpTimeStamp ); + + return 0; +} + + +/* function to get the current jitter */ +Word16 JB4_JMF_Jitter( + const JB4_JMF_HANDLE h, + UWord32 *jitter ) +{ + JB4_CIRCULARBUFFER_ELEMENT min_ele, percentile; + + /* sanity check (must not be empty) and return invalid result if there is only one entry */ + IF ( JB4_CIRCULARBUFFER_Size( h->fifo ) < 2U ) + { + return -1; + } + + JB4_CIRCULARBUFFER_MinAndPercentile( h->fifo, h->nElementsToIgnore, &min_ele, &percentile ); + + /* return the difference between the highest considered and the smallest value */ + *jitter = percentile - min_ele; + assert( percentile >= min_ele ); + + return 0; +} + + +/* function to get the minimum offset between received time and time stamp of all entries in the fifo */ +Word16 JB4_JMF_MinOffset( + const JB4_JMF_HANDLE h, + Word32 *offset ) +{ + JB4_CIRCULARBUFFER_ELEMENT min_ele; + + IF ( JB4_CIRCULARBUFFER_IsEmpty( h->offsetFifo ) ) + { + return -1; + } + + JB4_CIRCULARBUFFER_Min( h->offsetFifo, &min_ele ); + + *offset = min_ele; + move32(); + + return 0; +} + + +/***************************************************************************** + **************************** private functions ****************************** + *****************************************************************************/ + +/* helper function to add entry at back of the buffer */ +static void JB4_JMF_pushBack( + JB4_JMF_HANDLE h, + const Word32 delay, + const Word32 offset, + const UWord32 time ) +{ + Word32 minTime, maxTime; + UWord32 duration; + + /* check for size and discard first entry if too big */ + IF ( JB4_CIRCULARBUFFER_IsFull( h->fifo ) ) + { + JB4_JMF_popFront( h ); + } + + /* push back new entry */ + JB4_CIRCULARBUFFER_Enque( h->fifo, delay ); + JB4_CIRCULARBUFFER_Enque( h->offsetFifo, offset ); + JB4_CIRCULARBUFFER_Enque( h->timeStampFifo, time ); + + /* check for duration and discard first entries if too long */ + minTime = JB4_CIRCULARBUFFER_Front( h->timeStampFifo ); + maxTime = JB4_CIRCULARBUFFER_Back( h->timeStampFifo ); + IF ( maxTime > minTime ) + { + duration = maxTime - minTime; + move32(); + WHILE ( duration > h->maxWindowDuration ) + { + JB4_JMF_popFront( h ); + minTime = JB4_CIRCULARBUFFER_Front( h->timeStampFifo ); + IF ( maxTime <= minTime ) + { + BREAK; + } + duration = maxTime - minTime; + move32(); + } + } + + return; +} + + +/* helper function to remove an entry from the front of the buffer */ +static void JB4_JMF_popFront( + JB4_JMF_HANDLE h ) +{ + JB4_CIRCULARBUFFER_ELEMENT tmpElement; + + /* try to remove one element - fails if empty */ + IF ( JB4_CIRCULARBUFFER_Deque( h->fifo, &tmpElement ) != 0 ) + { + return; + } + + /* also remove offset entry */ + JB4_CIRCULARBUFFER_Deque( h->offsetFifo, &tmpElement ); + JB4_CIRCULARBUFFER_Deque( h->timeStampFifo, &tmpElement ); + + return; +} +#else /** jitter measure fifo - a fifo used for windowed measure of network status */ struct JB4_JMF { @@ -338,3 +651,4 @@ static void JB4_JMF_popFront( return; } +#endif /* IVAS_FLOAT_FIXED */ diff --git a/lib_dec/jbm_jb4_jmf.h b/lib_dec/jbm_jb4_jmf.h index 5efff7cd5ee36c4a91bf2f4cf0dc3b400161f590..dcd083db46ba3ef19b257bb5816d8ed8a84f5054 100644 --- a/lib_dec/jbm_jb4_jmf.h +++ b/lib_dec/jbm_jb4_jmf.h @@ -42,6 +42,23 @@ #include #include "options.h" +#ifdef IVAS_FLOAT_FIXED +/** handle for jitter measure fifo - a fifo used for windowed measure of network status */ +typedef struct JB4_JMF *JB4_JMF_HANDLE; + +ivas_error JB4_JMF_Create( JB4_JMF_HANDLE *ph ); + +void JB4_JMF_Destroy( JB4_JMF_HANDLE *ph ); + +Word16 JB4_JMF_Init( JB4_JMF_HANDLE h, const Word16 timeScale, const UWord16 windowSize, const UWord16 windowDuration, const UWord16 consideredFraction ); + +Word16 JB4_JMF_PushPacket( JB4_JMF_HANDLE h, const UWord32 sysTime, const UWord32 rtpTimeStamp ); + +Word16 JB4_JMF_Jitter( const JB4_JMF_HANDLE h, UWord32 *jitter ); + +Word16 JB4_JMF_MinOffset( const JB4_JMF_HANDLE h, Word32 *offset ); + +#else /** handle for jitter measure fifo - a fifo used for windowed measure of network status */ typedef struct JB4_JMF *JB4_JMF_HANDLE; @@ -56,6 +73,7 @@ int16_t JB4_JMF_PushPacket( JB4_JMF_HANDLE h, const uint32_t sysTime, const uint int16_t JB4_JMF_Jitter( const JB4_JMF_HANDLE h, uint32_t *jitter ); int16_t JB4_JMF_MinOffset( const JB4_JMF_HANDLE h, int32_t *offset ); +#endif /* IVAS_FLOAT_FIXED */ #endif /* JBM_JB4_JMF_H */ diff --git a/lib_dec/jbm_jb4sb.c b/lib_dec/jbm_jb4sb.c index 04a2aac31281cb568bb0763aaa5047ca5e84ccb9..92177ed715bcee217d2e365388389d208b5e5fdc 100644 --- a/lib_dec/jbm_jb4sb.c +++ b/lib_dec/jbm_jb4sb.c @@ -57,6 +57,1581 @@ #define MAXOFFSET 10 +#ifdef IVAS_FLOAT_FIXED +/*! Calculates the difference between two RTP timestamps - the diff is positive, if B 'later', negative otherwise */ +static Word32 JB4_rtpTimeStampDiff( const UWord32 tsA, const UWord32 tsB ); +/* function to calculate different options for the target playout delay */ +static void JB4_targetPlayoutDelay( const JB4_HANDLE h, UWord32 *targetMin, UWord32 *targetMax, UWord32 *targetDtx, UWord32 *targetStartUp ); +/*! function to do playout adaptation before playing the next data unit */ +/*! In case of time shrinking, data units will be dropped before the next data unit to play is returned and + * in case of time stretching a empty data unit is returned and the frame should be concealed. + * @param[in] now current system time + * @param[out] dataUnit the next data unit to play + * @param[out] scale the scale in percent used as target for time scaling of the returned data unit + * @param[out] maxScaling the maximum allowed external time scaling */ +static Word16 JB4_adaptPlayout( JB4_HANDLE h, UWord32 sysTime, UWord32 extBufferedTime, JB4_DATAUNIT_HANDLE *pDataUnit, UWord32 *scale, UWord32 *maxScaling ); +/*! function to do playout adaptation before playing the first data unit */ +/*! @param[in] now current system time + * @param[out] prebuffer true, if the data unit should be prebuffered */ +static void JB4_adaptFirstPlayout( JB4_HANDLE h, UWord32 sysTime, bool *prebuffer ); +/*! function for playout adaptation while active (no DTX) */ +static void JB4_adaptActivePlayout( JB4_HANDLE h, UWord32 extBufferedTime, UWord32 *scale, UWord32 *maxScaling ); +/*! function for playout adaptation while DTX */ +static void JB4_adaptDtxPlayout( JB4_HANDLE h, UWord32 sysTime, bool *stretchTime ); +/*! function to look into the buffer and check if it makes sense to drop a data unit */ +/*! @param[out] dropEarly true, if a data unit could be dropped early + * @param[out] buffered the buffered time span in timeScale units + * @return true, if a data unit could be dropped */ +static Word16 JB4_inspectBufferForDropping( const JB4_HANDLE h, bool *dropEarly, UWord32 *buffered ); +/* function to look into the buffer and check if it makes sense to drop a data unit during DTX */ +static Word16 JB4_checkDtxDropping( const JB4_HANDLE h ); +/*! function to estimate the short term jitter */ +static void JB4_estimateShortTermJitter( JB4_HANDLE h, const UWord32 rcvTime, const UWord32 rtpTimeStamp ); +/*! function to pop a data unit from the buffer */ +static void JB4_popFromBuffer( JB4_HANDLE h, const UWord32 sysTime, JB4_DATAUNIT_HANDLE *pDataUnit ); +/*! function to drop a data unit from the buffer - updates nShrinked */ +static void JB4_dropFromBuffer( JB4_HANDLE h ); +/*! function to calculate the playout delay based on the current jitter */ +/*! @param[in] playTime the system time when the data unit will be played + * @param[in] timeStamp the time stamp of the data unit to played + * @param[out] delay the calculated playout delay */ +static Word16 JB4_playoutDelay( const JB4_HANDLE h, const UWord32 playTime, const UWord32 rtpTimeStamp, UWord32 *delay ); +/*! function to update lastPlayoutDelay and lastTargetTime after popFromBuffer() */ +static void JB4_updateLastTimingMembers( JB4_HANDLE h, const UWord32 playTime, const UWord32 rtpTimeStamp ); +/*! function to compare the RTP time stamps of two data units: newElement==arrayElement ? 0 : (newElement>arrayElement ? +1 : -1) */ +static Word16 JB4_inputBufferCompareFunction( const JB4_INPUTBUFFER_ELEMENT newElement, const JB4_INPUTBUFFER_ELEMENT arrayElement, bool *replaceWithNewElementIfEqual ); + + +/*! Jitter Buffer Management Interface */ +struct JB4 +{ + /*! @name statistics for user */ + /*@{ */ + /*! the number of late lost data units */ + UWord32 nLateLost; + /*! the number of data units that were available (not NULL) at playout time */ + UWord32 nAvailablePopped; + /*! the number of data units that were not available (NULL) at playout time */ + UWord32 nUnavailablePopped; + /*! the number of unavailable pops since the last available one - used as temp value for nLost and nStretched */ + UWord32 nLostOrStretched; + /*! the number of data units that were lost at playout time */ + UWord32 nLost; + /*! the number of empty data units inserted for playout adaptation */ + UWord32 nStretched; + /*! the number of data units dropped for playout adaptation */ + /*! This function counts all time shrinking events, no matter if a dropped data unit was actually available. */ + UWord32 nShrinked; + /*! the number of data units that were returned to create comfort noice (including NULL) */ + UWord32 nComfortNoice; + /*! the number of jitter induced concealment operations (as defined in 3GPP TS 26.114) */ + UWord32 jitterInducedConcealments; + /*! the target playout delay of the last returned data unit */ + UWord32 targetPlayoutDelay; + /*! the target playout time of the last returned data unit */ + UWord32 lastTargetTime; + /*@} */ + /*! @name internal configuration values - do not change!!! */ + /*@{ */ + /*! internal time scale for all calculations */ + Word16 timeScale; + /*! internal frame duration in timeScale units */ + UWord32 frameDuration; + /*@} */ + /*! @name jitter buffer configuration values */ + /*@{ */ + /*! the allowed delay reserve in addition to network jitter to reduce late-loss [milliseconds] */ + Word16 safetyMargin; + /*@} */ + /*! @name data for short term jitter estimation */ + /*@{ */ + /*! short term jitter measure FIFO */ + JB4_JMF_HANDLE stJmf; + /*! FIFO of short term jitter values */ + JB4_CIRCULARBUFFER_HANDLE stJitterFifo; + /*! FIFO of RTP time stamps for the values stored in stJitterFifo */ + JB4_CIRCULARBUFFER_HANDLE stTimeStampFifo; + /*! short term jitter */ + UWord32 stJitter; + /*@} */ + /*! @name jitter buffer data */ + /*@{ */ + /*! true, if a data unit was already popped from the buffer */ + bool firstDataUnitPopped; + /*! system time of the previous JB4_PopDataUnit() call */ + UWord32 prevPopSysTime; + /*! RTP timestamp of the last played/dropped data unit that was actually available */ + UWord32 lastReturnedTs; + /*! true, if the last popped data unit contained no active signal, i.e. silence -> hint for DTX */ + bool lastPoppedWasSilence; + /*! the playout time minus the minimum offset of the last played data unit in microseconds */ + Word32 lastPlayoutOffset; + /*! RTP time stamp of the next data unit that is expected to be fetched from the buffer */ + UWord32 nextExpectedTs; + Word16 rfOffset2Active; + Word16 rfOffset3Active; + Word16 rfOffset5Active; + Word16 rfOffset7Active; + Word32 rfDelay; + /*! long term jitter measure FIFO */ + JB4_JMF_HANDLE ltJmf; + + UWord32 FecOffWinLen; + UWord32 FecOffWin[10]; + UWord32 optimum_offset; + + float netLossRate; + Word32 netLossRate_fx; + Word32 nPartialCopiesUsed; + Word32 last_nLost; + Word32 last_ntot; + + UWord32 totWin; + bool pre_partial_frame; + /*@} */ + + /*! @name members to store the data units */ + /*@{ */ + /*! the data unit buffer */ + JB4_INPUTBUFFER_HANDLE inputBuffer; + struct JB4_DATAUNIT memorySlots[MAX_JBM_SLOTS]; + JB4_DATAUNIT_HANDLE freeMemorySlots[MAX_JBM_SLOTS]; + UWord16 nFreeMemorySlots; + /*@} */ +}; /* JB4 */ + + +ivas_error JB4_Create( + JB4_HANDLE *ph ) +{ + Word16 iter; + JB4_HANDLE h; + ivas_error error; + + IF ( ( h = malloc( sizeof( struct JB4 ) ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JB4 structure\n" ) ); + } + + /* statistics for user */ + h->nLateLost = 0; + move32(); + h->nAvailablePopped = 0; + move32(); + h->nUnavailablePopped = 0; + move32(); + h->nLostOrStretched = 0; + move32(); + h->nLost = 0; + move32(); + h->nStretched = 0; + move32(); + h->nShrinked = 0; + move32(); + h->nComfortNoice = 0; + move32(); + h->jitterInducedConcealments = 0; + move32(); + h->targetPlayoutDelay = 0; + move32(); + h->lastTargetTime = 0; + move32(); + /* internal configuration values - do not change!!! */ + h->timeScale = 0; + move32(); + h->frameDuration = 0; + move32(); + + /* jitter buffer configuration values: done in JB4_Init() */ + /* short term jitter evaluation */ + IF ( NE_32( ( error = JB4_JMF_Create( &h->stJmf ) ), IVAS_ERR_OK ) ) + { + return error; + } + IF ( NE_32( ( error = JB4_CIRCULARBUFFER_Create( &h->stJitterFifo ) ), IVAS_ERR_OK ) ) + { + return error; + } + IF ( NE_32( ( error = JB4_CIRCULARBUFFER_Create( &h->stTimeStampFifo ) ), IVAS_ERR_OK ) ) + { + return error; + } + h->stJitter = 0; + move32(); + + /* jitter buffer data */ + h->firstDataUnitPopped = false; + h->prevPopSysTime = 0; + move32(); + h->lastReturnedTs = 0; + move32(); + h->lastPoppedWasSilence = false; + h->lastPlayoutOffset = 0; + move32(); + h->nextExpectedTs = 0; + move32(); + h->rfOffset2Active = 0; + move16(); + h->rfOffset3Active = 0; + move16(); + h->rfOffset5Active = 0; + move16(); + h->rfOffset7Active = 0; + move16(); + h->rfDelay = 0; + move32(); + JB4_JMF_Create( &h->ltJmf ); + h->pre_partial_frame = 0; + + h->FecOffWinLen = 0; + FOR ( iter = 0; iter < 10; iter++ ) + { + h->FecOffWin[iter] = 0; + move32(); + } + h->optimum_offset = 3; + move32(); + h->totWin = 0; + move32(); + h->netLossRate = 0.0f; + h->netLossRate_fx = 0; + move32(); + h->nPartialCopiesUsed = 0; + move32(); + h->last_nLost = 0; + move32(); + h->last_ntot = 0; + move32(); + + /* members to store the data units */ + IF ( ( error = JB4_INPUTBUFFER_Create( &h->inputBuffer ) ) != IVAS_ERR_OK ) + { + return error; + } + + /* allocate memory for data units */ + FOR ( iter = 0; iter < MAX_JBM_SLOTS; ++iter ) + { + IF ( ( h->memorySlots[iter].data = malloc( MAX_AU_SIZE ) ) == NULL ) + { + return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for JB4 structure\n" ) ); + } + h->freeMemorySlots[iter] = &h->memorySlots[iter]; + } + h->nFreeMemorySlots = MAX_JBM_SLOTS; + move16(); + *ph = h; + + return IVAS_ERR_OK; +} + + +void JB4_Destroy( + JB4_HANDLE *ph ) +{ + JB4_HANDLE h; + UWord16 i; + + IF ( !ph ) + { + return; + } + h = *ph; + IF ( !h ) + { + return; + } + + JB4_JMF_Destroy( &h->stJmf ); + JB4_CIRCULARBUFFER_Destroy( &h->stJitterFifo ); + JB4_CIRCULARBUFFER_Destroy( &h->stTimeStampFifo ); + JB4_JMF_Destroy( &h->ltJmf ); + JB4_INPUTBUFFER_Destroy( &h->inputBuffer ); + + FOR ( i = 0; i < MAX_JBM_SLOTS; ++i ) + { + free( h->memorySlots[i].data ); + } + + free( h ); + *ph = NULL; + + return; +} + + +ivas_error JB4_Init( + JB4_HANDLE h, + const Word16 safetyMargin ) +{ + UWord16 ltJmfSize, stFifoSize, stJmfSize, stJmfAllowedLateLoss; + UWord16 inputBufferCapacity; + ivas_error error; + + /* internal timescale is 1000, frame duration is 20ms */ + h->timeScale = 1000; /* ms */ + move16(); + h->frameDuration = 20; /* ms */ + move32(); + + /* jitter buffer configuration values */ + h->safetyMargin = safetyMargin; + move16(); + + /* long term jitter measure FIFO: 500 frames and 10s */ + ltJmfSize = 10000; + move16(); + JB4_JMF_Init( h->ltJmf, h->timeScale, ltJmfSize / 20, ltJmfSize, 1000 ); + /* short term jitter evaluation */ + stFifoSize = 200; + move16(); + stJmfSize = 50; + move16(); + stJmfAllowedLateLoss = 60; /* 6%, e.g. ignore three packets out of 50 */ + move16(); + JB4_CIRCULARBUFFER_Init( h->stJitterFifo, stFifoSize ); + JB4_CIRCULARBUFFER_Init( h->stTimeStampFifo, stFifoSize ); + JB4_JMF_Init( h->stJmf, h->timeScale, stJmfSize, h->timeScale /* 1s */, (UWord16) ( 1000 - stJmfAllowedLateLoss ) ); + + inputBufferCapacity = MAX_JBM_SLOTS - 2; + move16(); + + IF ( ( error = JB4_INPUTBUFFER_Init( h->inputBuffer, inputBufferCapacity, JB4_inputBufferCompareFunction ) ) != IVAS_ERR_OK ) + { + return error; + } + + return IVAS_ERR_OK; +} + + +/* Returns a memory slot to store a new data unit */ +JB4_DATAUNIT_HANDLE JB4_AllocDataUnit( + JB4_HANDLE h ) +{ + JB4_DATAUNIT_HANDLE dataUnit; + while ( h->nFreeMemorySlots == 0 ) + { + assert( JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) == 0 ); + JB4_dropFromBuffer( h ); + } + + --h->nFreeMemorySlots; + dataUnit = h->freeMemorySlots[h->nFreeMemorySlots]; + h->freeMemorySlots[h->nFreeMemorySlots] = NULL; + assert( dataUnit != NULL ); + + return dataUnit; +} + + +/* Notifies the JBM that a data unit is no longer used and the memory can be reused */ +void JB4_FreeDataUnit( + JB4_HANDLE h, + JB4_DATAUNIT_HANDLE dataUnit ) +{ + assert( dataUnit != NULL ); + assert( h->nFreeMemorySlots < MAX_JBM_SLOTS ); + h->freeMemorySlots[h->nFreeMemorySlots] = dataUnit; + h->nFreeMemorySlots++; + + return; +} + + +Word16 JB4_PushDataUnit( + JB4_HANDLE h, + JB4_DATAUNIT_HANDLE dataUnit, + const UWord32 rcvTime ) +{ + JB4_DATAUNIT_HANDLE droppedDataUnit = NULL; + + assert( dataUnit->duration == h->frameDuration ); + assert( dataUnit->timeScale == (UWord16) h->timeScale ); + + /* ignore frames from too far in future (3 seconds) */ + IF ( h->firstDataUnitPopped && JB4_rtpTimeStampDiff( h->lastReturnedTs, dataUnit->timeStamp ) >= + (Word32) ( 50 * 3 * dataUnit->duration ) ) + { + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + + /* reserve space for one element to add: drop oldest if buffer is full */ + WHILE ( JB4_INPUTBUFFER_IsFull( h->inputBuffer ) ) + { + JB4_dropFromBuffer( h ); + } + assert( JB4_INPUTBUFFER_IsFull( h->inputBuffer ) == 0 ); + + /* do statistics on partial copy offset using active primary copies to + * avoid unexpected resets because RF_NO_DATA partial copies are dropped before JBM */ + IF ( dataUnit->silenceIndicator == 0 && dataUnit->partial_frame == 0 ) + { + IF ( EQ_16( dataUnit->partialCopyOffset, 0 ) ) + { + IF ( GT_16( h->rfOffset2Active, 0 ) ) + --h->rfOffset2Active; + IF ( GT_16( h->rfOffset3Active, 0 ) ) + --h->rfOffset3Active; + IF ( GT_16( h->rfOffset5Active, 0 ) ) + --h->rfOffset5Active; + IF ( GT_16( h->rfOffset7Active, 0 ) ) + --h->rfOffset7Active; + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 2 ) ) + { + h->rfOffset2Active = 100; + move16(); + h->rfOffset3Active = 0; + move16(); + h->rfOffset5Active = 0; + move16(); + h->rfOffset7Active = 0; + move16(); + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 3 ) ) + { + h->rfOffset2Active = 0; + move16(); + h->rfOffset3Active = 100; + move16(); + h->rfOffset5Active = 0; + move16(); + h->rfOffset7Active = 0; + move16(); + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 5 ) ) + { + h->rfOffset2Active = 0; + move16(); + h->rfOffset3Active = 0; + move16(); + h->rfOffset5Active = 100; + move16(); + h->rfOffset7Active = 0; + move16(); + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 7 ) ) + { + h->rfOffset2Active = 0; + move16(); + h->rfOffset3Active = 0; + move16(); + h->rfOffset5Active = 0; + move16(); + h->rfOffset7Active = 100; + move16(); + } + } + + IF ( dataUnit->partial_frame != 0 ) + { + /* check for "real" late loss: a frame with higher/same timestamp was already returned to be fed into decoder */ + IF ( h->firstDataUnitPopped && LE_32( JB4_rtpTimeStampDiff( h->lastReturnedTs, dataUnit->timeStamp ), 0 ) ) + { + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + + /* drop partial copy if the missing frame was already concealed */ + IF ( h->firstDataUnitPopped ) + { + IF ( LE_16( dataUnit->partialCopyOffset, 3 ) && LT_32( JB4_rtpTimeStampDiff( h->nextExpectedTs, dataUnit->timeStamp ), 0 ) ) + { + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 5 ) && LT_32( JB4_rtpTimeStampDiff( h->nextExpectedTs, dataUnit->timeStamp ), -40 ) ) + { + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + ELSE IF ( EQ_16( dataUnit->partialCopyOffset, 7 ) && LT_32( JB4_rtpTimeStampDiff( h->nextExpectedTs, dataUnit->timeStamp ), -80 ) ) + { + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + } + + /* try to store partial copy - will be dropped if primary copy already available */ + IF ( EQ_16( JB4_INPUTBUFFER_Enque( h->inputBuffer, dataUnit, (void **) &droppedDataUnit ), 0 ) ) + { + /* partial copy is useful, consider it in long-term jitter estimation */ + IF ( LE_16( dataUnit->partialCopyOffset, 3 ) ) + { + JB4_JMF_PushPacket( h->ltJmf, rcvTime, dataUnit->timeStamp ); + } + } + ELSE + { + JB4_FreeDataUnit( h, dataUnit ); + } + IF ( droppedDataUnit != NULL ) + { + JB4_FreeDataUnit( h, droppedDataUnit ); + } + } + ELSE + { + /* calculate jitter */ + JB4_JMF_PushPacket( h->ltJmf, rcvTime, dataUnit->timeStamp ); + JB4_estimateShortTermJitter( h, rcvTime, dataUnit->timeStamp ); + /* check for "real" late loss: a frame with higher/same timestamp was already returned to be fed into decoder */ + IF ( h->firstDataUnitPopped && JB4_rtpTimeStampDiff( h->lastReturnedTs, dataUnit->timeStamp ) <= 0 ) + { + IF ( !dataUnit->silenceIndicator ) + { + ++h->nLateLost; + /* deletion of a speech frame because it arrived at the JBM too late */ + ++h->jitterInducedConcealments; + } + JB4_FreeDataUnit( h, dataUnit ); + return 0; + } + /* store data unit */ + IF ( JB4_INPUTBUFFER_Enque( h->inputBuffer, dataUnit, (void **) &droppedDataUnit ) != 0 ) + { + JB4_FreeDataUnit( h, dataUnit ); + } + IF ( droppedDataUnit != NULL ) + { + JB4_FreeDataUnit( h, droppedDataUnit ); + } + } + return 0; +} + + +Word16 JB4_getFECoffset( + JB4_HANDLE h ) +{ + return (Word16) h->optimum_offset; +} + + +Word16 JB4_FECoffset( + JB4_HANDLE h ) +{ + IF ( h->netLossRate < 0.05 ) + { + return 0; + } + ELSE + { + return 1; + } +} + + +Word16 JB4_PopDataUnit( + JB4_HANDLE h, + const UWord32 sysTime, + const UWord32 extBufferedTime, + JB4_DATAUNIT_HANDLE *pDataUnit, + UWord32 *scale, + UWord32 *maxScaling ) +{ + Word16 ret; + + assert( sysTime >= h->prevPopSysTime ); + IF ( sysTime > h->prevPopSysTime + 20 ) + { + h->lastPlayoutOffset += 20; + } + h->prevPopSysTime = sysTime; + move32(); + + ret = JB4_adaptPlayout( h, sysTime, extBufferedTime, pDataUnit, scale, maxScaling ); + + return ret; +} + + +/* Calculates the difference between two RTP timestamps - the diff is positive, if B 'later', negative otherwise */ +static Word32 JB4_rtpTimeStampDiff( + const UWord32 tsA, + const UWord32 tsB ) +{ + Word32 ret; + /* do not dare to inline this function, casting to Word32 is important here! */ + ret = (Word32) ( tsB - tsA ); + move32(); + return ret; +} + + +/* function to get the number of data units contained in the buffer */ +UWord16 JB4_bufferedDataUnits( + const JB4_HANDLE h ) + +{ + return JB4_INPUTBUFFER_Size( h->inputBuffer ); +} + + +/***************************************************************************** + **************************** private functions ****************************** + *****************************************************************************/ + + +/* function to calculate different options for the target playout delay */ +static void JB4_targetPlayoutDelay( + const JB4_HANDLE h, + UWord32 *targetMin, + UWord32 *targetMax, + UWord32 *targetDtx, + UWord32 *targetStartUp ) +{ + UWord32 ltJitter, extraDelayReserve; + + /* adapt target delay to partial copy offset */ + extraDelayReserve = 0; + move32(); + h->rfDelay = 0; + move32(); + IF ( h->rfOffset7Active != 0 ) + { + h->rfDelay = 140; + move32(); + } + ELSE IF ( h->rfOffset5Active != 0 ) + { + h->rfDelay = 100; + move32(); + } + ELSE IF ( h->rfOffset2Active == 0 && h->rfOffset3Active == 0 ) + { + /* keep some delay reserve for RF-off */ + extraDelayReserve = 15; + move32(); + } + + /* get estimated long term jitter */ + IF ( JB4_JMF_Jitter( h->ltJmf, <Jitter ) == 0 ) + { + /* combine long term and short term jitter to calculate target delay values */ + *targetMax = h->stJitter + h->safetyMargin + h->rfDelay; + *targetMin = JB4_MIN( ltJitter + 20 + h->rfDelay + extraDelayReserve, *targetMax ); + move32(); + *targetDtx = JB4_MIN( ltJitter + extraDelayReserve, h->stJitter ); + move32(); + *targetStartUp = ( *targetMin + *targetMax + extraDelayReserve / 4 ) / 2; + } + ELSE + { + /* combine long term and short term jitter to calculate target delay values */ + *targetMax = h->safetyMargin; + move32(); + *targetMin = JB4_MIN( 20, *targetMax ); + move32(); + *targetDtx = 0; + move32(); + *targetStartUp = ( *targetMin + *targetMax ) / 2; + } + + IF ( *targetStartUp < 60 ) + { + *targetStartUp = 60; + move32(); + } + + return; +} + + +/* function to do playout adaptation before playing the next data unit */ +static Word16 JB4_adaptPlayout( + JB4_HANDLE h, + UWord32 sysTime, + UWord32 extBufferedTime, + JB4_DATAUNIT_HANDLE *pDataUnit, + UWord32 *scale, + UWord32 *maxScaling ) +{ + bool stretchTime; + + /* reset scale */ + IF ( scale == NULL || maxScaling == NULL ) + { + return -1; + } + *scale = 100; + move32(); + *maxScaling = 0; + move32(); + stretchTime = false; + + /* switch type of current playout (first one, active, DTX) */ + IF ( !h->firstDataUnitPopped ) + { + JB4_adaptFirstPlayout( h, sysTime, &stretchTime ); + } + ELSE IF ( h->lastPoppedWasSilence ) + { + JB4_adaptDtxPlayout( h, sysTime, &stretchTime ); + } + ELSE + { + JB4_adaptActivePlayout( h, extBufferedTime, scale, maxScaling ); + } + + /* time shrinking done IF needed, now do time stretching or pop data unit to play */ + IF ( stretchTime ) + { + /* return empty data unit */ + *pDataUnit = NULL; + IF ( h->firstDataUnitPopped ) + { + ++h->nUnavailablePopped; + IF ( !h->lastPoppedWasSilence ) + { + ++h->nStretched; + /* jitter-induced insertion (e.g. buffer underflow) */ + ++h->jitterInducedConcealments; + } + } + /* add one frame to last playout delay */ + h->lastPlayoutOffset += h->frameDuration; + } + ELSE + { + /* return next data unit from buffer */ + JB4_popFromBuffer( h, sysTime, pDataUnit ); + } + + return 0; +} + + +/* function for playout adaptation while active (no DTX) */ +static void JB4_adaptActivePlayout( + JB4_HANDLE h, + UWord32 extBufferedTime, + UWord32 *scale, + UWord32 *maxScaling ) +{ + JB4_DATAUNIT_HANDLE nextDataUnit; + bool convertToLateLoss, dropEarly; + UWord32 targetMin, targetMax, targetDtx, targetStartUp, targetMaxStretch; + UWord32 currPlayoutDelay, gap, buffered; + UWord32 dropGapMax, dropRateMin, dropRateMax, rate; + Word32 minOffTicks, tsDiffToNextDataUnit; + + JB4_targetPlayoutDelay( h, &targetMin, &targetMax, &targetDtx, &targetStartUp ); + IF ( JB4_JMF_MinOffset( h->ltJmf, &minOffTicks ) != 0 ) + { + return; + } + h->targetPlayoutDelay = ( targetMin + targetMax ) / 2; + + convertToLateLoss = false; + dropEarly = false; + dropGapMax = 200; + move32(); + dropRateMin = 5; + move32(); + dropRateMax = 200; /* 20% */ + move32(); + + /* calculate current playout delay */ + currPlayoutDelay = h->lastPlayoutOffset - minOffTicks + extBufferedTime; + IF ( !JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) ) + { + nextDataUnit = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + tsDiffToNextDataUnit = JB4_rtpTimeStampDiff( h->nextExpectedTs, nextDataUnit->timeStamp ); + IF ( tsDiffToNextDataUnit < 0 ) + { + convertToLateLoss = true; + /* time stretching is expected -> increase playout delay to allow dropping the late frame */ + currPlayoutDelay -= tsDiffToNextDataUnit; + currPlayoutDelay += 1; + } + } + + /* decided between shrinking/stretching */ + IF ( currPlayoutDelay > targetMax ) /* time shrinking */ + { + gap = currPlayoutDelay - h->targetPlayoutDelay; + /* check if gap is positive and dropping is allowed + * and buffer contains enough time (ignoring one frame) */ + IF ( gap > 0 && + JB4_inspectBufferForDropping( h, &dropEarly, &buffered ) == 0 && + ( convertToLateLoss || + ( buffered + h->frameDuration + extBufferedTime ) > targetMax ) ) + { + IF ( convertToLateLoss ) + { + JB4_dropFromBuffer( h ); + } + ELSE IF ( dropEarly ) + { + JB4_dropFromBuffer( h ); + ++h->nLostOrStretched; + } + ELSE + { + /* limit gap to [gapMin,gapMax] and calculate current drop rate from gap */ + rate = JB4_MIN( (UWord32) ( gap ), dropGapMax ) * + ( dropRateMax - dropRateMin ) / dropGapMax + + dropRateMin; + *scale = ( 1000 - rate ) / 10; + *maxScaling = currPlayoutDelay - targetMax; + } + } + } + ELSE /* time stretching */ + { + UWord32 delayWithClearedExternalBuffer; + /* Stretching only makes sense if we win one additional frame in the input buffer. + * If too much additional delay would be required to do so, then do not scale. + * Also make sure that the delay doesn't increase too much. */ + delayWithClearedExternalBuffer = currPlayoutDelay - extBufferedTime + h->frameDuration; + targetMaxStretch = targetMax - h->frameDuration; + IF ( delayWithClearedExternalBuffer + h->frameDuration <= targetMaxStretch && + currPlayoutDelay < targetMaxStretch && currPlayoutDelay < (UWord32) ( 110 + h->rfDelay / 4 ) ) + { + *scale = 120; + move32(); + *maxScaling = targetMaxStretch - currPlayoutDelay; + } + } + + return; +} + + +/* function for playout adaptation while DTX */ +static void JB4_adaptDtxPlayout( + JB4_HANDLE h, + UWord32 sysTime, + bool *stretchTime ) +{ + JB4_DATAUNIT_HANDLE firstDu; + UWord32 firstTs; + UWord32 targetMin, targetMax, targetDtx, targetStartUp; + UWord32 currPlayoutDelay, headRoom; + Word32 minOffTicks, tsDiffToNextDataUnit; + + JB4_targetPlayoutDelay( h, &targetMin, &targetMax, &targetDtx, &targetStartUp ); + IF ( JB4_JMF_MinOffset( h->ltJmf, &minOffTicks ) != 0 ) + { + return; + } + + /* calculate current playout delay */ + currPlayoutDelay = h->lastPlayoutOffset - minOffTicks; + + /* check for startup after DTX */ + IF ( !JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) ) + { + firstDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + firstTs = firstDu->timeStamp; + move32(); + + tsDiffToNextDataUnit = JB4_rtpTimeStampDiff( h->nextExpectedTs, firstTs ); + /* check if the next available data unit should already be used (time stamp order) */ + IF ( GT_32( tsDiffToNextDataUnit, 0 ) ) + { + /* time stretching is expected -> increase playout delay */ + currPlayoutDelay += tsDiffToNextDataUnit; + } + IF ( !firstDu->silenceIndicator ) + { + /* recalculate playout delay based on first buffered data unit */ + JB4_playoutDelay( h, sysTime, firstTs, &currPlayoutDelay ); + /* check if the next available data unit should already be used (time stamp order) */ + IF ( GT_32( tsDiffToNextDataUnit, 0 ) ) + { + /* time stretching is expected -> increase playout delay */ + currPlayoutDelay += tsDiffToNextDataUnit; + } + h->targetPlayoutDelay = targetStartUp; + move32(); + headRoom = 600 * h->frameDuration / 1000; + /* decided between shrinking/stretching */ + IF ( currPlayoutDelay > targetStartUp + headRoom ) /* time shrinking */ + { + IF ( JB4_checkDtxDropping( h ) ) + { + JB4_dropFromBuffer( h ); + } + } + ELSE IF ( currPlayoutDelay + headRoom < targetStartUp ) /* time stretching */ + { + *stretchTime = true; + } + return; + } + } + + /* adapt while DTX */ + h->targetPlayoutDelay = targetDtx; + + /* decided between shrinking/stretching */ + IF ( currPlayoutDelay >= targetDtx + h->frameDuration ) /* time shrinking */ + { + IF ( JB4_checkDtxDropping( h ) ) + { + JB4_dropFromBuffer( h ); + } + } + ELSE IF ( currPlayoutDelay + 500 * h->frameDuration / 1000 < targetDtx ) /* time stretching */ + { + *stretchTime = true; + } + + return; +} + + +/* function to do playout adaptation before playing the first data unit */ +static void JB4_adaptFirstPlayout( + JB4_HANDLE h, + UWord32 sysTime, + bool *prebuffer ) +{ + UWord32 currPlayoutDelay; + JB4_DATAUNIT_HANDLE firstDu; + UWord32 targetMin, targetMax, targetDtx, targetStartUp; + IF ( JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) ) + { + *prebuffer = true; + return; + } + JB4_targetPlayoutDelay( h, &targetMin, &targetMax, &targetDtx, &targetStartUp ); + IF ( targetStartUp < h->frameDuration ) + { + return; + } + /* calculate delay if first data unit would be played now */ + firstDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + IF ( JB4_playoutDelay( h, sysTime, firstDu->timeStamp, &currPlayoutDelay ) != 0 ) + { + *prebuffer = true; + return; + } + IF ( currPlayoutDelay + h->frameDuration / 2 < targetStartUp ) /* time stretching */ + { + *prebuffer = true; + } + ELSE /* no adaptation, start playout */ + { + *prebuffer = false; + } + + return; +} + + +/* function to look into the buffer and check if it makes sense to drop a data unit */ +static Word16 JB4_inspectBufferForDropping( + const JB4_HANDLE h, + bool *dropEarly, + UWord32 *buffered ) +{ + UWord16 inputBufferSize; + Word32 seqNrDiff; + Word32 bufferedTs; + UWord32 firstTs; + uint64_t beginTs, endTs; + JB4_DATAUNIT_HANDLE firstDu, secondDu, lastDu; + + assert( !h->lastPoppedWasSilence ); + *dropEarly = false; + *buffered = 0; + move16(); + inputBufferSize = JB4_INPUTBUFFER_Size( h->inputBuffer ); + IF ( inputBufferSize == 0U ) + { + return -1; + } + + firstDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + firstTs = firstDu->timeStamp; + /* check for loss: sequence number diff is exactly 0 in the valid case */ + IF ( h->firstDataUnitPopped ) + { + seqNrDiff = JB4_rtpTimeStampDiff( h->nextExpectedTs, firstTs ) / + (Word32) ( h->frameDuration ); + } + ELSE + { + seqNrDiff = 0; + move32(); + } + IF ( LE_32( seqNrDiff, 0 ) ) + { + /* preview data unit to play after dropping */ + IF ( inputBufferSize <= 1U ) + { + /* data unit to play missing, avoid drop followed by concealment */ + return -1; + } + secondDu = JB4_INPUTBUFFER_Element( h->inputBuffer, 1 ); + IF ( firstTs + h->frameDuration != secondDu->timeStamp ) + { + /* data unit to play is not available, avoid drop followed by concealment */ + return -1; + } + /* calculate buffered time span */ + bufferedTs = 0; + move32(); + } + ELSE IF ( EQ_32( seqNrDiff, 2 ) ) + { + /* data unit to play is not available, avoid dropping followed by concealment */ + return -1; + } + ELSE /* seqNoDiff == 1 || seqNoDiff > 2 */ + { + /* first data unit is not available -> drop it early to avoid concealment + * This is very aggressive: ignores the maximum drop rate (50% drop and 50% concealment for adjacent lost), + * but on the other hand, dropping sounds better than concealment. */ + *dropEarly = true; + /* data unit to drop (first one) is lost */ + bufferedTs = 0; + move32(); + } + + /* add time stamp difference of last and first actually buffered data unit */ + IF ( inputBufferSize == 1U ) + { + bufferedTs += h->frameDuration; + } + ELSE + { + lastDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Back( h->inputBuffer ); + beginTs = firstTs; + endTs = lastDu->timeStamp + h->frameDuration; + /* check for RTP time stamp wrap around */ + IF ( endTs < beginTs ) + { + endTs = endTs + 0xFFFFFFFF; + } + bufferedTs += (Word32) ( endTs - beginTs ); + } + + /* the result should not be negative */ + IF ( LT_32( bufferedTs, 0 ) ) + { + return -1; + } + *buffered = bufferedTs; + move32(); + + return 0; +} + + +/* function to look into the buffer and check if it makes sense to drop a data unit */ +static Word16 JB4_checkDtxDropping( + const JB4_HANDLE h ) +{ + UWord16 inputBufferSize; + Word32 seqNrDiff; + JB4_DATAUNIT_HANDLE firstDu; + Word16 droppingAllowed; + + assert( h->firstDataUnitPopped ); + assert( h->lastPoppedWasSilence ); + + droppingAllowed = 1; + move16(); + inputBufferSize = JB4_INPUTBUFFER_Size( h->inputBuffer ); + IF ( inputBufferSize > 0U ) + { + firstDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + /* check for loss: sequence number diff is exactly 0 in the valid case */ + seqNrDiff = JB4_rtpTimeStampDiff( h->nextExpectedTs, firstDu->timeStamp ) / + (Word32) ( h->frameDuration ); + IF ( LE_32( seqNrDiff, 0 ) ) + { + /* no not drop first active frame */ + droppingAllowed = 0; + move16(); + } + } + /* else: buffer empty, allow dropping FRAME_NO_DATA */ + + return droppingAllowed; +} + + +/* function to estimate the short term jitter */ +static void JB4_estimateShortTermJitter( + JB4_HANDLE h, + const UWord32 rcvTime, + const UWord32 rtpTimeStamp ) +{ + UWord32 jitter, duration, maxDuration; + Word32 minTime, maxTime; + JB4_CIRCULARBUFFER_ELEMENT maxElement, dequedElement; + + jitter = 0; + JB4_JMF_PushPacket( h->stJmf, rcvTime, rtpTimeStamp ); + /* save delta delay */ + IF ( EQ_16( JB4_JMF_Jitter( h->stJmf, &jitter ), 0 ) ) + { + /* compensate difference between both offsets */ + Word32 stOffset, ltOffset; + JB4_JMF_MinOffset( h->stJmf, &stOffset ); + JB4_JMF_MinOffset( h->ltJmf, <Offset ); + jitter += stOffset - ltOffset; + assert( (Word16) jitter >= 0 ); + IF ( JB4_CIRCULARBUFFER_IsFull( h->stJitterFifo ) ) + { + JB4_CIRCULARBUFFER_Deque( h->stJitterFifo, &dequedElement ); + JB4_CIRCULARBUFFER_Deque( h->stTimeStampFifo, &dequedElement ); + } + JB4_CIRCULARBUFFER_Enque( h->stJitterFifo, jitter ); + JB4_CIRCULARBUFFER_Enque( h->stTimeStampFifo, rtpTimeStamp ); + + /* check for duration and discard first entries if too long */ + minTime = JB4_CIRCULARBUFFER_Front( h->stTimeStampFifo ); + maxTime = JB4_CIRCULARBUFFER_Back( h->stTimeStampFifo ); + IF ( maxTime > minTime ) + { + duration = maxTime - minTime; + maxDuration = 4 * h->timeScale; + WHILE ( duration > maxDuration ) + { + JB4_CIRCULARBUFFER_Deque( h->stJitterFifo, &dequedElement ); + JB4_CIRCULARBUFFER_Deque( h->stTimeStampFifo, &dequedElement ); + minTime = JB4_CIRCULARBUFFER_Front( h->stTimeStampFifo ); + IF ( LE_32( maxTime, minTime ) ) + { + BREAK; + } + duration = maxTime - minTime; + } + } + } + + /* update h->stJitter */ + IF ( !JB4_CIRCULARBUFFER_IsEmpty( h->stJitterFifo ) ) + { + JB4_CIRCULARBUFFER_Max( h->stJitterFifo, &maxElement ); + /* round up to full frame duration */ + h->stJitter = (UWord32) ceil( (double) ( maxElement ) / h->frameDuration ) * + h->frameDuration; + } + + return; +} + + +/* function to pop a data unit from the buffer */ +static void JB4_popFromBuffer( + JB4_HANDLE h, + const UWord32 sysTime, + JB4_DATAUNIT_HANDLE *pDataUnit ) +{ + JB4_DATAUNIT_HANDLE nextDataUnit; + UWord32 nStretched; + Word32 tsDiff; + JB4_DATAUNIT_HANDLE tempDataUnit; + UWord16 readlen; + UWord16 i; + Word16 frameoffset; + UWord32 maxval; + Word32 lost, total_rec; + JB4_DATAUNIT_HANDLE partialCopyDu; + UWord16 searchpos, endpos; + + /* check if a data unit is available */ + IF ( JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) ) + { + /* no data unit available */ + *pDataUnit = NULL; + h->nextExpectedTs += h->frameDuration; + IF ( h->lastPoppedWasSilence ) + { + ++h->nComfortNoice; + } + ELSE + { + ++h->nUnavailablePopped; + ++h->nLostOrStretched; + } + + return; + } + + /* preview next data unit in sequence order */ + nextDataUnit = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + + /* check if this is the first data unit */ + IF ( !h->firstDataUnitPopped ) + { + h->firstDataUnitPopped = true; + /* adjust sequence numbers to avoid handling first packet as loss */ + h->nextExpectedTs = nextDataUnit->timeStamp; + move16(); + } + + /* check if the next available data unit should already be used (time stamp order) */ + tsDiff = JB4_rtpTimeStampDiff( nextDataUnit->timeStamp, h->nextExpectedTs ); + + h->totWin += 1; + IF ( ( h->totWin > 3000 ) || ( h->FecOffWinLen > 100 ) ) + { + maxval = h->FecOffWin[1]; + move16(); + h->optimum_offset = 1; + move16(); + FOR ( i = 2; i < MAXOFFSET; i++ ) + { + IF ( h->FecOffWin[i] > maxval ) + { + maxval = h->FecOffWin[i]; + move16(); + h->optimum_offset = i; + move16(); + } + h->FecOffWin[i] = 0; + move32(); + } + h->FecOffWin[0] = 0; + move32(); + h->FecOffWin[1] = 0; + move32(); + h->totWin = 0; + move32(); + h->FecOffWinLen = 0; + move32(); + + + lost = h->nLost + h->nPartialCopiesUsed - h->last_nLost; + total_rec = h->nAvailablePopped + h->nUnavailablePopped - h->last_ntot; + + IF ( lost != 0 && total_rec != 0 ) + { + h->netLossRate_fx = (float) lost / (float) total_rec; + h->netLossRate = (float) lost / (float) total_rec; + } + ELSE + { + h->netLossRate_fx = 0; + h->netLossRate = 0.0f; + } + h->last_nLost = L_add( h->nLost, h->nPartialCopiesUsed ); + h->last_ntot = L_add( h->nAvailablePopped, h->nUnavailablePopped ); + } + + IF ( LT_32( tsDiff, 0 ) ) + { + readlen = JB4_INPUTBUFFER_Size( h->inputBuffer ); + FOR ( i = 0; i < readlen; i++ ) + { + tempDataUnit = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Element( h->inputBuffer, i ); + IF ( !tempDataUnit->partial_frame && !h->lastPoppedWasSilence ) + { + frameoffset = (Word16) ( JB4_rtpTimeStampDiff( h->nextExpectedTs, tempDataUnit->timeStamp ) / 20 ); + + IF ( GT_16( frameoffset, 0 ) && LT_16( frameoffset, MAXOFFSET ) ) + { + h->FecOffWin[frameoffset] += 1; + } + } + } + h->FecOffWinLen += 1; + + /* next expected data unit is missing + * -> conceal network loss, do time stretching or create comfort noise */ + *pDataUnit = NULL; + + /* update statistics */ + h->nextExpectedTs += h->frameDuration; + IF ( h->lastPoppedWasSilence ) + { + ++h->nComfortNoice; + } + ELSE + { + ++h->nUnavailablePopped; + ++h->nLostOrStretched; + } + return; + } + + /* fetch the next data unit from buffer */ + *pDataUnit = nextDataUnit; + nextDataUnit->nextCoderType = INACTIVE; + IF ( h->pre_partial_frame || nextDataUnit->partial_frame ) + { + IF ( nextDataUnit->partial_frame ) + { + h->pre_partial_frame = 1; + } + ELSE IF ( h->pre_partial_frame ) + { + h->pre_partial_frame = 0; + } + + endpos = JB4_INPUTBUFFER_Size( h->inputBuffer ); + FOR ( searchpos = 0; searchpos < endpos; searchpos++ ) + { + partialCopyDu = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Element( h->inputBuffer, searchpos ); + IF ( partialCopyDu->timeStamp == nextDataUnit->timeStamp + partialCopyDu->duration ) + { + get_NextCoderType( partialCopyDu->data, &nextDataUnit->nextCoderType ); + BREAK; + } + } + } + JB4_INPUTBUFFER_Deque( h->inputBuffer, (void **) pDataUnit ); + + IF ( nextDataUnit->partial_frame ) + { + h->nPartialCopiesUsed += 1; + + readlen = JB4_INPUTBUFFER_Size( h->inputBuffer ); + FOR ( i = 0; i < readlen; i++ ) + { + tempDataUnit = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Element( h->inputBuffer, i ); + IF ( !tempDataUnit->partial_frame && !h->lastPoppedWasSilence ) + { + frameoffset = (Word16) ( JB4_rtpTimeStampDiff( h->nextExpectedTs, tempDataUnit->timeStamp ) / 20 ); + + IF ( frameoffset > 0 && frameoffset < MAXOFFSET ) + { + h->FecOffWin[frameoffset] += 1; + } + } + } + h->FecOffWinLen += 1; + } + + /* update statistics */ + IF ( h->nLostOrStretched != 0U ) + { + assert( h->lastPoppedWasSilence == false ); + /* separate concealments since last available pop in lost and stretched */ + nStretched = tsDiff / h->frameDuration; + assert( h->nLostOrStretched >= nStretched ); + h->nLost += h->nLostOrStretched - nStretched; + /* jitter-induced insertion (e.g. buffer underflow) */ + h->jitterInducedConcealments += nStretched; + h->nStretched += nStretched; + h->nLostOrStretched = 0; + } + h->lastReturnedTs = nextDataUnit->timeStamp; + JB4_updateLastTimingMembers( h, sysTime, nextDataUnit->timeStamp ); + h->nextExpectedTs = nextDataUnit->timeStamp + h->frameDuration; + IF ( nextDataUnit->silenceIndicator ) + { + h->lastPoppedWasSilence = true; + ++h->nComfortNoice; + } + ELSE + { + h->lastPoppedWasSilence = false; + ++h->nAvailablePopped; + } + + return; +} + +/* function to drop a data unit from the buffer - updates nShrinked */ +static void JB4_dropFromBuffer( + JB4_HANDLE h ) +{ + JB4_DATAUNIT_HANDLE nextDataUnit, dataUnit; + Word32 tsDiff; + UWord32 nStretched; + + /* check if a data unit is available */ + IF ( JB4_INPUTBUFFER_IsEmpty( h->inputBuffer ) ) + { + return; + } + /* preview next data unit in sequence order */ + nextDataUnit = (JB4_DATAUNIT_HANDLE) JB4_INPUTBUFFER_Front( h->inputBuffer ); + + /* check if this is the first data unit */ + IF ( !h->firstDataUnitPopped ) + { + h->firstDataUnitPopped = true; + /* adjust sequence numbers to avoid handling first packet as loss */ + h->nextExpectedTs = nextDataUnit->timeStamp; + move32(); + } + + /* check if the next available data unit should already be used (time stamp order) */ + tsDiff = JB4_rtpTimeStampDiff( nextDataUnit->timeStamp, h->nextExpectedTs ); + IF ( LT_32( tsDiff, 0 ) ) + { + /* next expected data unit is missing, remember this data unit as popped, + * but do not count it as lost, because it will not be concealed */ + h->nextExpectedTs += h->frameDuration; + /* substract one frame from last playout delay */ + h->lastPlayoutOffset -= h->frameDuration; + IF ( !h->lastPoppedWasSilence ) + { + ++h->nShrinked; + /* modification of the output timeline due to link loss */ + ++h->nUnavailablePopped; + ++h->nLostOrStretched; + } + IF ( h->lastTargetTime != 0U ) + { + h->lastTargetTime += h->frameDuration; + } + return; + } + + /* fetch the next data unit from buffer */ + JB4_INPUTBUFFER_Deque( h->inputBuffer, (void *) &dataUnit ); + /* update statistics */ + IF ( h->nLostOrStretched != 0U ) + { + assert( h->lastPoppedWasSilence == false ); + /* separate concealments since last available pop in lost and stretched */ + nStretched = tsDiff / h->frameDuration; + assert( h->nLostOrStretched >= nStretched ); + + /* convert stretching followed by shrinking to late-loss */ + IF ( nStretched > 0U ) + { + --nStretched; + ++h->nLateLost; + h->nLost += h->nLostOrStretched - nStretched; + /* jitter-induced insertion (e.g. buffer underflow) */ + h->jitterInducedConcealments += nStretched; + IF ( !dataUnit->silenceIndicator ) + { + /* JBM induced removal of a speech frame (intentional frame dropping) */ + ++h->jitterInducedConcealments; + } + h->nStretched += nStretched; + } + ELSE + { + h->nLost += h->nLostOrStretched; + ++h->nShrinked; + IF ( !dataUnit->silenceIndicator ) + { + /* JBM induced removal of a speech frame (intentional frame dropping) */ + ++h->jitterInducedConcealments; + } + } + h->nLostOrStretched = 0; + move32(); + } + ELSE + { + IF ( !dataUnit->silenceIndicator ) + { + ++h->nShrinked; + /* JBM induced removal of a speech frame (intentional frame dropping) */ + ++h->jitterInducedConcealments; + } + } + + h->lastReturnedTs = dataUnit->timeStamp; + move32(); + h->lastPoppedWasSilence = dataUnit->silenceIndicator; + h->nextExpectedTs = dataUnit->timeStamp + h->frameDuration; + move32(); + + /* substract one frame from last playout delay */ + h->lastPlayoutOffset -= h->frameDuration; + IF ( h->lastTargetTime != 0U ) + h->lastTargetTime += h->frameDuration; + + JB4_FreeDataUnit( h, dataUnit ); + + return; +} + + +/* function to calculate the playout delay based on the current jitter */ +static Word16 JB4_playoutDelay( + const JB4_HANDLE h, + const UWord32 playTime, + const UWord32 rtpTimeStamp, + UWord32 *delay ) +{ + Word32 minOffTicks; + + IF ( NE_16( JB4_JMF_MinOffset( h->ltJmf, &minOffTicks ), 0 ) ) + { + return -1; + } + + *delay = playTime - minOffTicks - rtpTimeStamp; + + return 0; +} + + +/* function to update lastPlayoutDelay and lastTargetTime after popFromBuffer() */ +static void JB4_updateLastTimingMembers( + JB4_HANDLE h, + const UWord32 playTime, + const UWord32 rtpTimeStamp ) +{ + Word32 minOffTicks; + + IF ( JB4_JMF_MinOffset( h->ltJmf, &minOffTicks ) != 0 ) + { + return; + } + + /* playoutDelay = playTime - minOffset - timeStamp */ + h->lastPlayoutOffset = playTime - rtpTimeStamp; + /* targetTime = minOffset + timeStamp + targetDelay */ + h->lastTargetTime = (UWord32) ( minOffTicks + rtpTimeStamp + h->targetPlayoutDelay ); + + return; +} + + +/* function to compare the RTP time stamps of two data units: newElement==arrayElement ? 0 : (newElement>arrayElement ? +1 : -1) */ +static Word16 JB4_inputBufferCompareFunction( + const JB4_INPUTBUFFER_ELEMENT newElement, + const JB4_INPUTBUFFER_ELEMENT arrayElement, + bool *replaceWithNewElementIfEqual ) +{ + JB4_DATAUNIT_HANDLE newDataUnit, arrayDataUnit; + Word32 diff; + Word16 result; + + *replaceWithNewElementIfEqual = 0; + newDataUnit = (JB4_DATAUNIT_HANDLE) newElement; + arrayDataUnit = (JB4_DATAUNIT_HANDLE) arrayElement; + diff = JB4_rtpTimeStampDiff( arrayDataUnit->timeStamp, newDataUnit->timeStamp ); + IF ( diff > 0 ) + { + result = 1; + move16(); + } + ELSE IF ( LT_32( diff, 0 ) ) + { + result = -1; + move16(); + } + ELSE /* equal timestamps */ + { + result = 0; + move16(); + IF ( newDataUnit->partial_frame == 0 && arrayDataUnit->partial_frame == 1 ) + { + /* replace partial copy with primary copy */ + *replaceWithNewElementIfEqual = 1; + } + ELSE IF ( newDataUnit->partial_frame == arrayDataUnit->partial_frame && newDataUnit->dataSize > arrayDataUnit->dataSize ) + { + /* if both are primary or partial: take the one with higher size (e.g. higher bitrate) */ + *replaceWithNewElementIfEqual = 1; + } + } + + return result; +} + +#else /*! Calculates the difference between two RTP timestamps - the diff is positive, if B 'later', negative otherwise */ static int32_t JB4_rtpTimeStampDiff( const uint32_t tsA, const uint32_t tsB ); /* function to calculate different options for the target playout delay */ @@ -1530,4 +3105,5 @@ static int16_t JB4_inputBufferCompareFunction( return result; } +#endif /* IVAS_FLOAT_FIXED */ #undef WMC_TOOL_SKIP diff --git a/lib_dec/jbm_jb4sb.h b/lib_dec/jbm_jb4sb.h index b64805f342b62fe26bcaa7d60b28c164c5414894..68b91442ac6622514b57e13da233c2904efbac3b 100644 --- a/lib_dec/jbm_jb4sb.h +++ b/lib_dec/jbm_jb4sb.h @@ -45,6 +45,62 @@ #include "typedef.h" #include "ivas_error.h" +#ifdef IVAS_FLOAT_FIXED +/** handle for jitter buffer */ +typedef struct JB4 *JB4_HANDLE; + +/** jitter buffer data units (access unit together with RTP seqNo, timestamp, ...) */ +struct JB4_DATAUNIT +{ + /** the RTP sequence number (16 bits) */ + UWord16 sequenceNumber; + /** the RTP time stamp (32 bits) of this chunk in timeScale() units */ + UWord32 timeStamp; + /** the duration of this chunk in timeScale() units */ + UWord32 duration; + /** the RTP time scale, which is used for timeStamp() and duration() */ + UWord32 timeScale; + /** the receive time of the RTP packet in milliseconds */ + UWord32 rcvTime; + /** true, if the data unit contains only silence */ + bool silenceIndicator; + /** Q bit for AMR-WB IO */ + Word16 qBit; + + /** the binary encoded access unit */ + uint8_t *data; + /** the size of the binary encoded access unit [bits] */ + UWord16 dataSize; + + /** identify if the data unit has a partial copy of a previous frame */ + bool partial_frame; + /** offset of the partial copy contained in that frame or zero */ + Word16 partialCopyOffset; + Word16 nextCoderType; +}; +typedef struct JB4_DATAUNIT *JB4_DATAUNIT_HANDLE; + + +ivas_error JB4_Create( JB4_HANDLE *ph ); + +void JB4_Destroy( JB4_HANDLE *ph ); + +ivas_error JB4_Init( JB4_HANDLE h, const Word16 safetyMargin ); + +JB4_DATAUNIT_HANDLE JB4_AllocDataUnit( JB4_HANDLE h ); + +void JB4_FreeDataUnit( JB4_HANDLE h, JB4_DATAUNIT_HANDLE dataUnit ); + +Word16 JB4_PushDataUnit( JB4_HANDLE h, JB4_DATAUNIT_HANDLE dataUnit, const UWord32 rcvTime ); + +Word16 JB4_PopDataUnit( JB4_HANDLE h, const UWord32 sysTime, const UWord32 extBufferedTime, JB4_DATAUNIT_HANDLE *pDataUnit, UWord32 *scale, UWord32 *maxScaling ); + +Word16 JB4_getFECoffset( JB4_HANDLE h ); + +Word16 JB4_FECoffset( JB4_HANDLE h ); + +UWord16 JB4_bufferedDataUnits( const JB4_HANDLE h ); +#else /** handle for jitter buffer */ typedef struct JB4 *JB4_HANDLE; @@ -99,5 +155,6 @@ int16_t JB4_getFECoffset( JB4_HANDLE h ); int16_t JB4_FECoffset( JB4_HANDLE h ); uint16_t JB4_bufferedDataUnits( const JB4_HANDLE h ); +#endif /* IVAS_FLOAT_FIXED */ #endif /* JBM_JB4SB_H */