diff --git a/lib_com/options.h b/lib_com/options.h index 2d39346da68a5aaddcffc7c291e283977eda7f34..9e2052b43b8b3a7b1414469911054d74ff74e0d6 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -170,6 +170,7 @@ #define RTP_SR_CODEC_FRAME_SIZE_IN_TOC_BYTE /* CR for split rendering codec framesize signalling in Toc Byte*/ #define FIX_SPLIT_RENDERING_ON_DECODER_RESTART /* Re-configure split rendering on decoder restart */ #define RTP_UPDATES_SA4_134 /* Updates to RTP during SA4 134 */ +#define COMPACT_ORIENTATION_PI_DATA /* ################### Start BE switches ################################# */ /* only BE switches wrt selection floating point code */ diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index af1143990fa55e937fb7729f657b6a7326b76bc6..31ebe1c21cdce5acff960b16ef17d6a36a849328 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -106,6 +106,95 @@ static int16_t ivasPayload_convertToQ9( float value ) return (int16_t) ( value ); } +#ifdef COMPACT_ORIENTATION_PI_DATA +static uint32_t packQuaternion( IVAS_QUATERNION orientation, uint8_t *buffer ) +{ + uint32_t nBytes = 0; + float q[4], q_max; + uint16_t max_q_idx, n, k; + uint32_t lWord; + + q[0] = orientation.w; + q[1] = orientation.x; + q[2] = orientation.y; + q[3] = orientation.z; + + max_q_idx = 0; + q_max = (float) fabs( q[0] ); + for ( n = 1; n < 4; n++ ) + { + if ( (float) fabs( q[n] ) > q_max ) + { + q_max = (float) fabs( q[n] ); + max_q_idx = n; + } + } + + if ( q[max_q_idx] < 0 ) + { + for ( n = 0; n < 4; n++ ) + { + q[n] = -q[n]; + } + } + lWord = max_q_idx << 30; + + k = 1; + for ( n = 0; n < 4; n++ ) + { + if ( n == max_q_idx ) + { + continue; + } + lWord |= ( ( (int16_t) ( ( q[n] + 1 ) * 1023 * 0.5f ) & MASK_10BIT ) << ( 30 - k * 10 ) ); + k++; + } + + buffer[nBytes++] = ( lWord >> 24 ) & MASK_8BIT; + buffer[nBytes++] = ( lWord >> 16 ) & MASK_8BIT; + buffer[nBytes++] = ( lWord >> 8 ) & MASK_8BIT; + buffer[nBytes++] = (lWord) &MASK_8BIT; + + return nBytes; +} + + +static ivas_error unpackQuaternion( const uint8_t *buffer, IVAS_QUATERNION *orientation ) +{ + uint32_t i, k, lWord; + uint16_t max_q_idx, tmp; + float q[4], qs; + + lWord = ( buffer[0] ) << 24; + lWord |= ( buffer[1] ) << 16; + lWord |= ( buffer[2] ) << 8; + lWord |= buffer[3]; + + max_q_idx = ( lWord >> 30 ) & MASK_2BIT; + k = 1; + qs = 0.0f; + for ( i = 0; i < 4; i++ ) + { + if ( i == max_q_idx ) + { + continue; + } + tmp = ( lWord >> ( 30 - k * 10 ) ) & MASK_10BIT; + q[i] = tmp / 1023.0f * 2.0f - 1; + qs += q[i] * q[i]; + k++; + } + q[max_q_idx] = sqrtf( 1 - qs ); + orientation->w = q[0]; + orientation->x = q[1]; + orientation->y = q[2]; + orientation->z = q[3]; + + return IVAS_ERR_OK; +} + +#else + /*-----------------------------------------------------------------------* * ivasPayload_convertToQ7() * @@ -119,6 +208,7 @@ static int16_t ivasPayload_convertToQ7( float value ) return (int16_t) ( value ); } #endif +#endif static ivas_error packUnsupportedData( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) { @@ -210,10 +300,14 @@ static ivas_error packOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t *b buffer[nBytes++] = ( orientation->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ #ifdef RTP_UPDATES_SA4_134 buffer[nBytes++] = 4; +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( orientation->orientation, &buffer[nBytes] ); +#else buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.w ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.x ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.y ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation.z ); +#endif #else buffer[nBytes++] = 8; nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.w ) ); @@ -262,10 +356,14 @@ static ivas_error packISMOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t buffer[nBytes++] = (uint8_t) orientation->numObjects * 4; for ( n = 0; n < orientation->numObjects; n++ ) { +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( orientation->orientation[n], &buffer[nBytes] ); +#else buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].w ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].x ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].y ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( orientation->orientation[n].z ); +#endif } #else buffer[nBytes++] = (uint8_t) orientation->numObjects * 8; @@ -294,10 +392,14 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( buffer, &( orientation->orientation ) ); +#else orientation->orientation.w = FLOAT_FROM_Q7( (int8_t) buffer[0] ); orientation->orientation.x = FLOAT_FROM_Q7( (int8_t) buffer[1] ); orientation->orientation.y = FLOAT_FROM_Q7( (int8_t) buffer[2] ); orientation->orientation.z = FLOAT_FROM_Q7( (int8_t) buffer[3] ); +#endif #else /* Orientation data is 8 bytes */ if ( numDataBytes != 8 ) @@ -306,12 +408,12 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); + orientation->orientation.w = FLOAT_FROM_Q15( readInt16( &buffer[0] ) ); orientation->orientation.x = FLOAT_FROM_Q15( readInt16( &buffer[2] ) ); orientation->orientation.y = FLOAT_FROM_Q15( readInt16( &buffer[4] ) ); orientation->orientation.z = FLOAT_FROM_Q15( readInt16( &buffer[6] ) ); #endif - return IVAS_ERR_OK; } @@ -333,10 +435,14 @@ static ivas_error unpackISMOrientation( const uint8_t *buffer, uint32_t numDataB for ( n = 0; n < ism_orientation->numObjects; n++ ) { +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( &buffer[4 * n], &( ism_orientation->orientation[n] ) ); +#else ism_orientation->orientation[n].w = FLOAT_FROM_Q7( (int8_t) buffer[4 * n] ); ism_orientation->orientation[n].x = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 1] ); ism_orientation->orientation[n].y = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 2] ); ism_orientation->orientation[n].z = FLOAT_FROM_Q7( (int8_t) buffer[4 * n + 3] ); +#endif } #else /* Orientation data is 8 bytes */ @@ -1036,10 +1142,14 @@ static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8 #ifdef RTP_UPDATES_SA4_134 if ( packedSize == 5 || packedSize == 4 ) { +#ifdef COMPACT_ORIENTATION_PI_DATA + nBytes += packQuaternion( audioFocus->direction, &buffer[nBytes] ); +#else buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.w ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.x ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.y ); buffer[nBytes++] = (uint8_t) ivasPayload_convertToQ7( audioFocus->direction.z ); +#endif } if ( packedSize == 5 || packedSize == 1 ) { @@ -1084,10 +1194,14 @@ static ivas_error unpackAudioFocusCommon( const uint8_t *buffer, uint32_t numDat } else { +#ifdef COMPACT_ORIENTATION_PI_DATA + unpackQuaternion( buffer, &( audioFocus->direction ) ); +#else audioFocus->direction.w = FLOAT_FROM_Q7( (int8_t) buffer[0] ); audioFocus->direction.x = FLOAT_FROM_Q7( (int8_t) buffer[1] ); audioFocus->direction.y = FLOAT_FROM_Q7( (int8_t) buffer[2] ); audioFocus->direction.z = FLOAT_FROM_Q7( (int8_t) buffer[3] ); +#endif if ( numDataBytes == 5 ) { diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index aec9d4cbe3344bcda45c02126faa21c756f1960b..1c0eef8d3702b8652d87608305de7aaca823cf8f 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -761,11 +761,23 @@ def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION piSize % 4 ) == 0 and piSize <= 16, "Incorrect PI Data Size for list[ORIENTATION]" orientations = list() + q = [(float)]*4 + qs = 0 while piSize > 0: - w = bitstrm.read(8).int / 128.0 - x = bitstrm.read(8).int / 128.0 - y = bitstrm.read(8).int / 128.0 - z = bitstrm.read(8).int / 128.0 + max_q_idx = bitstrm.read(2).uint + for i in range(0,4): + if i == max_q_idx: + continue + tmp = bitstrm.read(10).uint + q[i] = (tmp / 1023.0) * 2.0 - 1.0 + qs = qs + q[i]**2 + + q[max_q_idx] = (1 - qs)**0.5 + + w = q[0] + x = q[1] + y = q[2] + z = q[3] orientations.append(ORIENTATION(w, x, y, z)) piSize -= 4 return orientations @@ -777,10 +789,16 @@ def packOrientations(bitstrm: BitStream, data: any): assert ( type(orientation) == ORIENTATION ), "Orientation PI Data expects a data of type list[ORIENTATION]" - bitstrm.append(f"intbe:8={q7(orientation.w)}") - bitstrm.append(f"intbe:8={q7(orientation.x)}") - bitstrm.append(f"intbe:8={q7(orientation.y)}") - bitstrm.append(f"intbe:8={q7(orientation.z)}") + q = [orientation.w, orientation.x, orientation.y, orientation.z] + max_q = max(q, key=abs) + max_q_idx = q.index(max_q) + if max_q < 0: + q = [-x for x in q] + bitstrm.append(f"uint:2={max_q_idx}") + for i in range(0,4): + if i == max_q_idx: + continue + bitstrm.append(f"uint:10={(int)((q[i]+1)*1023*1/2)}") def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index b385ab80f2713bdb463c2d8f9c462a81b8299f6b..8ef0f57c701e19235cf996d7e0a60e8b3b470619 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -182,12 +182,17 @@ def generateRequests(startTs: int, endTs: int) -> dict: def generatePiData(startTs: int, endTs: int) -> dict: data = dict() - someOrientation = lambda: ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ) + def random_unit_quaternion(): + w = 2*random.random() - 1.0 + x = 2*random.random() - 1.0 + y = 2*random.random() - 1.0 + z = 2*random.random() - 1.0 + n = (w*w + x*x + y*y + z*z)**0.5 + if n == 0.0: + return random_unit_quaternion() + return ORIENTATION(w/n, x/n, y/n, z/n) + + someOrientation = lambda: random_unit_quaternion() somePosition = lambda: POSITION( x=random.randint(-32788, 32767) / 100.0, y=random.randint(-32788, 32767) / 100.0, @@ -214,23 +219,8 @@ def generatePiData(startTs: int, endTs: int) -> dict: someDIG = lambda: DIEGETIC_TYPE( isDigetic=[bool(random.getrandbits(1)) for _ in range(random.randint(1, 5))] ) - someAuFocusDirLvl = lambda: AUDIO_FOCUS( - ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ), - level=AUDIO_FOCUS_LEVEL(random.randint(0, 15)), - ) - someAuFocusDir = lambda: AUDIO_FOCUS( - ORIENTATION( - w=2 * random.random() - 1.0, - x=2 * random.random() - 1.0, - y=2 * random.random() - 1.0, - z=2 * random.random() - 1.0, - ) - ) + someAuFocusDirLvl = lambda: AUDIO_FOCUS(random_unit_quaternion(),level=AUDIO_FOCUS_LEVEL(random.randint(0, 15)),) + someAuFocusDir = lambda: AUDIO_FOCUS(random_unit_quaternion()) someAuFocusLvl = lambda: AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] someLatency = lambda: PI_LATENCY( @@ -278,7 +268,7 @@ def generatePiData(startTs: int, endTs: int) -> dict: someNumISM = lambda : ISM_NUM(num=random.randint(1, 4)) someISMIds = lambda num_ism : ISM_ID(ids=[int(random.getrandbits(8)) for _ in range(num_ism)]) someISMGains = lambda num_ism : ISM_GAIN(gains=[random.choice([int(random.randint(-24,12)), -128]) for _ in range(num_ism)]) # -128 corresponds to -Inf - someISMOrientations = lambda num_ism : [ORIENTATION(w=2*random.random()-1.0, x=2*random.random()-1.0, y=2*random.random()-1.0, z=2*random.random()-1.0) for _ in range(num_ism)] + someISMOrientations = lambda num_ism : [random_unit_quaternion() for _ in range(num_ism)] someISMPositions = lambda num_ism : [POSITION( x=random.randint(-32788, 32767)/100.0, y=random.randint(-32788, 32767)/100.0, z=random.randint(-32788, 32767)/100.0) for _ in range(num_ism)] someISMPositionsCompact = lambda num_ism : [POSITION( x=random.randint(-1024, 1023)/100.0, y=random.randint(-1024, 1023)/100.0, z=random.randint(-512, 511)/100.0) for _ in range(num_ism)] someISMDistanceAttenuations = lambda num_ism : [DISTANCE_ATTENUATION(ref_dist=random.randint(1,64)/10.0, max_dist=random.randint(1,64), roll_off=random.randint(0,40)/10.0) for _ in range(num_ism)] @@ -330,10 +320,10 @@ def isEqualFrame(refFrame: bytes, dutFrame: bytes): def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): - assert abs(ref.w - dut.w) < 0.0079, "Scene Orientation PI Data mismatch in w" - assert abs(ref.x - dut.x) < 0.0079, "Scene Orientation PI Data mismatch in x" - assert abs(ref.y - dut.y) < 0.0079, "Scene Orientation PI Data mismatch in y" - assert abs(ref.z - dut.z) < 0.0079, "Scene Orientation PI Data mismatch in z" + assert abs(abs(ref.w) - abs(dut.w)) < 0.01, "Scene Orientation PI Data mismatch in w" + assert abs(abs(ref.x) - abs(dut.x)) < 0.01, "Scene Orientation PI Data mismatch in x" + assert abs(abs(ref.y) - abs(dut.y)) < 0.01, "Scene Orientation PI Data mismatch in y" + assert abs(abs(ref.z) - abs(dut.z)) < 0.01, "Scene Orientation PI Data mismatch in z" def isEqualPosition(ref: POSITION, dut: POSITION): @@ -400,16 +390,16 @@ def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): assert dut.direction is not None, "Audio Focus PI Data missing direction" if ref.direction is not None and dut.direction is not None: assert ( - abs(ref.direction["w"] - dut.direction.w) < 0.0079 + abs(abs(ref.direction["w"]) - abs(dut.direction.w)) < 0.01 ), "Audio Focus PI Data mismatch in direction w" assert ( - abs(ref.direction["x"] - dut.direction.x) < 0.0079 + abs(abs(ref.direction["x"]) - abs(dut.direction.x)) < 0.01 ), "Audio Focus PI Data mismatch in direction x" assert ( - abs(ref.direction["y"] - dut.direction.y) < 0.0079 + abs(abs(ref.direction["y"]) - abs(dut.direction.y)) < 0.01 ), "Audio Focus PI Data mismatch in direction y" assert ( - abs(ref.direction["z"] - dut.direction.z) < 0.0079 + abs(abs(ref.direction["z"]) - abs(dut.direction.z)) < 0.01 ), "Audio Focus PI Data mismatch in direction z" assert ref.level == dut.level, "Audio Focus PI Data mismatch in level"