From 10265cef5e7d1a1dcc0644018a0fa6bb3ecff687 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 2 Oct 2024 13:21:40 +0300 Subject: [PATCH 001/340] Add support for IVAS rtpdump reading and writing --- apps/decoder.c | 124 +++ apps/encoder.c | 282 ++++- lib_com/bitstream.c | 52 + lib_com/common_api_types.h | 25 + lib_com/ivas_cnst.h | 23 + lib_com/ivas_prot.h | 25 + lib_com/options.h | 2 + lib_dec/lib_dec.c | 153 +++ lib_dec/lib_dec.h | 28 + lib_enc/lib_enc.c | 20 + lib_enc/lib_enc.h | 7 + lib_rend/ivas_prot_rend.h | 8 + lib_rend/ivas_rotation.c | 76 +- lib_rend/ivas_stat_rend.h | 3 + lib_util/disable_headtracking_file_reader.c | 169 +++ lib_util/disable_headtracking_file_reader.h | 86 ++ lib_util/evs_rtp_payload.c | 5 + lib_util/evs_rtp_payload.h | 5 + lib_util/g192.c | 4 + lib_util/g192.h | 8 +- lib_util/ivas_rtp_payload.c | 1081 +++++++++++++++++++ lib_util/ivas_rtp_payload.h | 421 ++++++++ lib_util/rtpdump.c | 28 + lib_util/rtpdump.h | 6 + readme.txt | 12 +- 25 files changed, 2644 insertions(+), 9 deletions(-) create mode 100644 lib_util/disable_headtracking_file_reader.c create mode 100644 lib_util/disable_headtracking_file_reader.h create mode 100644 lib_util/ivas_rtp_payload.c create mode 100644 lib_util/ivas_rtp_payload.h diff --git a/apps/decoder.c b/apps/decoder.c index b317b9a539..d06c08239c 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -37,6 +37,9 @@ #include "bitstream_reader.h" #include "evs_rtp_payload.h" #include "ism_file_writer.h" +#ifdef IVAS_RTPDUMP +#include "ivas_rtp_payload.h" +#endif #include "jbm_file_writer.h" #include "hrtf_file_reader.h" #include "ls_custom_file_reader.h" @@ -1730,10 +1733,18 @@ static void usage_dec( void ) fprintf( stdout, "--------\n" ); fprintf( stdout, "-VOIP : VoIP mode: RTP in G192\n" ); fprintf( stdout, "-VOIP_hf_only=0 : VoIP mode: EVS RTP Payload Format hf_only=0 in rtpdump\n" ); +#ifdef IVAS_RTPDUMP + fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS or IVAS RTP Payload Format hf_only=1 in rtpdump\n" ); + fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); + fprintf( stdout, " EVS RTP Payload Format or rtpdump files containing TS26.253 Annex A\n" ); + fprintf( stdout, " IVAS RTP Payload Format. The SDP parameter hf_only is required.\n" ); + fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); +#else fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump\n" ); fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); fprintf( stdout, " EVS RTP Payload Format. The SDP parameter hf_only is required.\n" ); fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); +#endif #ifdef SUPPORT_JBM_TRACEFILE fprintf( stdout, "-Tracefile TF : VoIP mode: Generate trace file named TF\n" ); #endif @@ -2936,14 +2947,25 @@ static ivas_error printBitstreamInfoVoip( bool previewFailed = true; ivas_error error = IVAS_ERR_OK; FILE *f_rtpstream = NULL; +#ifdef IVAS_RTPDUMP + IVAS_RTPDUMP_DEPACKER rtpdumpDepacker; + IVAS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_NO_ERROR; +#else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; +#endif uint8_t au[( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3]; int16_t auSizeBits; uint8_t *auPtr = NULL; +#ifdef IVAS_RTPDUMP + bool evsIvasModeBit; + uint16_t bitrateIndex; + bool ivasIndicatorOrQBit; +#else bool isAMRWB_IOmode; uint16_t frameTypeIndex; bool qBit; +#endif uint32_t nextPacketRcvTime_ms = 0; uint16_t rtpSequenceNumber; uint32_t rtpTimeStamp; @@ -2961,7 +2983,11 @@ static ivas_error printBitstreamInfoVoip( goto cleanup; } +#ifdef IVAS_RTPDUMP + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream ); +#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream, arg.inputFormat == IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF ); +#endif if ( rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) { fprintf( stderr, "error in EVS_RTPDUMP_DEPACKER_open(): %d\n", rtpdumpDepackerError ); @@ -2982,12 +3008,20 @@ static ivas_error printBitstreamInfoVoip( if ( arg.inputFormat == IVAS_DEC_INPUT_FORMAT_G192 ) { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSizeBits, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); +#ifdef IVAS_RTPDUMP + ivasIndicatorOrQBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ +#else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ +#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ +#ifdef IVAS_RTPDUMP + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSizeBits ); +#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); +#endif /* EVS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; @@ -2997,7 +3031,11 @@ static ivas_error printBitstreamInfoVoip( fprintf( stderr, "failed to read first RTP packet\n" ); goto cleanup; } +#ifdef IVAS_RTPDUMP + } while ( !ivasIndicatorOrQBit || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); +#else } while ( !qBit || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); +#endif BS_Reader_Rewind( hBsReader ); @@ -3011,7 +3049,11 @@ static ivas_error printBitstreamInfoVoip( cleanup: +#ifdef IVAS_RTPDUMP + IVAS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); +#else EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); +#endif if ( previewFailed && error == IVAS_ERR_OK ) { @@ -3076,12 +3118,23 @@ static ivas_error decodeVoIP( int32_t delayTimeScale = -1; int16_t i; FILE *f_rtpstream = NULL; +#ifdef IVAS_RTPDUMP + IVAS_RTPDUMP_DEPACKER rtpdumpDepacker; + IVAS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_NO_ERROR; +#else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; +#endif uint8_t *auPtr = NULL; +#ifdef IVAS_RTPDUMP + bool evsIvasModeBit; + uint16_t bitrateIndex; + bool ivasIndicatorOrQBit; +#else bool isAMRWB_IOmode; uint16_t frameTypeIndex; bool qBit; +#endif IVAS_DEC_BS_FORMAT bsFormat = IVAS_DEC_BS_UNKOWN; IsmFileWriter *ismWriters[IVAS_MAX_NUM_OBJECTS]; @@ -3131,12 +3184,21 @@ static ivas_error decodeVoIP( goto cleanup; } +#ifdef IVAS_RTPDUMP + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream ); + if ( rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) + { + fprintf( stderr, "error in IVAS_RTPDUMP_DEPACKER_open(): %d\n", rtpdumpDepackerError ); + goto cleanup; + } +#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream, arg.inputFormat == IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF ); if ( rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) { fprintf( stderr, "error in EVS_RTPDUMP_DEPACKER_open(): %d\n", rtpdumpDepackerError ); goto cleanup; } +#endif break; case IVAS_DEC_INPUT_FORMAT_G192: auPtr = au; @@ -3173,17 +3235,29 @@ static ivas_error decodeVoIP( if ( arg.inputFormat == IVAS_DEC_INPUT_FORMAT_G192 ) { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); +#ifdef IVAS_RTPDUMP + ivasIndicatorOrQBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ +#else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ +#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ +#ifdef IVAS_RTPDUMP + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame(&rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSize ); +#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); +#endif /* EVS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; } +#ifdef IVAS_RTPDUMP + if ( error != IVAS_ERR_OK || rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) +#else if ( error != IVAS_ERR_OK || rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) +#endif { fprintf( stderr, "failed to read first RTP packet\n" ); goto cleanup; @@ -3328,7 +3402,11 @@ static ivas_error decodeVoIP( while ( nextPacketRcvTime_ms <= systemTime_ms ) { /* feed the previous read packet into the receiver now */ +#ifdef IVAS_RTPDUMP + error = IVAS_DEC_VoIP_FeedFrame(hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, ivasIndicatorOrQBit ); +#else error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, qBit ); +#endif if ( error != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_FeedFrame: %s\n", IVAS_DEC_GetErrorMessage( error ) ); @@ -3341,28 +3419,66 @@ static ivas_error decodeVoIP( { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); +#ifdef IVAS_RTPDUMP + ivasIndicatorOrQBit = 1; /* good q_bit for VOIP_G192_RTP */ +#else qBit = 1; /* good q_bit for VOIP_G192_RTP */ +#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ +#ifdef IVAS_RTPDUMP + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, + &nextPacketRcvTime_ms, + &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, + &auPtr, (uint16_t *) &auSize ); + /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ + rtpTimeStamp = rtpTimeStamp / 16; + + /* parse PI data */ + error = IVAS_DEC_parsePIdata( hIvasDec, &rtpdumpDepacker.PIdataCurrentFrame, &rtpdumpDepacker.PIdataDepackerState ); + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_parsePIdata: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + IVAS_DEC_resetPIdataCurrentFrame( &rtpdumpDepacker.PIdataCurrentFrame ); +#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); /* EVS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; +#endif + } +#ifdef IVAS_RTPDUMP + if ( error == IVAS_ERR_END_OF_FILE || rtpdumpDepackerError == IVAS_RTPDUMP_DEPACKER_EOF ) +#else if ( error == IVAS_ERR_END_OF_FILE || rtpdumpDepackerError == EVS_RTPDUMP_DEPACKER_EOF ) +#endif { /* finished reading */ nextPacketRcvTime_ms = (uint32_t) -1; } +#ifdef IVAS_RTPDUMP + else if ( error != IVAS_ERR_OK ) +#else else if ( error != IVAS_ERR_OK || rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) +#endif { fprintf( stderr, "\nError in BS_Reader_ReadVoipFrame_compact, error code: %d\n", error ); goto cleanup; } +#ifdef IVAS_RTPDUMP + else if ( rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) + { + fprintf( stderr, "\nError in IVAS_RTPDUMP_DEPACKER_readNextFrame, error code: %d\n", error ); + goto cleanup; + } +#endif } /* we are finished when all packets have been received and jitter buffer is empty */ @@ -3524,6 +3640,10 @@ static ivas_error decodeVoIP( } } +#ifdef IVAS_RTPDUMP + IVAS_DEC_resetExternalOrientations(hIvasDec); +#endif + if ( !arg.quietModeEnabled ) { fprintf( stdout, "%-8d\b\b\b\b\b\b\b\b", frame ); @@ -3668,7 +3788,11 @@ static ivas_error decodeVoIP( cleanup: +#ifdef IVAS_RTPDUMP + IVAS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); +#else EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); +#endif AudioFileWriter_close( &afWriter ); JbmOffsetFileWriter_close( &jbmOffsetWriter ); #ifdef SUPPORT_JBM_TRACEFILE diff --git a/apps/encoder.c b/apps/encoder.c index f64986ec84..258a016540 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -38,6 +38,12 @@ #include "ism_file_reader.h" #include "jbm_file_reader.h" #include "masa_file_reader.h" +#ifdef IVAS_RTPDUMP +#include "disable_headtracking_file_reader.h" +#include "evs_rtp_payload.h" +#include "ivas_rtp_payload.h" +#include "rotation_file_reader.h" +#endif #ifdef DEBUGGING #include "debug.h" #endif @@ -146,6 +152,12 @@ typedef struct #endif bool pca; bool ism_extended_metadata; +#ifdef IVAS_RTPDUMP + bool rtpdumpOutput; + char *sceneOrientationTrajFileName; + char *deviceOrientationTrajFileName; + char *disableHeadtrackingFileName; +#endif } EncArguments; @@ -190,6 +202,11 @@ int main( MasaFileReader *masaReader = NULL; IsmFileReader *ismReaders[IVAS_MAX_NUM_OBJECTS] = { NULL, NULL, NULL, NULL }; int16_t *pcmBuf = NULL; +#ifdef IVAS_RTPDUMP + RotFileReader *sceneOrientationFileReader = NULL; + RotFileReader *deviceOrientationFileReader = NULL; + DisableHeadtrackingFileReader *disableHeadtrackingFileReader = NULL; +#endif #ifdef DEBUGGING FILE *f_forcedModeProfile = NULL; #ifdef DEBUG_SBA @@ -205,6 +222,12 @@ int main( reset_mem( USE_BYTES ); #endif +#ifdef IVAS_RTPDUMP + FILE *f_rtpstream = NULL; + IVAS_RTPDUMP_PACKER *rtpdumpPacker = NULL; + IVAS_RTPDUMP_PACKER_ERROR rtpdumpPackerError = IVAS_RTPDUMP_PACKER_NO_ERROR; +#endif + initArgStruct( &arg ); /*------------------------------------------------------------------------------------------* @@ -235,7 +258,11 @@ int main( const BS_WRITER_FORMAT bsWriterFormat = arg.mimeOutput ? BS_WRITER_FORMAT_MIME : BS_WRITER_FORMAT_G192; +#ifdef IVAS_RTPDUMP + if ( !arg.rtpdumpOutput && BS_Writer_Open_filename( &hBsWriter, arg.outputBitstreamFilename, bsWriterFormat ) != IVAS_ERR_OK ) +#else if ( BS_Writer_Open_filename( &hBsWriter, arg.outputBitstreamFilename, bsWriterFormat ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "\nCan't open %s\n\n", arg.outputBitstreamFilename ); goto cleanup; @@ -593,6 +620,70 @@ int main( } } +#ifdef IVAS_RTPDUMP + /*------------------------------------------------------------------------------------------* + * RTPDump + *------------------------------------------------------------------------------------------*/ + + if ( arg.rtpdumpOutput ) + { + /* Open the output file for RTPDump writing */ + f_rtpstream = fopen( arg.outputBitstreamFilename, "wb" ); + + if ( f_rtpstream == NULL ) + { + fprintf( stderr, "could not open: %s\n", arg.outputBitstreamFilename ); + goto cleanup; + } + + rtpdumpPackerError = IVAS_RTPDUMP_PACKER_open( &rtpdumpPacker, f_rtpstream ); + if ( rtpdumpPackerError != IVAS_RTPDUMP_PACKER_NO_ERROR ) + { + fprintf( stderr, "error in IVAS_RTPDUMP_PACKER_open(): %d\n", rtpdumpPackerError ); + goto cleanup; + } + } + + /*------------------------------------------------------------------------------------------* + * Open scene orientation file + *------------------------------------------------------------------------------------------*/ + + if ( arg.sceneOrientationTrajFileName != NULL ) + { + if ( ( error = RotationFileReader_open( arg.sceneOrientationTrajFileName, &sceneOrientationFileReader ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError: Can't open scene orientation file %s \n\n", arg.sceneOrientationTrajFileName ); + goto cleanup; + } + } + + /*------------------------------------------------------------------------------------------* + * Open device orientation file + *------------------------------------------------------------------------------------------*/ + + if ( arg.deviceOrientationTrajFileName != NULL ) + { + if ( ( error = RotationFileReader_open( arg.deviceOrientationTrajFileName, &deviceOrientationFileReader ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError: Can't open device orientation file %s \n\n", arg.deviceOrientationTrajFileName ); + goto cleanup; + } + } + + /*------------------------------------------------------------------------------------------* + * Open disable headtracking file + *------------------------------------------------------------------------------------------*/ + + if (arg.disableHeadtrackingFileName != NULL ) + { + if (( error = DisableHeadtrackingFileReader_open(arg.disableHeadtrackingFileName, &disableHeadtrackingFileReader ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError: Can't open disable headtracking file %s \n\n", arg.disableHeadtrackingFileName ); + goto cleanup; + } + } +#endif + int16_t numSamplesRead = 0; uint16_t bitStream[IVAS_MAX_BITS_PER_FRAME]; uint16_t numBits = 0; @@ -763,12 +854,70 @@ int main( goto cleanup; } - /* write bitstream */ - if ( ( error = BS_Writer_WriteFrame_short( hBsWriter, bitStream, numBits, totalBitrate ) ) != IVAS_ERR_OK ) +#ifdef IVAS_RTPDUMP + if ( rtpdumpPacker->rtpdump ) { - fprintf( stderr, "\nBS_Writer_WriteFrame_short failed, error code %d\n\n", error ); - goto cleanup; + /* scene orientation */ + if ( sceneOrientationFileReader ) + { + IVAS_QUATERNION sceneOrientationQuat; + if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &sceneOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while reading scene orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( sceneOrientationFileReader ) ); + goto cleanup; + } + rtpdumpPacker->piDataPacker.sceneOrientationQuat = &sceneOrientationQuat; + } + + /* device orientation */ + if ( deviceOrientationFileReader ) + { + IVAS_QUATERNION deviceOrientationQuat; + if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &deviceOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while reading device orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( deviceOrientationFileReader ) ); + goto cleanup; + } + rtpdumpPacker->piDataPacker.deviceOrientationQuat = &deviceOrientationQuat; + } + + /* disable headtracking */ + if ( disableHeadtrackingFileReader ) + { + if ( ( error = DisableHeadtrackingFileReading( disableHeadtrackingFileReader, &rtpdumpPacker->piDataPacker.disableHeadtracking ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while reading disable headtracking data from %s\n", IVAS_ENC_GetErrorMessage( error ), DisableHeadtrackingFileReader_getFilePath( disableHeadtrackingFileReader ) ); + goto cleanup; + } + IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( rtpdumpPacker ); + } + + /* PI presence */ + IVAS_RTPDUMP_PACKER_determinePIpresence( rtpdumpPacker ); + + /* write rtpdump */ + rtpdumpPackerError = IVAS_RTPDUMP_PACKER_writeNextFrame( rtpdumpPacker, bitStream, numBits, totalBitrate, + IVAS_isImmersiveFormat( hIvasEnc ), 0, false, NO_BANDWIDTH_REQUEST, NO_FORMAT_REQUEST ); + if ( rtpdumpPackerError != IVAS_RTPDUMP_PACKER_NO_ERROR ) + { + fprintf( stderr, "IVAS_RTPDUMP_PACKER_writeNextFrame() failed, error code: %d\n", rtpdumpPackerError ); + goto cleanup; + } + + IVAS_RTPDUMP_PACKER_resetPIdata( rtpdumpPacker ); } + else + { +#endif + /* write bitstream */ + if ( ( error = BS_Writer_WriteFrame_short( hBsWriter, bitStream, numBits, totalBitrate ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nBS_Writer_WriteFrame_short failed, error code %d\n\n", error ); + goto cleanup; + } +#ifdef IVAS_RTPDUMP + } +#endif frame++; if ( !arg.quietModeEnabled ) @@ -840,6 +989,28 @@ cleanup: fclose( f_bitrateProfile ); } +#ifdef IVAS_RTPDUMP + if ( rtpdumpPacker ) + { + IVAS_RTPDUMP_PACKER_close( &rtpdumpPacker ); + } + + if ( sceneOrientationFileReader ) + { + RotationFileReader_close( &sceneOrientationFileReader ); + } + + if ( deviceOrientationFileReader ) + { + RotationFileReader_close( &deviceOrientationFileReader ); + } + + if ( disableHeadtrackingFileReader ) + { + DisableHeadtrackingFileReader_close( &disableHeadtrackingFileReader ); + } +#endif + IVAS_ENC_Close( &hIvasEnc ); #ifdef WMOPS @@ -895,6 +1066,12 @@ static void initArgStruct( EncArguments *arg ) arg->mimeOutput = false; arg->ism_extended_metadata = false; arg->complexityLevel = IVAS_ENC_COMPLEXITY_LEVEL_THREE; +#ifdef IVAS_RTPDUMP + arg->rtpdumpOutput = false; + arg->sceneOrientationTrajFileName = NULL; + arg->deviceOrientationTrajFileName = NULL; + arg->disableHeadtrackingFileName = NULL; +#endif #ifdef DEBUGGING arg->forcedMode = IVAS_ENC_FORCE_UNFORCED; @@ -1705,6 +1882,73 @@ static bool parseCmdlIVAS_enc( i++; } +#ifdef IVAS_RTPDUMP + /*-----------------------------------------------------------------* + * RTPDump output + *-----------------------------------------------------------------*/ + + else if ( strcmp( argv_to_upper, "-RTPDUMP" ) == 0 ) + { + arg->rtpdumpOutput = true; + fprintf( stdout, "Output format: RTPDump\n" ); + ++i; + } + + /*-----------------------------------------------------------------* + * Scene orientation + *-----------------------------------------------------------------*/ + + else if ( strcmp( argv_to_upper, "-SCENE_ORIENTATION" ) == 0 ) + { + i++; + if ( argc - i <= 4 || argv[i][0] == '-' ) + { + fprintf( stderr, "Error: Scene orientation file name not specified!\n\n" ); + usage_enc(); + return false; + } + + arg->sceneOrientationTrajFileName = argv[i]; + i++; + } + + /*-----------------------------------------------------------------* + * Device orientation + *-----------------------------------------------------------------*/ + + else if ( strcmp( argv_to_upper, "-DEVICE_ORIENTATION" ) == 0 ) + { + i++; + if ( argc - i <= 4 || argv[i][0] == '-' ) + { + fprintf( stderr, "Error: Device orientation file name not specified!\n\n" ); + usage_enc(); + return false; + } + + arg->deviceOrientationTrajFileName = argv[i]; + i++; + } + + /*-----------------------------------------------------------------* + * Disable headtracking file reading + *-----------------------------------------------------------------*/ + + else if ( strcmp( argv_to_upper, "-DISABLE_HEADTRACKING_FILE" ) == 0 ) + { + i++; + if ( argc - i <= 4 || argv[i][0] == '-' ) + { + fprintf( stderr, "Error: Disable headtracking input file name not specified!\n\n" ); + usage_enc(); + return false; + } + + arg->disableHeadtrackingFileName = argv[i]; + i++; + } + +#endif /*-----------------------------------------------------------------* * Option not recognized *-----------------------------------------------------------------*/ @@ -1716,6 +1960,27 @@ static bool parseCmdlIVAS_enc( } } /* end of while */ +#ifdef IVAS_RTPDUMP + if ( arg->sceneOrientationTrajFileName != NULL && arg->rtpdumpOutput == false ) + { + fprintf( stderr, "Error: Scene orientations are only enabled with rtpdump output!\n\n" ); + usage_enc(); + return false; + } + if ( arg->deviceOrientationTrajFileName != NULL && arg->rtpdumpOutput == false ) + { + fprintf( stderr, "Error: Device orientations are only enabled with rtpdump output!\n\n" ); + usage_enc(); + return false; + } + if ( arg->disableHeadtrackingFileName != NULL && arg->rtpdumpOutput == false ) + { + fprintf( stderr, "Error: Disable headtracking file reading is only enabled with rtpdump output!\n\n" ); + usage_enc(); + return false; + } + +#endif /*-----------------------------------------------------------------* * Mandatory input arguments *-----------------------------------------------------------------*/ @@ -1922,6 +2187,15 @@ static void usage_enc( void ) #endif fprintf( stdout, "-q : Quiet mode, no frame counters\n" ); fprintf( stdout, " default is deactivated\n" ); +#ifdef IVAS_RTPDUMP + fprintf( stdout, "-rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the \n" ); + fprintf( stdout, " bitstream frames into TS26.253 Annex A IVAS RTP Payload Format packets and \n" ); + fprintf( stdout, " writes those to the output file. In EVS mono operating mode, TS26.445 Annex A.2.2 \n" ); + fprintf( stdout, " EVS RTP Payload Format is used. \n" ); + fprintf( stdout, "-scene_orientation : Scene orientation trajectory file. Only used with rtpdump output.\n" ); + fprintf( stdout, "-device_orientation : Device orientation trajectory file. Only used with rtpdump output.\n" ); + fprintf( stdout, "-disable_headtracking_file : Disable headtracking input file. (1) to disable and (0) to enable headtracking. Only used with rtpdump output.\n" ); +#endif fprintf( stdout, "\n" ); return; diff --git a/lib_com/bitstream.c b/lib_com/bitstream.c index 47fbbb9db2..f5a7fbd480 100644 --- a/lib_com/bitstream.c +++ b/lib_com/bitstream.c @@ -163,6 +163,58 @@ Word16 rate2EVSmode( return rate2AMRWB_IOmode( brate ); } +#ifdef IVAS_RTPDUMP +/*-------------------------------------------------------------------* + * rate2IVASmode() + * + * Lookup IVAS mode. Note: FRAME_NO_DATA frames should be looked up with rate2EVSmode(). + *-------------------------------------------------------------------*/ +Word16 rate2IVASmode( + const Word32 brate /* i : bitrate */ +) +{ + switch ( brate ) + { + /* IVAS modes */ + case IVAS_SID_5k2: + return IVAS_TOC_SID; + case IVAS_13k2: + return IVAS_TOC_13200; + case IVAS_16k4: + return IVAS_TOC_16400; + case IVAS_24k4: + return IVAS_TOC_24400; + case IVAS_32k: + return IVAS_TOC_32000; + case IVAS_48k: + return IVAS_TOC_48000; + case IVAS_64k: + return IVAS_TOC_64000; + case IVAS_80k: + return IVAS_TOC_80000; + case IVAS_96k: + return IVAS_TOC_96000; + case IVAS_128k: + return IVAS_TOC_128000; + case IVAS_160k: + return IVAS_TOC_160000; + case IVAS_192k: + return IVAS_TOC_192000; + case IVAS_256k: + return IVAS_TOC_256000; + case IVAS_384k: + return IVAS_TOC_384000; + case IVAS_512k: + return IVAS_TOC_512000; + default: + break; + } + + return -1; +} + +#endif + /*-------------------------------------------------------------------* * ind_list_realloc() * diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index 8cbdfcb3cd..251e4f9003 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -36,6 +36,9 @@ /* options.h needed for debugging/development features * It should be stripped for delivery along with debugging switches */ #include "options.h" +#ifdef IVAS_RTPDUMP +#include +#endif #include #include #include "ivas_error.h" @@ -213,6 +216,28 @@ typedef struct _IVAS_JBM_TRACE_DATA } IVAS_JBM_TRACE_DATA; +#ifdef IVAS_RTPDUMP +typedef struct +{ + bool PIdataPresent; + char *sceneOrientation; + char *deviceOrientationCompensated; + char *deviceOrientationUncompensated; + char *acousticEnvironmentId; + char *acousticEnvironmentOnlyLateReverb; + char *acousticEnvironmentLateReverbAndEarlyReflections; + bool enableHeadTracking; + bool disableHeadTracking; +} PI_DATA_CURRENT_FRAME; + +typedef struct +{ + bool sceneOrientationSaved; + IVAS_QUATERNION sceneOrientationQuat; + bool deviceOrientationSaved; + IVAS_QUATERNION deviceOrientationQuat; +} PI_DATA_DEPACKER_STATE; +#endif /*----------------------------------------------------------------------------------* * Split rendering API constants, structures, and enums diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index 105c0e8057..e4dc8f8b88 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -1772,6 +1772,29 @@ typedef enum } STEREO_DMX_EVS_PRC; +#ifdef IVAS_RTPDUMP +/* "IVAS bit rate" column in table A.3.3.3.2-1 of TS26.253 Annex A */ +typedef enum +{ + IVAS_TOC_13200 = 0x0, // 0000 + IVAS_TOC_16400 = 0x1, // 0001 + IVAS_TOC_24400 = 0x2, // 0010 + IVAS_TOC_32000 = 0x3, // 0011 + IVAS_TOC_48000 = 0x4, // 0100 + IVAS_TOC_64000 = 0x5, // 0101 + IVAS_TOC_80000 = 0x6, // 0110 + IVAS_TOC_96000 = 0x7, // 0111 + IVAS_TOC_128000 = 0x8, // 1000 + IVAS_TOC_160000 = 0x9, // 1001 + IVAS_TOC_192000 = 0xA, // 1010 + IVAS_TOC_256000 = 0xB, // 1011 + IVAS_TOC_384000 = 0xC, // 1100 + IVAS_TOC_512000 = 0xD, // 1101 + IVAS_TOC_RESERVED = 0xE, // 1110 + IVAS_TOC_SID = 0xF // 1111 +} IVAS_TOC_BITRATE_INDEX; + +#endif #endif /* clang-format on */ /* IVAS_CNST_H */ diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index 700a9cc75c..6c3bf7f309 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -770,7 +770,32 @@ void ivas_apply_non_diegetic_panning( const int16_t output_frame /* i : output frame length per channel */ ); +#ifdef IVAS_RTPDUMP +Word16 rate2IVASmode( + const Word32 brate /* i : bitrate */ +); + +void QuaternionProduct( + const IVAS_QUATERNION q1, + const IVAS_QUATERNION q2, + IVAS_QUATERNION *const r +); + +void QuaternionInverse( + const IVAS_QUATERNION q, + IVAS_QUATERNION *const r +); +void resetPIdataCurrentFrame( + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ +); + +bool ivasPayload_parseOrientationPIdata( + char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ + IVAS_QUATERNION *q /* o : Parsed quaternion value */ +); + +#endif /*----------------------------------------------------------------------------------* * JBM prototypes *----------------------------------------------------------------------------------*/ diff --git a/lib_com/options.h b/lib_com/options.h index 367583eb90..e66a459089 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -162,6 +162,8 @@ #define OBJ_EDITING_PARAMISM_BIN /* Nokia: object editing for ParamISM to binaural */ #endif +#define IVAS_RTPDUMP /* RTPDUMP writing and reading for IVAS payloads */ + /* ################### Start BE switches ################################# */ /* only BE switches wrt selection floating point code */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 900b879cb6..4187a7d10e 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3582,6 +3582,159 @@ ivas_error IVAS_DEC_Flush( } +#ifdef IVAS_RTPDUMP +/*---------------------------------------------------------------------* + * IVAS_DEC_parseSinglePIorientation( ) + * + * Parse a single orientation from PI data and feed to external orientation handle. + * Save the parsed (inverted) orientation value and use in processing in future frames, if no new PI orientation is received. + *---------------------------------------------------------------------*/ + +ivas_error IVAS_DEC_parseSinglePIorientation( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + char *orientation, /* i : input PI orientation */ + bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ +) +{ + int16_t i; + ivas_error error = IVAS_ERR_OK; + if ( orientation ) + { + IVAS_QUATERNION orientationQuat, orientationQuatInv; + if ( !hIvasDec->st_ivas->hExtOrientationData ) + { + if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + if ( !hIvasDec->st_ivas->hCombinedOrientationData ) + { + if ( ( error = ivas_combined_orientation_open( &( hIvasDec->st_ivas->hCombinedOrientationData ), hIvasDec->st_ivas->hDecoderConfig->output_Fs, hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + /* read the orientation PI value */ + if ( !ivasPayload_parseOrientationPIdata( orientation, &orientationQuat ) ) + { + return IVAS_ERROR( IVAS_ERR_INTERNAL, "Parsing PI orientation failed" ); + } + QuaternionInverse( orientationQuat, &orientationQuatInv ); + + /* update the saved orientation value */ + *isOrientationSaved = true; + *savedInvOrientation = orientationQuatInv; + } + + if ( *isOrientationSaved ) + { + /* use the new PI orientation or the previously saved orientation in processing */ + for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) + { + QuaternionProduct(hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], *savedInvOrientation, + &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i]); + hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; + } + hIvasDec->updateOrientation = true; + } + return error; +} + + +/*---------------------------------------------------------------------* + * IVAS_DEC_parsePIenableDisableHeadtracking( ) + * + * Parse enable/disable headtracking from PI data + *---------------------------------------------------------------------*/ + +void IVAS_DEC_parsePIenableDisableHeadtracking( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ +) +{ + if ( PIdataCurrentFrame->enableHeadTracking ) + { + if ( hIvasDec->st_ivas->hCombinedOrientationData ) + { + hIvasDec->st_ivas->hCombinedOrientationData->enableHeadTrackingPI = 1; + } + } + if ( PIdataCurrentFrame->disableHeadTracking ) + { + if ( hIvasDec->st_ivas->hCombinedOrientationData ) + { + hIvasDec->st_ivas->hCombinedOrientationData->enableHeadTrackingPI = 0; + } + } +} + + +/*---------------------------------------------------------------------* + * IVAS_DEC_parsePIdata( ) + * + * Parse PI data for a single frame + *---------------------------------------------------------------------*/ + +ivas_error IVAS_DEC_parsePIdata( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* i : PI data handle for the current frame */ + PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ +) +{ + ivas_error error = IVAS_ERR_OK; + + /* scene orientation */ + if (( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) + { + return error; + } + + /* device orientation */ + if (( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) + { + return error; + } + + /* enable/disable headtracking */ + IVAS_DEC_parsePIenableDisableHeadtracking( hIvasDec, PIdataCurrentFrame ); + + return error; +} + + +/*---------------------------------------------------------------------* + * IVAS_DEC_resetExternalOrientations( ) + * + * Reset external orientations + *---------------------------------------------------------------------*/ + +void IVAS_DEC_resetExternalOrientations( + IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ +) +{ + ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); +} + + +/*---------------------------------------------------------------------* + * IVAS_DEC_resetPIdataCurrentFrame( ) + * + * Reset current PI frame struct + *---------------------------------------------------------------------*/ + +void IVAS_DEC_resetPIdataCurrentFrame( + PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ +) +{ + resetPIdataCurrentFrame( piDataCurrentFrame ); +} + + +#endif /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_IsEmpty( ) * diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 50476c964b..3d3e41e6a8 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -319,6 +319,34 @@ ivas_error IVAS_DEC_Flush( int16_t *nSamplesFlushed /* o : number of samples flushed */ ); +#ifdef IVAS_RTPDUMP +ivas_error IVAS_DEC_parseSinglePIorientation( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + char *orientation, /* i : input PI orientation */ + bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ +); + +void IVAS_DEC_parsePIenableDisableHeadtracking( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ +); + +ivas_error IVAS_DEC_parsePIdata( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* i : PI data handle for the current frame */ + PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ +); + +void IVAS_DEC_resetExternalOrientations( + IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ +); + +void IVAS_DEC_resetPIdataCurrentFrame( + PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ +); + +#endif /* Setter functions - apply changes to decoder configuration */ /*! r: error code */ diff --git a/lib_enc/lib_enc.c b/lib_enc/lib_enc.c index 23e666a7f0..b934b5c8f2 100644 --- a/lib_enc/lib_enc.c +++ b/lib_enc/lib_enc.c @@ -2417,3 +2417,23 @@ static void init_encoder_config( return; } +#ifdef IVAS_RTPDUMP + + +/*---------------------------------------------------------------------* + * IVAS_isImmersiveFormat() + * + * Check if the used format is immersive or mono + *---------------------------------------------------------------------*/ + +bool IVAS_isImmersiveFormat( + const IVAS_ENC_HANDLE hIvasEnc +) +{ + if ( hIvasEnc->st_ivas->hEncoderConfig->ivas_format == MONO_FORMAT ) + { + return false; + } + return true; +} +#endif diff --git a/lib_enc/lib_enc.h b/lib_enc/lib_enc.h index 2f40c1ab1b..ba0debf662 100644 --- a/lib_enc/lib_enc.h +++ b/lib_enc/lib_enc.h @@ -383,6 +383,13 @@ void IVAS_ENC_PrintDisclaimer( void ); +#ifdef IVAS_RTPDUMP +/*! r: true if ivas format is not mono */ +bool IVAS_isImmersiveFormat( + const IVAS_ENC_HANDLE hIvasEnc /* i : IVAS encoder handle */ +); + +#endif /* clang-format on */ #endif /* LIB_ENC_H */ diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 8e6980b054..3ddb3a68f9 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1292,6 +1292,12 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ); +#ifdef IVAS_RTPDUMP +void ivas_external_orientation_reset( + EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ +); + +#endif void ivas_external_orientation_close( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o: external orientation handle */ ); @@ -1339,6 +1345,7 @@ ivas_error ivas_render_config_init_from_rom( * Quaternion operations *----------------------------------------------------------------------------------*/ +#ifndef IVAS_RTPDUMP void QuaternionProduct( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, @@ -1350,6 +1357,7 @@ void QuaternionInverse( IVAS_QUATERNION *const r ); +#endif void QuaternionSlerp( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index dc5136cd00..d11e2b6b6e 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -693,6 +693,7 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ) { +#ifndef IVAS_RTPDUMP int16_t i; IVAS_QUATERNION identity; @@ -700,12 +701,16 @@ ivas_error ivas_external_orientation_open( identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; +#endif /* Allocate handle */ if ( ( *hExtOrientationData = (EXTERNAL_ORIENTATION_HANDLE) malloc( sizeof( EXTERNAL_ORIENTATION_DATA ) ) ) == NULL ) { return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for external orientation memory\n" ) ); } ( *hExtOrientationData )->num_subframes = num_subframes; +#ifdef IVAS_RTPDUMP + ivas_external_orientation_reset( hExtOrientationData ); +#else /* Enable head rotation and disable external orientation as default */ for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) { @@ -715,10 +720,42 @@ ivas_error ivas_external_orientation_open( ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; ( *hExtOrientationData )->Quaternions[i] = identity; } +#endif return IVAS_ERR_OK; } +#ifdef IVAS_RTPDUMP +/*-----------------------------------------------------------------------* + * ivas_external_orientation_reset() + * + * Reset external orientation values + *-----------------------------------------------------------------------*/ + +void ivas_external_orientation_reset( + EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ +) +{ + if ( *hExtOrientationData != NULL ) + { + int16_t i; + IVAS_QUATERNION identity; + + identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; + /* Enable head rotation and disable external orientation as default */ + for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) + { + ( *hExtOrientationData )->enableHeadRotation[i] = 1; + ( *hExtOrientationData )->enableExternalOrientation[i] = 0; + ( *hExtOrientationData )->enableRotationInterpolation[i] = 0; + ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; + ( *hExtOrientationData )->Quaternions[i] = identity; + } + } +} + + +#endif /*-----------------------------------------------------------------------* * ivas_external_orientation_close() * @@ -759,9 +796,14 @@ ivas_error ivas_combined_orientation_open( IVAS_VECTOR3 origo; int16_t pos_idx; +#ifdef IVAS_RTPDUMP + identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; + origo = (IVAS_VECTOR3) { .x = 0.0f, .y = 0.0f, .z = 0.0f }; +#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; origo.x = origo.y = origo.z = 0.0f; +#endif /* Allocate handle */ if ( ( *hCombinedOrientationData = (COMBINED_ORIENTATION_HANDLE) malloc( sizeof( COMBINED_ORIENTATION_DATA ) ) ) == NULL ) @@ -822,6 +864,9 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->subframe_idx = 0; ( *hCombinedOrientationData )->subframe_size = (int16_t) ( fs / ( FRAMES_PER_SEC * MAX_PARAM_SPATIAL_SUBFRAMES ) ); ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; +#ifdef IVAS_RTPDUMP + ( *hCombinedOrientationData )->enableHeadTrackingPI = 1; +#endif return IVAS_ERR_OK; } @@ -943,9 +988,14 @@ ivas_error combine_external_and_head_orientations( IVAS_QUATERNION identity; IVAS_VECTOR3 origo; +#ifdef IVAS_RTPDUMP + identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; + origo = (IVAS_VECTOR3) { .x = 0.0f, .y = 0.0f, .z = 0.0f }; +#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; origo.x = origo.y = origo.z = 0.0f; +#endif /* Form combined orientations or return if no data available */ if ( hCombinedOrientationData == NULL ) @@ -983,10 +1033,24 @@ ivas_error combine_external_and_head_orientations( else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { /* Head rotation only */ - for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) +#ifdef IVAS_RTPDUMP + if ( hCombinedOrientationData->enableHeadTrackingPI == 1 ) + { +#endif + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + } +#ifdef IVAS_RTPDUMP + } + else { - hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = identity; + } } +#endif } if ( hExtOrientationData != NULL ) @@ -1064,7 +1128,11 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->isHeadRotationFrozen = 0; } /* Use the most recent head rotation */ +#ifdef IVAS_RTPDUMP + if ( hExtOrientationData->enableHeadRotation[i] == 1 && hCombinedOrientationData->enableHeadTrackingPI == 1 ) +#else if ( hExtOrientationData->enableHeadRotation[i] == 1 ) +#endif { if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) { @@ -1223,8 +1291,12 @@ static void external_target_interpolation( if ( hExtOrientationData->enableExternalOrientation[i - 1] == 0 ) { IVAS_QUATERNION identity; +#ifdef IVAS_RTPDUMP + identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; +#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; +#endif hCombinedOrientationData->Quaternions_ext_interpolation_start = identity; } else if ( hExtOrientationData->enableExternalOrientation[i - 1] == 2 ) diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index f1d777699c..64a56e85fa 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -710,6 +710,9 @@ typedef struct ivas_combined_orientation_struct int16_t cur_subframe_samples_rendered; int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; +#ifdef IVAS_RTPDUMP + int8_t enableHeadTrackingPI; +#endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; /*----------------------------------------------------------------------------------* diff --git a/lib_util/disable_headtracking_file_reader.c b/lib_util/disable_headtracking_file_reader.c new file mode 100644 index 0000000000..985733dc83 --- /dev/null +++ b/lib_util/disable_headtracking_file_reader.c @@ -0,0 +1,169 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + + +#include "disable_headtracking_file_reader.h" +#include +#include +#include +#include "prot.h" + +#ifdef IVAS_RTPDUMP + +struct DisableHeadtrackingFileReader +{ + FILE *inputFile; + int32_t frameCounter; + char *file_path; + bool fileRewind; +}; + + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_open() + * + * Allocate and initialize file reader + *-----------------------------------------------------------------------*/ + +ivas_error DisableHeadtrackingFileReader_open( + char *filePath, /* i : disable-headtracking input file name */ + DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ +) +{ + DisableHeadtrackingFileReader *self; + FILE *inputFile; + + /* Open trajectory file */ + if ( strlen( filePath ) < 1 ) + { + return IVAS_ERR_FAILED_FILE_OPEN; + } + + inputFile = fopen(filePath, "r" ); + + if ( !inputFile ) + { + return IVAS_ERR_FAILED_FILE_OPEN; + } + + self = calloc( sizeof( DisableHeadtrackingFileReader ), 1 ); + self->inputFile = inputFile; + self->frameCounter = 0; + self->file_path = calloc( sizeof( char ), strlen( filePath ) + 1 ); + strcpy( self->file_path, filePath ); + self->fileRewind = false; + + *fileReader = self; + + return IVAS_ERR_OK; +} + + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReading() + * + * Read values from the input file. (1) to disable and (0) to enable headtracking. + *-----------------------------------------------------------------------*/ + +ivas_error DisableHeadtrackingFileReading( + DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ + bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ +) +{ + float disableHeadTrackingFlag; + int32_t read_values; + + /* Initial values, if they are not read from the file (enable headtracking by default) */ + disableHeadTrackingFlag = 0; + + read_values = fscanf(fileReader->inputFile, "%f", &disableHeadTrackingFlag ); + if ( ( read_values != 1 ) ) + { + if ( feof( fileReader->inputFile ) ) + { + rewind( fileReader->inputFile ); + fileReader->fileRewind = true; + return DisableHeadtrackingFileReading( fileReader, pHeadTrackingDisabled ); + } + return IVAS_ERR_FAILED_FILE_PARSE; + } + + ( fileReader->frameCounter )++; + + *pHeadTrackingDisabled = (int8_t) disableHeadTrackingFlag == 1 ? true : false; + + return IVAS_ERR_OK; +} + + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_close() + * + * Deallocates memory for the file reader + *-----------------------------------------------------------------------*/ + +void DisableHeadtrackingFileReader_close( + DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ +) +{ + if ( fileReader == NULL || *fileReader == NULL ) + { + return; + } + + fclose( ( *fileReader )->inputFile ); + free( ( *fileReader )->file_path ); + free( *fileReader ); + *fileReader = NULL; + + return; +} + + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_getFilePath() + * + * + *-----------------------------------------------------------------------*/ + +const char *DisableHeadtrackingFileReader_getFilePath( + DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ +) +{ + if ( fileReader == NULL ) + { + return NULL; + } + + return fileReader->file_path; +} +#endif diff --git a/lib_util/disable_headtracking_file_reader.h b/lib_util/disable_headtracking_file_reader.h new file mode 100644 index 0000000000..4d413b442c --- /dev/null +++ b/lib_util/disable_headtracking_file_reader.h @@ -0,0 +1,86 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_DISABLE_HEADTRACKING_FILE_READER_H +#define IVAS_DISABLE_HEADTRACKING_FILE_READER_H + +#include +#include "common_api_types.h" + +#ifdef IVAS_RTPDUMP + +typedef struct DisableHeadtrackingFileReader DisableHeadtrackingFileReader; + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_open() + * + * Allocate and initialize reader handle + *-----------------------------------------------------------------------*/ + +ivas_error DisableHeadtrackingFileReader_open( + char *filePath, /* i : disable-headtracking input file name */ + DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ +); + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReading() + * + * Read values from the input file. (1) to disable and (0) to enable headtracking. + *-----------------------------------------------------------------------*/ + +ivas_error DisableHeadtrackingFileReading( + DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ + bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ +); + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_close() + * + * Deallocates memory for the reader handle + *-----------------------------------------------------------------------*/ + +void DisableHeadtrackingFileReader_close( + DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ +); + +/*-----------------------------------------------------------------------* + * DisableHeadtrackingFileReader_getFilePath() + * + * + *-----------------------------------------------------------------------*/ + +const char *DisableHeadtrackingFileReader_getFilePath( + DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ +); +#endif + +#endif /* IVAS_DISABLE_HEADTRACKING_FILE_READER_H */ diff --git a/lib_util/evs_rtp_payload.c b/lib_util/evs_rtp_payload.c index c122716f31..c32c77e22a 100644 --- a/lib_util/evs_rtp_payload.c +++ b/lib_util/evs_rtp_payload.c @@ -37,6 +37,7 @@ #include #include #include "evs_rtp_payload.h" +#include "options.h" static void evsPayload_unpackFrame_compact_amrWbIo( const char *payload, uint16_t payloadSizeBits, uint16_t iProtectedSize, unsigned char **framePtr, uint16_t *frameSizeInBits ) { @@ -123,7 +124,11 @@ bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBy frameFollowing, frameTypeIndex, qBit, framePtr, frameSizeInBits ); } +#ifdef IVAS_RTPDUMP +void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) +#else static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) +#endif { bool evsModeBit = ( toc & 0x20 ) != 0; *isAMRWB_IOmode = evsModeBit; diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 004ecf79b6..1ea8ad16d4 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -161,6 +161,11 @@ extern "C" bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); +#ifdef IVAS_RTPDUMP + void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); +#else + static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); +#endif bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); typedef struct diff --git a/lib_util/g192.c b/lib_util/g192.c index 09ce7248d9..7cd222e241 100644 --- a/lib_util/g192.c +++ b/lib_util/g192.c @@ -483,7 +483,11 @@ G192_ERROR G192_WriteVoipFrame_short( const uint16_t *serial, const int16_t numBits, uint16_t const rtpSequenceNumber, +#ifdef IVAS_RTPDUMP + uint32_t const rtpTimeStamp, +#else uint16_t const rtpTimeStamp, +#endif uint32_t const rcvTime_ms ) { int16_t G192_HEADER[2], G192_DATA[IVAS_MAX_BITS_PER_FRAME]; diff --git a/lib_util/g192.h b/lib_util/g192.h index 6d6104d453..d24e409cae 100644 --- a/lib_util/g192.h +++ b/lib_util/g192.h @@ -93,7 +93,13 @@ G192_ERROR G192_Writer_Open_filename( G192_HANDLE *phG192, const char *filename G192_ERROR G192_WriteFrame( G192_HANDLE const hG192, const uint16_t *serial, const int16_t numBits ); -G192_ERROR G192_WriteVoipFrame_short( G192_HANDLE const hG192, const uint16_t *serial, const int16_t num_bits, uint16_t const rtpSequenceNumber, uint16_t const rtpTimeStamp, uint32_t const rcvTime_ms ); +G192_ERROR G192_WriteVoipFrame_short( G192_HANDLE const hG192, const uint16_t *serial, const int16_t num_bits, uint16_t const rtpSequenceNumber, +#ifdef IVAS_RTPDUMP + uint32_t const rtpTimeStamp, +#else + uint16_t const rtpTimeStamp, +#endif + uint32_t const rcvTime_ms ); G192_ERROR G192_Writer_Close( G192_HANDLE *phG192 ); diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c new file mode 100644 index 0000000000..9700d47c23 --- /dev/null +++ b/lib_util/ivas_rtp_payload.c @@ -0,0 +1,1081 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include +#include +#include +#include "evs_rtp_payload.h" +#include "ivas_rtp_payload.h" +#include "ivas_prot.h" +#include "prot.h" + +#ifdef IVAS_RTPDUMP + + +/*-----------------------------------------------------------------------* + * writeByteToRtp() + * + * Write a single byte (value) to RTP packet. + *-----------------------------------------------------------------------*/ + +static void writeByteToRtp( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + char value /* i : Value to be written to the RTP packet */ +) +{ + self->rtpPacket.data[self->writingIndex] = value; + ++self->writingIndex; + self->rtpPacket.payloadSize += 1; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parseToc() + * + * Parse a single ToC byte for an IVAS payload. + *-----------------------------------------------------------------------*/ + +static void ivasPayload_parseToc( + uint8_t toc, /* i : ToC byte to be parsed */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + int32_t *bitrate /* o : Bitrate of the data frame indicated in the ToC byte */ +) +{ + bool isIvasToc = ( toc & 0x30 ) == 0x10; // xx01 xxxx + if ( !isIvasToc ) + { + evsHeaderFullPayload_parseToc(toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, bitrate ); + } + else + { + *evsIvasModeBit = false; + *ivasIndicatorBit = true; + *frameFollowing = ( toc & 0x40 ) != 0; + *bitrateIndex = toc & 0x0f; + *bitrate = IVASmode2rate[*bitrateIndex]; + } +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parsePItype() + * + * Parse a single PI type and set the PIdataCurrentFrame handle accordingly. + *-----------------------------------------------------------------------*/ + +static void ivasPayload_parsePItype( + char *PIframeDataBeginning, /* i : Pointer to the beginning of the PI data section in the RTP packet */ + uint16_t PIframeSizeBytes, /* i : PI data frame size in bytes */ + uint16_t parsedPIframeSizeBytes, /* i : Size of previously parsed PI frame data section in bytes */ + uint16_t PIframeType, /* i : Type of the PI data frame */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ +) +{ + if ( PIframeType == PI_SCENE_ORIENTATION ) + { + PIdataCurrentFrame->sceneOrientation = PIframeDataBeginning + parsedPIframeSizeBytes; + } + else if ( PIframeType == PI_DEVICE_ORIENTATION_COMPENSATED ) + { + PIdataCurrentFrame->deviceOrientationCompensated = PIframeDataBeginning + parsedPIframeSizeBytes; + } + else if ( PIframeType == PI_DEVICE_ORIENTATION_UNCOMPENSATED ) + { + PIdataCurrentFrame->deviceOrientationUncompensated = PIframeDataBeginning + parsedPIframeSizeBytes; + } + else if ( PIframeType == PI_ACOUSTIC_ENVIRONMENT ) + { + if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES ) + { + PIdataCurrentFrame->acousticEnvironmentId = PIframeDataBeginning + parsedPIframeSizeBytes; + } + else if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_ONLY_LATE_REVERB_SIZE_BYTES ) + { + PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = PIframeDataBeginning + parsedPIframeSizeBytes; + } + else if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_LATE_REVERB_AND_EARLY_REFLECTIONS_SIZE_BYTES ) + { + PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = PIframeDataBeginning + parsedPIframeSizeBytes; + } + } + else if ( PIframeType == PI_ENABLE_HEADTRACKING ) + { + PIdataCurrentFrame->enableHeadTracking = true; + } + else if ( PIframeType == PI_DISABLE_HEADTRACKING ) + { + PIdataCurrentFrame->disableHeadTracking = true; + } + else if ( PIframeType == PI_NO_DATA ) + { + /* no data */ + } + else + { + /* reserved */ + } +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_PIaddHeaderBytes() + * + * Add PI header section bytes to the PI data pointers for the current audio frame. + *-----------------------------------------------------------------------*/ + +static void ivasPayload_PIaddHeaderBytes( + uint16_t PIheaderSizeBytes, /* i : Size of the PI header section in bytes */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ +) +{ + if ( PIdataCurrentFrame->sceneOrientation != NULL ) + { + PIdataCurrentFrame->sceneOrientation += PIheaderSizeBytes; + } + if ( PIdataCurrentFrame->deviceOrientationCompensated != NULL ) + { + PIdataCurrentFrame->deviceOrientationCompensated += PIheaderSizeBytes; + } + if ( PIdataCurrentFrame->deviceOrientationUncompensated != NULL ) + { + PIdataCurrentFrame->deviceOrientationUncompensated += PIheaderSizeBytes; + } + if ( PIdataCurrentFrame->acousticEnvironmentId != NULL ) + { + PIdataCurrentFrame->acousticEnvironmentId += PIheaderSizeBytes; + } + if ( PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb != NULL ) + { + PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb += PIheaderSizeBytes; + } + if ( PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections != NULL ) + { + PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections += PIheaderSizeBytes; + } +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parsePIdata() + * + * Parse PI data for the current audio frame. + *-----------------------------------------------------------------------*/ + +static bool ivasPayload_parsePIdata( + char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ + uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ + uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ +) +{ + // TODO(pajunen): can this accidentally include zero padding bits? + + bool somePIframeFollowing; + char *PIframeDataBeginning; + uint16_t PIframeMarker, PIframeType, tempPIframeSizeBytes, PIframeSizeBytes, totalPIframeSizeBytes, totalPIheaderSizeBytes, count_audio; + PIframeDataBeginning = PIdataPayload; + somePIframeFollowing = true; + totalPIframeSizeBytes = 0; + totalPIheaderSizeBytes = 0; + count_audio = 0; + + while ( somePIframeFollowing ) + { + if ( (int16_t) PIdataPayloadSizeBytes <= 0 ) + { + fprintf( stderr, "Error: PI payload too small\n" ); + return false; + } + + /* parse PI data header */ + somePIframeFollowing = ( *PIdataPayload & 0x80 ) != 0; + PIframeMarker = ( *PIdataPayload & 0x60 ) >> 5; + PIframeType = ( *PIdataPayload & 0x1F ); + ++PIdataPayload; + ++totalPIheaderSizeBytes; + --PIdataPayloadSizeBytes; + tempPIframeSizeBytes = ( *PIdataPayload & 0xFF ); + PIframeSizeBytes = tempPIframeSizeBytes; + while ( tempPIframeSizeBytes == 255 ) + { + ++PIdataPayload; + ++totalPIheaderSizeBytes; + --PIdataPayloadSizeBytes; + tempPIframeSizeBytes = ( *PIdataPayload & 0xFF ); + PIframeSizeBytes += tempPIframeSizeBytes; + } + ++PIdataPayload; + ++totalPIheaderSizeBytes; + --PIdataPayloadSizeBytes; + PIdataPayloadSizeBytes -= PIframeSizeBytes; + + /* general PI data for all frames */ + if ( PIframeMarker == PM_GENERAL ) + { + ivasPayload_parsePItype(PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + } + else + { + /* current frame */ + if ( ( PIframeMarker > 0 ) & ( count_audio == frameIndex ) ) + { + ivasPayload_parsePItype(PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + } + + /* increase audio frame counter */ + if ( PIframeMarker == PM_LAST ) + { + ++count_audio; + } + } + totalPIframeSizeBytes += PIframeSizeBytes; + } + ivasPayload_PIaddHeaderBytes( totalPIheaderSizeBytes, PIdataCurrentFrame ); + + return true; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_convertFromQ15() + * + * Convert a Q15 encoded value to a float value. + *-----------------------------------------------------------------------*/ + +static float ivasPayload_convertFromQ15( uint16_t Q15Value ) +{ + float temp; + temp = (float)( Q15Value & 0x7FFF ) / 0x7FFF; + return ( Q15Value & 0x8000 ) ? -temp : temp; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ15() + * + * Convert a float value into a Q15 encoded value. + *-----------------------------------------------------------------------*/ + +static uint16_t ivasPayload_convertToQ15( float value ) +{ + uint16_t temp; + temp = (uint16_t)( fabsf( value ) * 0x7FFF ); + if ( value >= 0.0f ) + { + temp = temp & 0x7FFF; + } + else + { + temp = temp | 0x8000; + } + return temp; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_convertOrientationToQ15() + * + * Convert a float quaternion into a Q15 encoded quaternion. + *-----------------------------------------------------------------------*/ + +static void ivasPayload_convertOrientationToQ15( + IVAS_QUATERNION *orientation, /* i : Input orientation in quaternion to be converted to Q15 */ + IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ +) +{ + orientationQ15->w = ivasPayload_convertToQ15( orientation->w ); + orientationQ15->x = ivasPayload_convertToQ15( orientation->x ); + orientationQ15->y = ivasPayload_convertToQ15( orientation->y ); + orientationQ15->z = ivasPayload_convertToQ15( orientation->z ); +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parseQuaternionValue() + * + * Parse a single Q15 encoded quaternion component from the RTP packet and convert it to float. + *-----------------------------------------------------------------------*/ + +static float ivasPayload_parseQuaternionComponent( + char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ +) +{ + uint16_t tempQ15value; + tempQ15value = (uint16_t) **PIorientationData; + tempQ15value = ( tempQ15value << 8 ) | (uint16_t) *( *PIorientationData + 1 ); + *PIorientationData += 2; + return ivasPayload_convertFromQ15( tempQ15value ); +} + + +/*-----------------------------------------------------------------------* + * resetPIdataCurrentFrame() + * + * Reset the PI data handle for the current audio frame. + *-----------------------------------------------------------------------*/ + +void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) +{ + PIdataCurrentFrame->PIdataPresent = false; + PIdataCurrentFrame->sceneOrientation = NULL; + PIdataCurrentFrame->deviceOrientationCompensated = NULL; + PIdataCurrentFrame->deviceOrientationUncompensated = NULL; + PIdataCurrentFrame->acousticEnvironmentId = NULL; + PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = NULL; + PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = NULL; + PIdataCurrentFrame->enableHeadTracking = false; + PIdataCurrentFrame->disableHeadTracking = false; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parseOrientationPIdata() + * + * Parse a single orientation in quaternions from the PI data. + *-----------------------------------------------------------------------*/ + +bool ivasPayload_parseOrientationPIdata( + char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ + IVAS_QUATERNION *q /* o : Parsed quaternion value */ +) +{ + q->w = ivasPayload_parseQuaternionComponent(&PIorientationData); + q->x = ivasPayload_parseQuaternionComponent(&PIorientationData); + q->y = ivasPayload_parseQuaternionComponent(&PIorientationData); + q->z = ivasPayload_parseQuaternionComponent(&PIorientationData); + + if ( q->w > 1.0f || q->w < -1.0f || + q->x > 1.0f || q->x < -1.0f || + q->y > 1.0f || q->y < -1.0f || + q->z > 1.0f || q->z < -1.0f ) + { + fprintf( stderr, "Error: invalid orientation PI data\n" ); + return false; + } + return true; +} + +bool ivasPayload_unpackFrame( + char *payload, /* i : RTP payload data */ + uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ + uint16_t frameIndex, /* i : Index for the data frame in the payload */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeInBits, /* o : Data frame size in bits */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ +) +{ + bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit; + uint16_t someBitrateIndex, someFrameSizeInBits, fullPayloadSizeBytes, PIdataPayloadSizeBytes; + int32_t bitrate; + uint16_t iFrame; + char *PIdataPayload; + fullPayloadSizeBytes = payloadSizeBytes; + PIdataPayload = payload; + someFrameFollowing = true; + if ( payloadSizeBytes < 1 ) + { + fprintf( stderr, "Error: payload too small to parse ToC\n" ); + return false; + } + /* parse CMR/E-bytes */ + if ( *payload & E_BYTE ) // 1xxx xxxx + { + PIdataCurrentFrame->PIdataPresent = false; + while (1) + { + if ( ( *payload & 0xF0 ) == IVAS_E_CMR ) // 1111 xxxx + { + /* IVAS CMR/E-byte */ + while (1) + { + /* parse subsequent E-bytes */ + ++payload; + --payloadSizeBytes; + if ( ( *payload & 0xE0 ) == IVAS_E_BW_REQUEST ) // 100x xxxx + { + /* IVAS bandwidth request */ + if ( ( *payload & 0xFF ) == IVAS_BW_REQ_WB ) // 1000 0000 + { + /* Wideband request */ + } + else if ( ( *payload & 0xFF ) == IVAS_BW_REQ_SWB ) // 1000 0001 + { + /* Super-wideband request */ + } + else if ( ( *payload & 0xFF ) == IVAS_BW_REQ_FB ) // 1000 0010 + { + /* Fullband request */ + } + } + else if ( ( *payload & 0xE0 ) == IVAS_E_FMT_REQUEST ) // 101x xxxx + { + /* IVAS coded format request */ + if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_STEREO ) // 1010 0000 + { + /* Stereo request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_SBA ) // 1010 0001 + { + /* SBA request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_MASA ) // 1010 0010 + { + /* MASA request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_ISM ) // 1010 0011 + { + /* ISM request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_MC ) // 1010 0100 + { + /* MC request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_OMASA ) // 1010 0101 + { + /* OMASA request */ + } + else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_OSBA ) // 1010 0110 + { + /* OSBA request */ + } + } + else if ( ( *payload & 0xE0 ) == IVAS_E_PI_INDICATION ) // 110x xxxx + { + /* IVAS PI presence */ + PIdataCurrentFrame->PIdataPresent = true; + } + else if ( ( *payload & 0xE0 ) == IVAS_E_RESERVED ) // 111x xxxx + { + /* Reserved IVAS subsequent E-byte */ + } + else + { + break; + } + + if ( payloadSizeBytes < 1 ) + { + fprintf( stderr, "Error: payload too small to parse subsequent E-bytes\n" ); + return false; + } + } + break; + } + else + { + /* EVS CMR */ + ++payload; + --payloadSizeBytes; + if ( *payload & E_BYTE ) // 1xxx xxxx + { + continue; + } + else + { + break; + } + } + } + } + + /* parse all ToC entries */ + *frame = (unsigned char *) payload; /* no need to copy frame bytes */ + for ( iFrame = 0; someFrameFollowing; ++iFrame ) + { + if ( (int16_t) payloadSizeBytes <= 0 ) + { + fprintf( stderr, "Error: payload too small\n" ); + return false; + } + if ( *payload & E_BYTE ) // 1xxx xxxx + { + fprintf( stderr, "Error: expected ToC, found CMR/E-byte\n" ); + return false; + } + ivasPayload_parseToc(*payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &bitrate ); + if ( bitrate < 0 ) + { + fprintf( stderr, "Error: unexpected frameTypeIndex in ToC\n" ); + return false; + } + ++payload; + ++*frame; + someFrameSizeInBits = (uint16_t) ( bitrate / 50 ); + /* just keep/copy zero padding bits + * in case of AMRWB_IO_SID the STI bit and CMI bits following the SID_1k75 frame are also kept (A.2.2.1.3 of TS 26.445) */ + payloadSizeBytes -= 1 + ( someFrameSizeInBits + 7 ) / 8; + if ( iFrame < frameIndex ) + { + *frame += ( someFrameSizeInBits + 7 ) / 8; + if ( !someFrameFollowing ) + { + fprintf( stderr, "Error: expected ToC with F bit set\n" ); + return false; + } + } + else if ( iFrame == frameIndex ) + { + *evsIvasModeBit = someEvsIvasModeBit; + *frameFollowing = someFrameFollowing; + *bitrateIndex = someBitrateIndex; + *ivasIndicatorBit = someIvasIndicatorBit; + *frameSizeInBits = someFrameSizeInBits; + } + if ( (int16_t) payloadSizeBytes < 0 ) + { + fprintf( stderr, "Error: payload too small for frame %u data\n", frameIndex ); + return false; + } + } + + /* parse PI data */ + if ( PIdataCurrentFrame->PIdataPresent ) + { + PIdataPayloadSizeBytes = payloadSizeBytes; + PIdataPayload += ( fullPayloadSizeBytes - PIdataPayloadSizeBytes ); + if ( !ivasPayload_parsePIdata( PIdataPayload, PIdataPayloadSizeBytes, frameIndex, PIdataCurrentFrame ) ) + { + fprintf( stderr, "Error while parsing PI data\n" ); + return false; + } + } + + return true; +} + +IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( + IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ + FILE *file /* i : Input file containing the rtpdump */ +) +{ + RTPDUMP_ERROR rtpdumpError; + self->frameFollowing = false; + rtpdumpError = RTPDUMP_OpenWithFileToRead( &self->rtpdump, file ); + if ( rtpdumpError != RTPDUMP_NO_ERROR ) + { + return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; + } + resetPIdataCurrentFrame( &self->PIdataCurrentFrame ); + self->PIdataDepackerState.sceneOrientationSaved = false; + self->PIdataDepackerState.deviceOrientationSaved = false; + return IVAS_RTPDUMP_DEPACKER_NO_ERROR; +} + +IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( + IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ + uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ + uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ + uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeBits /* o : Data frame size in bits */ +) +{ + /* read next RTP packet from rtpdump */ + if ( !self->frameFollowing ) + { + RTPDUMP_ERROR rtpdumpError = RTPDUMP_ReadPacket( self->rtpdump, &self->rtpPacket, &self->timeoffset_ms ); + if ( rtpdumpError == RTPDUMP_READ_ENDOFFILE ) + { + return IVAS_RTPDUMP_DEPACKER_EOF; + } + else if ( rtpdumpError != RTPDUMP_NO_ERROR ) + { + return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; + } + self->frameIndex = 0; + } + /* unpack next frame from RTP packet */ + if ( !ivasPayload_unpackFrame( self->rtpPacket.data + self->rtpPacket.headerSize, + self->rtpPacket.payloadSize, self->frameIndex, + evsIvasModeBit, &self->frameFollowing, bitrateIndex, ivasIndicatorBit, + frame, frameSizeBits, &self->PIdataCurrentFrame ) ) + { + return IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR; + } + + /* return frame */ + *rtpSequenceNumber = self->rtpPacket.sequenceNumber; + *rtpTimeStamp = self->rtpPacket.timeStamp + self->frameIndex * 16000 / 50; + *rcvTime_ms = self->timeoffset_ms + self->frameIndex * 20; + ++self->frameIndex; + return IVAS_RTPDUMP_DEPACKER_NO_ERROR; +} + +void IVAS_RTPDUMP_DEPACKER_close( + IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ +) +{ + if ( !self ) + { + return; + } + RTPDUMP_Close( &self->rtpdump, 0 ); +} + +IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( + IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ + FILE *file /* i : Output file for the rtpdump stream */ +) +{ + IVAS_RTPDUMP_PACKER *self; + RTPDUMP_ERROR rtpdumpError; + self = calloc( sizeof( IVAS_RTPDUMP_PACKER ), 1 ); + rtpdumpError = RTPDUMP_OpenWithFileToWrite( &self->rtpdump, file ); + if ( rtpdumpError != RTPDUMP_NO_ERROR ) + { + return IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR; + } + RTPDUMP_SetDefaultRtpPacketHeader( &self->rtpPacket ); + memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); + self->piDataPacker.sceneOrientationQuat = NULL; + self->piDataPacker.deviceOrientationQuat = NULL; + self->piDataPacker.enableHeadtracking = false; + self->piDataPacker.disableHeadtracking = false; + self->piDataPackerState.previousDisableHeadtrackingState = false; + self->piDataPresent = false; + *rtpdumpPacker = self; + return IVAS_RTPDUMP_PACKER_NO_ERROR; +} + +void IVAS_RTPDUMP_PACKER_writeEbytes( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). + * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ +) +{ + UWord8 initialEbyte, subsequentEbyte; + bool initialEbyteWritten; + + /* determine E-bytes */ + initialEbyte = 0; + subsequentEbyte = 0; + initialEbyteWritten = false; + if ( requestImmersiveFormatBitrate ) + { + switch ( requestedBitrate ) + { + case IVAS_13k2: + initialEbyte = IVAS_CMR_13k2; + break; + case IVAS_16k4: + initialEbyte = IVAS_CMR_16k4; + break; + case IVAS_24k4: + initialEbyte = IVAS_CMR_24k4; + break; + case IVAS_32k: + initialEbyte = IVAS_CMR_32k; + break; + case IVAS_48k: + initialEbyte = IVAS_CMR_48k; + break; + case IVAS_64k: + initialEbyte = IVAS_CMR_64k; + break; + case IVAS_80k: + initialEbyte = IVAS_CMR_80k; + break; + case IVAS_96k: + initialEbyte = IVAS_CMR_96k; + break; + case IVAS_128k: + initialEbyte = IVAS_CMR_128k; + break; + case IVAS_160k: + initialEbyte = IVAS_CMR_160k; + break; + case IVAS_192k: + initialEbyte = IVAS_CMR_192k; + break; + case IVAS_256k: + initialEbyte = IVAS_CMR_256k; + break; + case IVAS_384k: + initialEbyte = IVAS_CMR_384k; + break; + case IVAS_512k: + initialEbyte = IVAS_CMR_512k; + break; + default: + initialEbyte = IVAS_CMR_NO_REQ; + break; + } + writeByteToRtp( self, (char) initialEbyte ); + initialEbyteWritten = true; + } + else + { + /* EVS CMR */ + initialEbyteWritten = false; + } + + if ( ivasBandwidthRequest != NO_BANDWIDTH_REQUEST ) + { + if ( !initialEbyteWritten ) + { + initialEbyte = IVAS_CMR_NO_REQ; + writeByteToRtp( self, (char) initialEbyte ); + initialEbyteWritten = true; + } + + switch ( ivasBandwidthRequest ) + { + case WB_REQUEST: + subsequentEbyte = IVAS_BW_REQ_WB; + break; + case SWB_REQUEST: + subsequentEbyte = IVAS_BW_REQ_SWB; + break; + case FB_REQUEST: + subsequentEbyte = IVAS_BW_REQ_FB; + break; + default: + subsequentEbyte = IVAS_BW_NO_REQ; + break; + } + writeByteToRtp( self, (char) subsequentEbyte ); + } + + if ( ivasFormatRequest != NO_FORMAT_REQUEST ) + { + if ( !initialEbyteWritten ) + { + initialEbyte = IVAS_CMR_NO_REQ; + writeByteToRtp( self, (char) initialEbyte ); + initialEbyteWritten = true; + } + + switch ( ivasFormatRequest ) + { + case STEREO_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_STEREO; + break; + case SBA_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_SBA; + break; + case MASA_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_MASA; + break; + case ISM_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_ISM; + break; + case MC_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_MC; + break; + case OMASA_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_OMASA; + break; + case OSBA_REQUEST: + subsequentEbyte = IVAS_FMT_REQ_OSBA; + break; + default: + subsequentEbyte = IVAS_FMT_NO_REQ; + break; + } + writeByteToRtp( self, (char) subsequentEbyte ); + } + + if ( self->piDataPresent ) + { + if ( !initialEbyteWritten ) + { + initialEbyte = IVAS_CMR_NO_REQ; + writeByteToRtp( self, (char) initialEbyte ); + initialEbyteWritten = true; + } + subsequentEbyte = IVAS_PI_INDICATION; + writeByteToRtp( self, (char) subsequentEbyte ); + } +} + +void IVAS_RTPDUMP_PACKER_writeToc( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t frameBitrate, /* i : Bitrate of the written frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). + * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ + bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ +) +{ + UWord8 H_bit, F_bit, EVS_IVAS_mode_bit, Q_bit, toc; + int16_t is_amr_wb; + + is_amr_wb = 0; + H_bit = 0; + F_bit = someFrameFollowing ? 1 : 0; + /* FRAME_NO_DATA frames are written as in TS26.445 */ + if ( isImmersiveFormat && frameBitrate != FRAME_NO_DATA ) + { + toc = (UWord8)( rate2IVASmode( frameBitrate ) ); + EVS_IVAS_mode_bit = 0; + Q_bit = 1; + } + else + { + toc = (UWord8)( rate2EVSmode( frameBitrate, &is_amr_wb ) ); + EVS_IVAS_mode_bit = (UWord8) is_amr_wb; + Q_bit = 0; + } + toc = ( H_bit << 7 ) | ( F_bit << 6 ) | ( EVS_IVAS_mode_bit << 5 ) | ( Q_bit << 4 ) | toc; + writeByteToRtp( self, (char) toc ); +} + +IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ + int32_t numBits, /* i : Number of bits contained in the data frame */ + int32_t frameBitrate, /* i : Bitrate of the data frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ +) +{ + + RTPDUMP_ERROR rtpdumpError; + int32_t i, k; + char tempData; + + self->writingIndex = self->rtpPacket.headerSize; + self->rtpPacket.payloadSize = 0; + memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); + + /* payload header */ + IVAS_RTPDUMP_PACKER_writeEbytes(self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); + IVAS_RTPDUMP_PACKER_writeToc( self, frameBitrate, isImmersiveFormat, false ); + + /* frame data */ + for (i = 0; i < numBits; i += 8) { + tempData = 0; + for (k = 0; k < 8; ++k) { + tempData = ( tempData << 1 ) | (char) bitStream[i + k]; + } + writeByteToRtp( self, tempData ); + } + + /* PI data */ + IVAS_RTPDUMP_PACKER_writePIdata( self ); + + /* write packet */ + rtpdumpError = RTPDUMP_WritePacket( self->rtpdump, &self->rtpPacket, self->timeoffset_ms ); + if ( rtpdumpError != RTPDUMP_NO_ERROR ) + { + return IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR; + } + + /* adjust packetizer values */ + self->rtpPacket.sequenceNumber += 1; + self->rtpPacket.timeStamp += 320; + self->timeoffset_ms += 20; + + return IVAS_RTPDUMP_PACKER_NO_ERROR; +} + +void IVAS_RTPDUMP_PACKER_writePIdata( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +) +{ + if ( !self->piDataPresent ) + { + return; + } + + UWord8 PIheaderByte; + IVAS_QUATERNION_Q15 sceneOrientationQ15, deviceOrientationQ15; + bool somePIfollowing = false; + + /* PI header section */ + + /* scene orientation */ + if ( self->piDataPacker.sceneOrientationQuat ) + { + if ( self->piDataPacker.deviceOrientationQuat || self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) + { + somePIfollowing = true; + } + if ( somePIfollowing ) + { + PIheaderByte = ( PF1 << 7 ) | ( PM_NOT_LAST << 5 ) | PI_SCENE_ORIENTATION; + } + else + { + PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_SCENE_ORIENTATION; + } + writeByteToRtp( self, (char) PIheaderByte ); + writeByteToRtp( self, PI_SCENE_ORIENTATION_SIZE_BYTES ); + } + + /* device orientation */ + if ( self->piDataPacker.deviceOrientationQuat ) + { + if ( self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) + { + somePIfollowing = true; + } + else + { + somePIfollowing = false; + } + if ( somePIfollowing ) + { + PIheaderByte = ( PF0 << 7 ) | ( PM_NOT_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; + } + else + { + PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; + } + writeByteToRtp( self, (char) PIheaderByte ); + writeByteToRtp( self, PI_DEVICE_ORIENTATION_UNCOMPENSATED_SIZE_BYTES ); + } + + /* enable/disable headtracking */ + if ( self->piDataPacker.enableHeadtracking && !self->piDataPacker.disableHeadtracking ) + { + PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_ENABLE_HEADTRACKING; + writeByteToRtp( self, (char) PIheaderByte ); + writeByteToRtp( self, PI_ENABLE_HEADTRACKING_SIZE_BYTES ); + } + if ( self->piDataPacker.disableHeadtracking ) + { + PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DISABLE_HEADTRACKING; + writeByteToRtp( self, (char) PIheaderByte ); + writeByteToRtp( self, PI_DISABLE_HEADTRACKING_SIZE_BYTES ); + } + + /* PI frame data section */ + + if ( self->piDataPacker.sceneOrientationQuat ) + { + ivasPayload_convertOrientationToQ15( self->piDataPacker.sceneOrientationQuat, &sceneOrientationQ15 ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.w >> 8 ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.w & 0x00FF ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.x >> 8 ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.x & 0x00FF ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.y >> 8 ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.y & 0x00FF ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.z >> 8 ) ); + writeByteToRtp( self, (char) ( sceneOrientationQ15.z & 0x00FF ) ); + } + + if ( self->piDataPacker.deviceOrientationQuat ) + { + ivasPayload_convertOrientationToQ15( self->piDataPacker.deviceOrientationQuat, &deviceOrientationQ15 ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.w >> 8 ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.w & 0x00FF ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.x >> 8 ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.x & 0x00FF ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.y >> 8 ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.y & 0x00FF ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.z >> 8 ) ); + writeByteToRtp( self, (char) ( deviceOrientationQ15.z & 0x00FF ) ); + } +} + +void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +) +{ + if ( self->piDataPacker.disableHeadtracking == self->piDataPackerState.previousDisableHeadtrackingState ) + { + /* same state as before, do not send PI data */ + self->piDataPacker.disableHeadtracking = false; + } + else if ( self->piDataPacker.disableHeadtracking && self->piDataPackerState.previousDisableHeadtrackingState == false ) + { + /* headtracking was enabled, send disable headtracking PI data */ + self->piDataPackerState.previousDisableHeadtrackingState = true; + } + else if ( !self->piDataPacker.disableHeadtracking && self->piDataPackerState.previousDisableHeadtrackingState == true ) + { + /* headtracking was disabled, send enable headtracking PI data */ + self->piDataPackerState.previousDisableHeadtrackingState = false; + self->piDataPacker.enableHeadtracking = true; + } +} + +void IVAS_RTPDUMP_PACKER_determinePIpresence( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +) +{ + self->piDataPresent = false; + if ( self->piDataPacker.sceneOrientationQuat || + self->piDataPacker.deviceOrientationQuat || + self->piDataPacker.enableHeadtracking || + self->piDataPacker.disableHeadtracking ) + { + self->piDataPresent = true; + } +} + +void IVAS_RTPDUMP_PACKER_resetPIdata( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +) +{ + self->piDataPresent = false; + self->piDataPacker.sceneOrientationQuat = NULL; + self->piDataPacker.deviceOrientationQuat = NULL; + self->piDataPacker.enableHeadtracking = false; + self->piDataPacker.disableHeadtracking = false; +} + +void IVAS_RTPDUMP_PACKER_close( + IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ +) +{ + if ( !self || !( *self ) ) + { + return; + } + RTPDUMP_Close( &( *self )->rtpdump, 0 ); + free( *self ); + *self = NULL; +} +#endif diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h new file mode 100644 index 0000000000..7adf4eb4b2 --- /dev/null +++ b/lib_util/ivas_rtp_payload.h @@ -0,0 +1,421 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_RTP_PAYLOAD_H +#define IVAS_RTP_PAYLOAD_H + +#pragma once +#include +#include +#include +#include "common_api_types.h" +#include "ivas_cnst.h" +#include "rtpdump.h" + +#ifdef IVAS_RTPDUMP + +#ifdef __cplusplus +extern "C" +{ +#endif + + +static const int32_t IVASmode2rate[16] = { + 13200, + 16400, + 24400, + 32000, + 48000, + 64000, + 80000, + 96000, + 128000, + 160000, + 192000, + 256000, + 384000, + 512000, + -1, /* Reserved */ + 5200 /* SID */ +}; + +typedef enum +{ + PF0 = 0, + PF1 = 1 +} PI_PF; + +typedef enum +{ + PM_RESERVED = 0, + PM_NOT_LAST = 1, + PM_LAST = 2, + PM_GENERAL = 3 +} PI_PM; + +typedef enum +{ + PI_SCENE_ORIENTATION = 0, + PI_DEVICE_ORIENTATION_COMPENSATED = 1, + PI_DEVICE_ORIENTATION_UNCOMPENSATED = 2, + PI_ACOUSTIC_ENVIRONMENT = 3, + PI_ENABLE_HEADTRACKING = 4, + PI_DISABLE_HEADTRACKING = 5, + PI_NO_DATA = 31 +} PI_TYPE; + +typedef enum +{ + PI_SCENE_ORIENTATION_SIZE_BYTES = 8, + PI_DEVICE_ORIENTATION_COMPENSATED_SIZE_BYTES = 8, + PI_DEVICE_ORIENTATION_UNCOMPENSATED_SIZE_BYTES = 8, + PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES = 1, + PI_ACOUSTIC_ENVIRONMENT_ONLY_LATE_REVERB_SIZE_BYTES = 5, + PI_ACOUSTIC_ENVIRONMENT_LATE_REVERB_AND_EARLY_REFLECTIONS_SIZE_BYTES = 8, + PI_ENABLE_HEADTRACKING_SIZE_BYTES = 0, + PI_DISABLE_HEADTRACKING_SIZE_BYTES = 0, + PI_NO_DATA_SIZE_BYTES = 0 +} PI_SIZE_BYTES; + +typedef struct +{ + IVAS_QUATERNION *sceneOrientationQuat; + IVAS_QUATERNION *deviceOrientationQuat; + bool enableHeadtracking; + bool disableHeadtracking; +} PI_DATA_PACKER; + +typedef struct +{ + bool previousDisableHeadtrackingState; +} PI_DATA_PACKER_STATE; + +typedef struct +{ + uint16_t w, x, y, z; +} IVAS_QUATERNION_Q15; + +typedef enum +{ + IVAS_CMR_13k2 = 0xF0, // 1111 0000 + IVAS_CMR_16k4 = 0xF1, // 1111 0001 + IVAS_CMR_24k4 = 0xF2, // 1111 0010 + IVAS_CMR_32k = 0xF3, // 1111 0011 + IVAS_CMR_48k = 0xF4, // 1111 0100 + IVAS_CMR_64k = 0xF5, // 1111 0101 + IVAS_CMR_80k = 0xF6, // 1111 0110 + IVAS_CMR_96k = 0xF7, // 1111 0111 + IVAS_CMR_128k = 0xF8, // 1111 1000 + IVAS_CMR_160k = 0xF9, // 1111 1001 + IVAS_CMR_192k = 0xFA, // 1111 1010 + IVAS_CMR_256k = 0xFB, // 1111 1011 + IVAS_CMR_384k = 0xFC, // 1111 1100 + IVAS_CMR_512k = 0xFD, // 1111 1101 + IVAS_CMR_RESERVED = 0xFE, // 1111 1110 + IVAS_CMR_NO_REQ = 0xFF // 1111 1111 +} IVAS_INITIAL_E_BYTE; + +typedef enum +{ + IVAS_BW_REQ_WB = 0x80, // 1000 0000 + IVAS_BW_REQ_SWB = 0x81, // 1000 0001 + IVAS_BW_REQ_FB = 0x82, // 1000 0010 + IVAS_BW_NO_REQ = 0x83, // 1000 0011 + IVAS_FMT_REQ_STEREO = 0xA0, // 1010 0000 + IVAS_FMT_REQ_SBA = 0xA1, // 1010 0001 + IVAS_FMT_REQ_MASA = 0xA2, // 1010 0010 + IVAS_FMT_REQ_ISM = 0xA3, // 1010 0011 + IVAS_FMT_REQ_MC = 0xA4, // 1010 0100 + IVAS_FMT_REQ_OMASA = 0xA5, // 1010 0101 + IVAS_FMT_REQ_OSBA = 0xA6, // 1010 0110 + IVAS_FMT_NO_REQ = 0xA7, // 1010 0111 + IVAS_PI_INDICATION = 0xC0 // 1100 0000 +} IVAS_SUBSEQUENT_E_BYTE; + +typedef enum +{ + WB_REQUEST, + SWB_REQUEST, + FB_REQUEST, + NO_BANDWIDTH_REQUEST +} IVAS_BANDWIDTH_REQUEST; + +typedef enum +{ + STEREO_REQUEST, + SBA_REQUEST, + MASA_REQUEST, + ISM_REQUEST, + MC_REQUEST, + OMASA_REQUEST, + OSBA_REQUEST, + NO_FORMAT_REQUEST +} IVAS_FORMAT_REQUEST; + +typedef enum +{ + E_BYTE = 0x80, // 1xxx xxxx + IVAS_E_CMR = 0xF0, // 1111 xxxx + IVAS_E_BW_REQUEST = 0x80, // 100x xxxx + IVAS_E_FMT_REQUEST = 0xA0, // 101x xxxx + IVAS_E_PI_INDICATION = 0xC0, // 110x xxxx + IVAS_E_RESERVED = 0xE0 // 111x xxxx +} IVAS_E_BYTE_INDICATIONS; + +typedef struct +{ + RTPDUMP_HANDLE rtpdump; + RTPDUMP_RTPPACKET rtpPacket; + uint32_t timeoffset_ms; + uint16_t frameIndex; + bool frameFollowing; + PI_DATA_CURRENT_FRAME PIdataCurrentFrame; + PI_DATA_DEPACKER_STATE PIdataDepackerState; +} IVAS_RTPDUMP_DEPACKER; + +typedef struct +{ + RTPDUMP_HANDLE rtpdump; + RTPDUMP_RTPPACKET rtpPacket; + uint32_t timeoffset_ms; + PI_DATA_PACKER piDataPacker; + PI_DATA_PACKER_STATE piDataPackerState; + bool piDataPresent; + int32_t writingIndex; +} IVAS_RTPDUMP_PACKER; + +typedef enum +{ + IVAS_RTPDUMP_DEPACKER_NO_ERROR = 0, + IVAS_RTPDUMP_DEPACKER_EOF = -1, + IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, + IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR +} IVAS_RTPDUMP_DEPACKER_ERROR; + +typedef enum +{ + IVAS_RTPDUMP_PACKER_NO_ERROR = 0, + IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR = 1 +} IVAS_RTPDUMP_PACKER_ERROR; + + +/*-----------------------------------------------------------------------* + * ivasPayload_unpackFrame() + * + * Parse a single IVAS frame from the payload. Also read the IVAS payload header + * and parse the PI data associated with the IVAS frame. + *-----------------------------------------------------------------------*/ + +bool ivasPayload_unpackFrame( + char *payload, /* i : RTP payload data */ + uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ + uint16_t frameIndex, /* i : Index for the data frame in the payload */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeInBits, /* o : Data frame size in bits */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_DEPACKER_open() + * + * Open IVAS rtpdump depacker. + *-----------------------------------------------------------------------*/ + +IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( + IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ + FILE *file /* i : Input file containing the rtpdump */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_DEPACKER_readNextFrame() + * + * Unpack next frame from the RTP packet. If the packet is depleted, read the next RTP packet from the rtpdump. + *-----------------------------------------------------------------------*/ + +IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( + IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ + uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ + uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ + uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeBits /* o : Data frame size in bits */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_DEPACKER_close() + * + * Close IVAS rtpdump depacker. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_DEPACKER_close( + IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_open() + * + * Open IVAS rtpdump packer. + *-----------------------------------------------------------------------*/ + +IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( + IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ + FILE *file /* i : Output file for the rtpdump stream */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_writeEbytes() + * + * Write E-bytes (CMR and subsequent E-bytes) to the RTP packet. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_writeEbytes( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). + * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_writeToc() + * + * Write a single ToC byte to the RTP packet. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_writeToc( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t frameBitrate, /* i : Bitrate of the written frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). + * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ + bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_writeNextFrame() + * + * Write IVAS payload header, a single frame data and optional PI data to an RTP packet. + *-----------------------------------------------------------------------*/ + +IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ + int32_t numBits, /* i : Number of bits contained in the data frame */ + int32_t frameBitrate, /* i : Bitrate of the data frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_writePIdata() + * + * Write PI data section (PI headers and PI frame data) to the RTP packet. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_writePIdata( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState() + * + * Check for disable headtracking input and previously set disable/enable headtracking state. + * Set the current PI headtracking state accordingly. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_determinePIpresence() + * + * Check if there is PI data present for the current frame. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_determinePIpresence( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_resetPIdata() + * + * Reset PI data packer handle. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_resetPIdata( + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +); + + +/*-----------------------------------------------------------------------* + * IVAS_RTPDUMP_PACKER_close() + * + * Close IVAS rtpdump packer. + *-----------------------------------------------------------------------*/ + +void IVAS_RTPDUMP_PACKER_close( + IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ +); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif /* IVAS_RTP_PAYLOAD_H */ diff --git a/lib_util/rtpdump.c b/lib_util/rtpdump.c index 9f87849473..ad2345d236 100644 --- a/lib_util/rtpdump.c +++ b/lib_util/rtpdump.c @@ -264,6 +264,34 @@ RTPDUMP_OpenForWriting( RTPDUMP_HANDLE *phRTPDUMP, const char *filename ) return RTPDUMP_NO_ERROR; } + +#ifdef IVAS_RTPDUMP +RTPDUMP_ERROR +RTPDUMP_OpenWithFileToWrite( RTPDUMP_HANDLE *phRTPDUMP, FILE *file ) +{ + *phRTPDUMP = (RTPDUMP_HANDLE) calloc( 1, sizeof( struct RTPDUMP ) ); + if ( !phRTPDUMP || !( *phRTPDUMP ) ) + { + return RTPDUMP_MEMORY_ERROR; + } + + /* open file stream */ + ( *phRTPDUMP )->file = file; + if ( ( *phRTPDUMP )->file == NULL ) + { + return RTPDUMP_FILE_NOT_FOUND; + } + + if ( writeHeader( *phRTPDUMP ) != 0 ) + { + return RTPDUMP_INIT_ERROR; + } + + return RTPDUMP_NO_ERROR; +} + + +#endif RTPDUMP_ERROR RTPDUMP_ReadPacket( RTPDUMP_HANDLE hRTPDUMP, RTPDUMP_RTPPACKET *packet, diff --git a/lib_util/rtpdump.h b/lib_util/rtpdump.h index b97ec97d83..fa59bc9e6b 100644 --- a/lib_util/rtpdump.h +++ b/lib_util/rtpdump.h @@ -37,6 +37,7 @@ #pragma once #include #include +#include "options.h" #ifdef __cplusplus extern "C" @@ -83,6 +84,11 @@ extern "C" RTPDUMP_ERROR RTPDUMP_OpenForWriting( RTPDUMP_HANDLE *phRTPDUMP, const char *filename ); +#ifdef IVAS_RTPDUMP + RTPDUMP_ERROR + RTPDUMP_OpenWithFileToWrite( RTPDUMP_HANDLE *phRTPDUMP, FILE *file ); + +#endif RTPDUMP_ERROR RTPDUMP_ReadPacket( RTPDUMP_HANDLE hRTPDUMP, RTPDUMP_RTPPACKET *packet, diff --git a/readme.txt b/readme.txt index e6fbd729ea..d3e4a122b3 100644 --- a/readme.txt +++ b/readme.txt @@ -249,6 +249,13 @@ EVS mono is default, for IVAS choose one of the following: -stereo, -ism, -sba, -level level : Complexity level, level = (1, 2, 3), will be defined after characterisation. Currently, all values default to level 3 (full functionality). -q : Quiet mode, limit printouts to terminal, default is deactivated +-rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the + bitstream frames into TS26.253 Annex A IVAS RTP Payload Format packets and + writes those to the output file. In EVS mono operating mode, TS26.445 Annex A.2.2 + EVS RTP Payload Format is used. +-scene_orientation : Scene orientation trajectory file. Only used with rtpdump output. +-device_orientation : Device orientation trajectory file. Only used with rtpdump output. +-disable_headtracking_file : Disable headtracking input file. (1) to disable and (0) to enable headtracking. Only used with rtpdump output. The usage of the "IVAS_dec" program is as follows: @@ -274,9 +281,10 @@ Options: -------- -VOIP : VoIP mode: RTP in G192 -VOIP_hf_only=0 : VoIP mode: EVS RTP Payload Format hf_only=0 in rtpdump --VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump +-VOIP_hf_only=1 : VoIP mode: EVS or IVAS RTP Payload Format hf_only=1 in rtpdump The decoder may read rtpdump files containing TS26.445 Annex A.2.2 - EVS RTP Payload Format. The SDP parameter hf_only is required. + EVS RTP Payload Format or rtpdump files containing TS26.253 Annex A + IVAS RTP Payload Format. The SDP parameter hf_only is required. Reading RFC4867 AMR/AMR-WB RTP payload format is not supported. -Tracefile TF : VoIP mode: Generate trace file named TF -fec_cfg_file : Optimal channel aware configuration computed by the JBM -- GitLab From 8f8e5ba3be1ea10ee0e88c21d9c7d423c01ba5f8 Mon Sep 17 00:00:00 2001 From: Stefan Doehla Date: Mon, 17 Feb 2025 14:19:10 +0100 Subject: [PATCH 002/340] [fix] merge conflicts --- lib_com/common_api_types.h | 3 --- lib_enc/lib_enc.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index ade2a44ab1..443963c364 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -213,7 +213,6 @@ typedef struct _IVAS_JBM_TRACE_DATA } IVAS_JBM_TRACE_DATA; -<<<<<<< HEAD #ifdef IVAS_RTPDUMP typedef struct { @@ -236,7 +235,6 @@ typedef struct IVAS_QUATERNION deviceOrientationQuat; } PI_DATA_DEPACKER_STATE; #endif -======= typedef enum _ivas_binaural_renderer_type { IVAS_BIN_RENDERER_TYPE_NONE, @@ -247,7 +245,6 @@ typedef enum _ivas_binaural_renderer_type IVAS_BIN_RENDERER_TYPE_DEFAULT, } IVAS_BIN_RENDERER_TYPE; ->>>>>>> main /*----------------------------------------------------------------------------------* * Split rendering API constants, structures, and enums diff --git a/lib_enc/lib_enc.c b/lib_enc/lib_enc.c index 2cd3bcc3e5..167dcfb7e4 100644 --- a/lib_enc/lib_enc.c +++ b/lib_enc/lib_enc.c @@ -2435,7 +2435,7 @@ bool IVAS_isImmersiveFormat( return false; } return true; - +} #endif #ifdef DEBUGGING -- GitLab From c4ca71cb7cd579354242e3b41b08ece8dff27bc3 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 27 Feb 2025 16:12:30 +0200 Subject: [PATCH 003/340] Update MSVC project with RTPdump files --- Workspace_msvc/lib_util.vcxproj | 4 ++++ Workspace_msvc/lib_util.vcxproj.filters | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index bead7110f3..28832eb4f0 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -108,8 +108,10 @@ + + @@ -137,8 +139,10 @@ + + diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index 8fc8082d1a..f3824f1fe4 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -82,6 +82,12 @@ util_c + + util_c + + + util_c + @@ -171,6 +177,12 @@ util_h + + util_h + + + util_h + -- GitLab From ce724717a4c44abe5007a320bd18d5bfeabd1333 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 4 Mar 2025 16:47:19 +0200 Subject: [PATCH 004/340] Use 'rb' for opening bitstream file for RTPdump reading --- apps/decoder.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/decoder.c b/apps/decoder.c index b9191c5ed7..1e2de7189e 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3096,7 +3096,11 @@ static ivas_error decodeVoIP( { case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: +#ifdef IVAS_RTPDUMP + f_rtpstream = fopen( arg.inputBitstreamFilename, "rb" ); +#else f_rtpstream = fopen( arg.inputBitstreamFilename, "r" ); +#endif if ( f_rtpstream == NULL ) { -- GitLab From 3ad4e261bff94a4dd7831642f063a493e97f58f1 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 8 Apr 2025 14:19:58 +0300 Subject: [PATCH 005/340] Check also rptdumpPacker presence in if-statement --- apps/encoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/encoder.c b/apps/encoder.c index 5aff394659..5e975ab21b 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -857,7 +857,7 @@ int main( } #ifdef IVAS_RTPDUMP - if ( rtpdumpPacker->rtpdump ) + if ( rtpdumpPacker && rtpdumpPacker->rtpdump ) { /* scene orientation */ if ( sceneOrientationFileReader ) -- GitLab From fb4b45b83722751467978fc2746e931ef7c61576 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 8 Apr 2025 16:01:16 +0300 Subject: [PATCH 006/340] Determine bitrate for rtpdump based on the number of encoded bits --- apps/encoder.c | 2 +- lib_util/ivas_rtp_payload.c | 20 ++++++++++++++++++-- lib_util/ivas_rtp_payload.h | 1 - 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/encoder.c b/apps/encoder.c index 5e975ab21b..c0966533e5 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -898,7 +898,7 @@ int main( IVAS_RTPDUMP_PACKER_determinePIpresence( rtpdumpPacker ); /* write rtpdump */ - rtpdumpPackerError = IVAS_RTPDUMP_PACKER_writeNextFrame( rtpdumpPacker, bitStream, numBits, totalBitrate, + rtpdumpPackerError = IVAS_RTPDUMP_PACKER_writeNextFrame( rtpdumpPacker, bitStream, numBits, IVAS_isImmersiveFormat( hIvasEnc ), 0, false, NO_BANDWIDTH_REQUEST, NO_FORMAT_REQUEST ); if ( rtpdumpPackerError != IVAS_RTPDUMP_PACKER_NO_ERROR ) { diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 9700d47c23..2366c65698 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -58,6 +58,21 @@ static void writeByteToRtp( } +/*-----------------------------------------------------------------------* + * determineBitrate() + * + * Determine bitrate based on the number of encoded bits. + *-----------------------------------------------------------------------*/ + +static void determineBitrate( + const int32_t numBits, /* i : Number of bits contained in the data frame */ + int32_t *frameBitrate /* o : Bitrate of the data frame */ +) +{ + *frameBitrate = numBits * IVAS_NUM_FRAMES_PER_SEC; +} + + /*-----------------------------------------------------------------------* * ivasPayload_parseToc() * @@ -871,7 +886,6 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ int32_t numBits, /* i : Number of bits contained in the data frame */ - int32_t frameBitrate, /* i : Bitrate of the data frame */ bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ @@ -881,13 +895,15 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( { RTPDUMP_ERROR rtpdumpError; - int32_t i, k; + int32_t i, k, frameBitrate; char tempData; self->writingIndex = self->rtpPacket.headerSize; self->rtpPacket.payloadSize = 0; memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); + determineBitrate( numBits, &frameBitrate ); + /* payload header */ IVAS_RTPDUMP_PACKER_writeEbytes(self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); IVAS_RTPDUMP_PACKER_writeToc( self, frameBitrate, isImmersiveFormat, false ); diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 7adf4eb4b2..159e18a384 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -348,7 +348,6 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ int32_t numBits, /* i : Number of bits contained in the data frame */ - int32_t frameBitrate, /* i : Bitrate of the data frame */ bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ -- GitLab From 6a4215f7faa1156c8b26467538cd9c45a446d257 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 22 Apr 2025 13:01:23 +0300 Subject: [PATCH 007/340] Add rtpdump tests to self_test parameter file --- scripts/config/self_test.prm | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/scripts/config/self_test.prm b/scripts/config/self_test.prm index 7db62aef58..7dd2d2e301 100644 --- a/scripts/config/self_test.prm +++ b/scripts/config/self_test.prm @@ -306,6 +306,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit ../IVAS_dec EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT.tst +// stereo bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, EXT out, rtpdump +../IVAS_cod -rtpdump -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit +../IVAS_dec -VOIP_hf_only=1 EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT_rtpdump.tst // 1 ISM with metadata at 13.2 kbps, 48 kHz in, 48 kHz out, EXT out @@ -580,6 +583,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism +4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv 128000 48 testv/stv4ISM48n.wav bit ../IVAS_dec BINAURAL_ROOM_REVERB 48 bit testv/stv4ISM48n.wav_BINAURAL_ROOM_REVERB_128000_48-48.tst +// 4 ISM with metadata bitrate switching from 24.4 kbps to 512 kbps, 48 kHz in, 48 kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case00_3000_q.csv -dtx -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_24k4_512k.bin 48 testv/stv4ISM48s.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv4ISM48s.wav_brate_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst // SBA at 13.2 kbps, 32kHz in, 32kHz out, HOA3 out, bandwidth switching @@ -958,6 +964,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -sba 3 96000 48 testv/stv3OA48c.wav bit ../IVAS_dec -render_config testv/rend_config_recreation.cfg BINAURAL_ROOM_REVERB 48 bit testv/stv3OA48c.wav_BINAURAL_ROOM_REVERB_96000_48-48_custom_configuration.tst +// SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit +../IVAS_dec -VOIP_hf_only=1 FOA 48 bit testv/stvFOA48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst // MASA 1dir 1TC at 13.2 kbps, 48kHz in, 48kHz out, BINAURAL out, bandwidth switching @@ -1238,6 +1247,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -masa 1 testv/stv1MASA1TC48c.met 256000 48 testv/stv1MASA1TC48c.wav bit ../IVAS_dec -render_config testv/rend_config_combined.cfg -t testv/headrot.csv BINAURAL_ROOM_REVERB 48 bit testv/stv1MASA1TC48c.wav_BINAURAL_ROOM_REVERB_256000_48-48_Headrot_custom_config.tst +// MASA 2dir 2TC bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot_case03_3000_q.csv -device_orientation testv/headrot.csv -dtx -masa 2 testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv2MASA2TC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv2MASA2TC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst // Multi-channel 5_1 at 13.2 kbps, 48kHz in, 48kHz out @@ -1561,6 +1573,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -mc 7_1_4 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv714MC48c.wav bit ../IVAS_dec EXT 48 bit testv/stv714MC48c.wav_sw_48-48_EXT.tst +// Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst // Stereo downmix to bit-exact EVS at 13200 kbps, 32kHz in, 32kHz out @@ -1571,6 +1586,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit ../IVAS_dec 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48.tst +// Stereo downmix to bit-exact EVS at 24400 kbps, 48kHz in, 48kHz out, rtpdump +../IVAS_cod -rtpdump -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit +../IVAS_dec -VOIP_hf_only=1 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48_rtpdump.tst // EVS non-diegetic panning at 64 kbps, 48kHz in, 48kHz out, STEREO out @@ -1581,6 +1599,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism 1 testv/stvISM1.csv 32000 48 testv/stv1ISM48s.wav bit ../IVAS_dec -non_diegetic_pan 80 STEREO 48 bit testv/stv1ISM48s.pcm_ISM_32000_48-48_STEREO_NON-DIEGETIC-PAN_80.tst +// EVS at 13.2 kbps, 48kHz in, 48kHz out, STEREO out, rtpdump +../IVAS_cod -rtpdump 13200 48 testv/stv48c.wav bit +../IVAS_dec -VOIP_hf_only=1 48 bit testv/stv48c.pcm_EVS_13200_48-48_STEREO_rtpdump.tst // stereo at 32 kbps, 48 kHz in, 32 kHz out, DTX on, JBM Prof 0 ../IVAS_cod -stereo -dtx 32000 48 testv/stvST48n.wav bit @@ -1857,6 +1878,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism_masa 4 2 testv/stvISM1.csv NULL testv/stvISM3.csv testv/stvISM4.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_80k_omasatechs_4ism.bin 48 testv/stvOMASA_4ISM_2MASA2TC48c.wav bit ../IVAS_dec EXT 48 bit testv/stvOMASA_4ISM_2MASA2TC48c.wav_EXT_sw_48-48.tst +// OMASA 2Dir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case00_3000_q.csv -ism_masa 3 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_160k_omasatechs_3ism.bin 48 testv/stvOMASA_3ISM_2MASA2TC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOMASA_3ISM_2MASA2TC48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst // OSBA FOA 1ISM at 32 kbps, 48kHz in, 48kHz out, BINAURAL out @@ -2056,3 +2080,7 @@ networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit ../IVAS_cod -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_48-32k_10fr.bin 48 testv/stv4ISM48s.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 ../IVAS_dec -obj_edit ../scripts/object_edit/combined_edit.txt -Tracefile tracefile_dec -VOIP BINAURAL 48 netsimoutput testv/stv4ISM48s.wav_sw_48-48_BINAURAL_OE_JBM5.tst + +// OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst -- GitLab From 9b6129e1c6807a5daf55243051ccf9190c625a40 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 22 Apr 2025 14:32:37 +0300 Subject: [PATCH 008/340] Assign identity vector components separately --- lib_rend/ivas_rotation.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index bc87251a4d..34215225a2 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -741,7 +741,10 @@ void ivas_external_orientation_reset( int16_t i; IVAS_QUATERNION identity; - identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; + identity.w = 1.0f; + identity.x = 0.0f; + identity.y = 0.0f; + identity.z = 0.0f; /* Enable head rotation and disable external orientation as default */ for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) { @@ -796,14 +799,9 @@ ivas_error ivas_combined_orientation_open( IVAS_VECTOR3 origo; int16_t pos_idx; -#ifdef IVAS_RTPDUMP - identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; - origo = (IVAS_VECTOR3) { .x = 0.0f, .y = 0.0f, .z = 0.0f }; -#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; origo.x = origo.y = origo.z = 0.0f; -#endif /* Allocate handle */ if ( ( *hCombinedOrientationData = (COMBINED_ORIENTATION_HANDLE) malloc( sizeof( COMBINED_ORIENTATION_DATA ) ) ) == NULL ) @@ -988,14 +986,9 @@ ivas_error combine_external_and_head_orientations( IVAS_QUATERNION identity; IVAS_VECTOR3 origo; -#ifdef IVAS_RTPDUMP - identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; - origo = (IVAS_VECTOR3) { .x = 0.0f, .y = 0.0f, .z = 0.0f }; -#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; origo.x = origo.y = origo.z = 0.0f; -#endif /* Form combined orientations or return if no data available */ if ( hCombinedOrientationData == NULL ) @@ -1291,12 +1284,8 @@ static void external_target_interpolation( if ( hExtOrientationData->enableExternalOrientation[i - 1] == 0 ) { IVAS_QUATERNION identity; -#ifdef IVAS_RTPDUMP - identity = (IVAS_QUATERNION) { .w = 1.0f, .x = 0.0f, .y = 0.0f, .z = 0.0f }; -#else identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; -#endif hCombinedOrientationData->Quaternions_ext_interpolation_start = identity; } else if ( hExtOrientationData->enableExternalOrientation[i - 1] == 2 ) -- GitLab From c1595ad357432c1ffd45205c39360c6a8fa9afef Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 23 Apr 2025 13:32:53 +0300 Subject: [PATCH 009/340] Edit test cases, check also for combined orientation handle when checking orientation input --- lib_dec/ivas_binRenderer_internal.c | 4 ++++ lib_dec/ivas_dirac_dec.c | 4 ++++ lib_dec/ivas_init_dec.c | 4 ++++ lib_dec/ivas_mc_param_dec.c | 4 ++++ lib_dec/ivas_mc_paramupmix_dec.c | 4 ++++ lib_dec/ivas_mct_dec.c | 4 ++++ lib_dec/ivas_output_config.c | 21 ++++++++++++++++++++- scripts/config/self_test.prm | 14 +++++++------- 8 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index 4b2c4c7d07..8258a47c20 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -1028,7 +1028,11 @@ ivas_error ivas_binRenderer_open( ivas_dirac_dec_get_response( (int16_t) ls_azimuth_CICP19[k], (int16_t) ls_elevation_CICP19[k], hBinRenderer->hReverb->foa_enc[k], 1 ); } } +#ifdef IVAS_RTPDUMP + else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 6ce9439d4b..c8136ad4e1 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1999,7 +1999,11 @@ void ivas_dirac_dec_render_sf( { mvs2s( &hSpatParamRendCom->azimuth[md_idx][hDirAC->hConfig->enc_param_start_band], &azimuth[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); mvs2s( &hSpatParamRendCom->elevation[md_idx][hDirAC->hConfig->enc_param_start_band], &elevation[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) +#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) +#endif { num_freq_bands = hDirAC->band_grouping[hDirAC->hConfig->enc_param_start_band]; rotateAziEle_DirAC( azimuth, elevation, num_freq_bands, hSpatParamRendCom->num_freq_bands, p_Rmat ); diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index aac6720870..e4fe450a4e 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -2153,7 +2153,11 @@ ivas_error ivas_init_decoder( } else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV || st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM ) { +#ifdef IVAS_RTPDUMP + if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index 048f26e379..49c1b8a033 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -401,7 +401,11 @@ ivas_error ivas_param_mc_dec_open( ivas_param_mc_dec_compute_interpolator( 0, 0, DEFAULT_JBM_CLDFB_TIMESLOTS, hParamMC->h_output_synthesis_params.interpolator ); /* Head or external rotation */ +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( hParamMC->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index 81bd4023a4..3d5353130c 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -349,7 +349,11 @@ ivas_error ivas_mc_paramupmix_dec_open( } /* Head or external rotation */ +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( hMCParamUpmix->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mct_dec.c b/lib_dec/ivas_mct_dec.c index 4c2ace3908..9bf9c9098e 100644 --- a/lib_dec/ivas_mct_dec.c +++ b/lib_dec/ivas_mct_dec.c @@ -1179,7 +1179,11 @@ static ivas_error ivas_mc_dec_reconfig( if ( st_ivas->hBinRenderer != NULL && ( st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV_ROOM ) ) { ivas_binRenderer_close( &st_ivas->hBinRenderer ); +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { efap_free_data( &st_ivas->hEFAPdata ); } diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index 305d99ef99..2c6bce7cc1 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -70,8 +70,11 @@ void ivas_renderer_select( /*-----------------------------------------------------------------* * Binaural rendering configurations *-----------------------------------------------------------------*/ - +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { st_ivas->hCombinedOrientationData->shd_rot_max_order = -1; } @@ -155,7 +158,11 @@ void ivas_renderer_select( *internal_config = IVAS_AUDIO_CONFIG_7_1_4; } +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { nchan_internal = ivas_sba_get_nchan_metadata( st_ivas->sba_analysis_order, st_ivas->hDecoderConfig->ivas_total_brate ); @@ -199,9 +206,17 @@ void ivas_renderer_select( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) { #ifdef DEBUGGING +#ifdef IVAS_RTPDUMP + if ( ( ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) || ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) ) && st_ivas->mc_mode == MC_MODE_MCT && st_ivas->hDecoderConfig->force_rend != FORCE_CLDFB_RENDERER ) +#else if ( ( ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) || ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) ) && st_ivas->mc_mode == MC_MODE_MCT && st_ivas->hDecoderConfig->force_rend != FORCE_CLDFB_RENDERER ) +#endif +#else +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->mc_mode == MC_MODE_MCT ) #else if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->mc_mode == MC_MODE_MCT ) +#endif #endif { *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; @@ -217,7 +232,11 @@ void ivas_renderer_select( *renderer_type = RENDERER_BINAURAL_FASTCONV; } +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { /* force HOA3 domain for rotation*/ *internal_config = IVAS_AUDIO_CONFIG_HOA3; diff --git a/scripts/config/self_test.prm b/scripts/config/self_test.prm index 7dd2d2e301..ebe07a3101 100644 --- a/scripts/config/self_test.prm +++ b/scripts/config/self_test.prm @@ -1573,9 +1573,9 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -mc 7_1_4 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv714MC48c.wav bit ../IVAS_dec EXT 48 bit testv/stv714MC48c.wav_sw_48-48_EXT.tst -// Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst +// Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_BINAURAL_rtpdump_PIdata.tst // Stereo downmix to bit-exact EVS at 13200 kbps, 32kHz in, 32kHz out @@ -2022,6 +2022,10 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_10pct.g networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 ../IVAS_dec -Tracefile tracefile_dec -VOIP BINAURAL_ROOM_REVERB 48 netsimoutput testv/stvOSBA_2ISM_2OA32c.wav_BINAURAL_brsw_32-48_JBM5.tst +// OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst + // OMASA 2Dir2TC 4ISM at 80 kbps, 48kHz in, 48kHz out, BINAURAL out, default object editing, 1SEP-PARAM ../IVAS_cod -ism_masa 4 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv testv/stv2MASA2TC48c.met 80000 48 testv/stvOMASA_4ISM_2MASA2TC48c.wav bit @@ -2080,7 +2084,3 @@ networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit ../IVAS_cod -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_48-32k_10fr.bin 48 testv/stv4ISM48s.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 ../IVAS_dec -obj_edit ../scripts/object_edit/combined_edit.txt -Tracefile tracefile_dec -VOIP BINAURAL 48 netsimoutput testv/stv4ISM48s.wav_sw_48-48_BINAURAL_OE_JBM5.tst - -// OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst -- GitLab From 360eef8c4cda1219c9df7a9b4dd7072aeff5121f Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 23 Apr 2025 14:47:48 +0300 Subject: [PATCH 010/340] Clang format --- apps/decoder.c | 7 +- apps/encoder.c | 4 +- lib_com/bitstream.c | 2 +- lib_dec/lib_dec.c | 24 +- lib_enc/lib_enc.c | 3 +- lib_rend/ivas_rotation.c | 2 +- lib_util/disable_headtracking_file_reader.c | 16 +- lib_util/disable_headtracking_file_reader.h | 12 +- lib_util/evs_rtp_payload.h | 304 ++++++++++---------- lib_util/ivas_rtp_payload.c | 200 ++++++------- lib_util/ivas_rtp_payload.h | 220 +++++++------- 11 files changed, 401 insertions(+), 393 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 020510f928..61a540e9f2 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3186,7 +3186,7 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame(&rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSize ); + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSize ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif @@ -3351,7 +3351,7 @@ static ivas_error decodeVoIP( { /* feed the previous read packet into the receiver now */ #ifdef IVAS_RTPDUMP - error = IVAS_DEC_VoIP_FeedFrame(hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, ivasIndicatorOrQBit ); + error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, ivasIndicatorOrQBit ); #else error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, qBit ); #endif @@ -3400,7 +3400,6 @@ static ivas_error decodeVoIP( /* EVS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; #endif - } #ifdef IVAS_RTPDUMP if ( error == IVAS_ERR_END_OF_FILE || rtpdumpDepackerError == IVAS_RTPDUMP_DEPACKER_EOF ) @@ -3618,7 +3617,7 @@ static ivas_error decodeVoIP( } #ifdef IVAS_RTPDUMP - IVAS_DEC_resetExternalOrientations(hIvasDec); + IVAS_DEC_resetExternalOrientations( hIvasDec ); #endif vec_pos_update = ( vec_pos_update + 1 ) % vec_pos_len; diff --git a/apps/encoder.c b/apps/encoder.c index 01f153959c..3d1e160f7d 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -673,9 +673,9 @@ int main( * Open disable headtracking file *------------------------------------------------------------------------------------------*/ - if (arg.disableHeadtrackingFileName != NULL ) + if ( arg.disableHeadtrackingFileName != NULL ) { - if (( error = DisableHeadtrackingFileReader_open(arg.disableHeadtrackingFileName, &disableHeadtrackingFileReader ) ) != IVAS_ERR_OK ) + if ( ( error = DisableHeadtrackingFileReader_open( arg.disableHeadtrackingFileName, &disableHeadtrackingFileReader ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError: Can't open disable headtracking file %s \n\n", arg.disableHeadtrackingFileName ); goto cleanup; diff --git a/lib_com/bitstream.c b/lib_com/bitstream.c index c281da1a0a..22fbcde10e 100644 --- a/lib_com/bitstream.c +++ b/lib_com/bitstream.c @@ -170,7 +170,7 @@ Word16 rate2EVSmode( * Lookup IVAS mode. Note: FRAME_NO_DATA frames should be looked up with rate2EVSmode(). *-------------------------------------------------------------------*/ Word16 rate2IVASmode( - const Word32 brate /* i : bitrate */ + const Word32 brate /* i : bitrate */ ) { switch ( brate ) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 786f911ada..072f274506 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3736,10 +3736,10 @@ ivas_error IVAS_DEC_Flush( *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_parseSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - char *orientation, /* i : input PI orientation */ - bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + char *orientation, /* i : input PI orientation */ + bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ ) { int16_t i; @@ -3780,8 +3780,8 @@ ivas_error IVAS_DEC_parseSinglePIorientation( /* use the new PI orientation or the previously saved orientation in processing */ for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) { - QuaternionProduct(hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], *savedInvOrientation, - &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i]); + QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], *savedInvOrientation, + &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; } hIvasDec->updateOrientation = true; @@ -3797,8 +3797,8 @@ ivas_error IVAS_DEC_parseSinglePIorientation( *---------------------------------------------------------------------*/ void IVAS_DEC_parsePIenableDisableHeadtracking( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ ) { if ( PIdataCurrentFrame->enableHeadTracking ) @@ -3833,13 +3833,13 @@ ivas_error IVAS_DEC_parsePIdata( ivas_error error = IVAS_ERR_OK; /* scene orientation */ - if (( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } /* device orientation */ - if (( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } @@ -3858,7 +3858,7 @@ ivas_error IVAS_DEC_parsePIdata( *---------------------------------------------------------------------*/ void IVAS_DEC_resetExternalOrientations( - IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ + IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ ) { ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); @@ -3872,7 +3872,7 @@ void IVAS_DEC_resetExternalOrientations( *---------------------------------------------------------------------*/ void IVAS_DEC_resetPIdataCurrentFrame( - PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ + PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ ) { resetPIdataCurrentFrame( piDataCurrentFrame ); diff --git a/lib_enc/lib_enc.c b/lib_enc/lib_enc.c index 993e2773fd..b2ea192118 100644 --- a/lib_enc/lib_enc.c +++ b/lib_enc/lib_enc.c @@ -2427,8 +2427,7 @@ static void init_encoder_config( *---------------------------------------------------------------------*/ bool IVAS_isImmersiveFormat( - const IVAS_ENC_HANDLE hIvasEnc -) + const IVAS_ENC_HANDLE hIvasEnc ) { if ( hIvasEnc->st_ivas->hEncoderConfig->ivas_format == MONO_FORMAT ) { diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 34215225a2..93c0cd0996 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -733,7 +733,7 @@ ivas_error ivas_external_orientation_open( *-----------------------------------------------------------------------*/ void ivas_external_orientation_reset( - EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ + EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ ) { if ( *hExtOrientationData != NULL ) diff --git a/lib_util/disable_headtracking_file_reader.c b/lib_util/disable_headtracking_file_reader.c index 985733dc83..992464a6f9 100644 --- a/lib_util/disable_headtracking_file_reader.c +++ b/lib_util/disable_headtracking_file_reader.c @@ -55,8 +55,8 @@ struct DisableHeadtrackingFileReader *-----------------------------------------------------------------------*/ ivas_error DisableHeadtrackingFileReader_open( - char *filePath, /* i : disable-headtracking input file name */ - DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ + char *filePath, /* i : disable-headtracking input file name */ + DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ ) { DisableHeadtrackingFileReader *self; @@ -68,7 +68,7 @@ ivas_error DisableHeadtrackingFileReader_open( return IVAS_ERR_FAILED_FILE_OPEN; } - inputFile = fopen(filePath, "r" ); + inputFile = fopen( filePath, "r" ); if ( !inputFile ) { @@ -95,8 +95,8 @@ ivas_error DisableHeadtrackingFileReader_open( *-----------------------------------------------------------------------*/ ivas_error DisableHeadtrackingFileReading( - DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ - bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ + DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ + bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ ) { float disableHeadTrackingFlag; @@ -105,7 +105,7 @@ ivas_error DisableHeadtrackingFileReading( /* Initial values, if they are not read from the file (enable headtracking by default) */ disableHeadTrackingFlag = 0; - read_values = fscanf(fileReader->inputFile, "%f", &disableHeadTrackingFlag ); + read_values = fscanf( fileReader->inputFile, "%f", &disableHeadTrackingFlag ); if ( ( read_values != 1 ) ) { if ( feof( fileReader->inputFile ) ) @@ -132,7 +132,7 @@ ivas_error DisableHeadtrackingFileReading( *-----------------------------------------------------------------------*/ void DisableHeadtrackingFileReader_close( - DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ + DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ ) { if ( fileReader == NULL || *fileReader == NULL ) @@ -156,7 +156,7 @@ void DisableHeadtrackingFileReader_close( *-----------------------------------------------------------------------*/ const char *DisableHeadtrackingFileReader_getFilePath( - DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ + DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ ) { if ( fileReader == NULL ) diff --git a/lib_util/disable_headtracking_file_reader.h b/lib_util/disable_headtracking_file_reader.h index 4d413b442c..1dda351565 100644 --- a/lib_util/disable_headtracking_file_reader.h +++ b/lib_util/disable_headtracking_file_reader.h @@ -47,8 +47,8 @@ typedef struct DisableHeadtrackingFileReader DisableHeadtrackingFileReader; *-----------------------------------------------------------------------*/ ivas_error DisableHeadtrackingFileReader_open( - char *filePath, /* i : disable-headtracking input file name */ - DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ + char *filePath, /* i : disable-headtracking input file name */ + DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ ); /*-----------------------------------------------------------------------* @@ -58,8 +58,8 @@ ivas_error DisableHeadtrackingFileReader_open( *-----------------------------------------------------------------------*/ ivas_error DisableHeadtrackingFileReading( - DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ - bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ + DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ + bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ ); /*-----------------------------------------------------------------------* @@ -69,7 +69,7 @@ ivas_error DisableHeadtrackingFileReading( *-----------------------------------------------------------------------*/ void DisableHeadtrackingFileReader_close( - DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ + DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ ); /*-----------------------------------------------------------------------* @@ -79,7 +79,7 @@ void DisableHeadtrackingFileReader_close( *-----------------------------------------------------------------------*/ const char *DisableHeadtrackingFileReader_getFilePath( - DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ + DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ ); #endif diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 74c4a56a56..490f9560ff 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -45,161 +45,165 @@ extern "C" { #endif - static const int32_t AMRWB_IOmode2rate[16] = { - 6600, /* AMRWB_IO_6600 */ - 8850, /* AMRWB_IO_8850 */ - 12650, /* AMRWB_IO_1265 */ - 14250, /* AMRWB_IO_1425 */ - 15850, /* AMRWB_IO_1585 */ - 18250, /* AMRWB_IO_1825 */ - 19850, /* AMRWB_IO_1985 */ - 23050, /* AMRWB_IO_2305 */ - 23850, /* AMRWB_IO_2385 */ - 1750, /* AMRWB_IO_SID: SID_1k75 followed by STI bit and CMI bits (A.2.2.1.3) */ - -1, /* AMRWB_IO_FUT1 */ - -1, /* AMRWB_IO_FUT2 */ - -1, /* AMRWB_IO_FUT3 */ - -1, /* AMRWB_IO_FUT4 */ - 0, /* SPEECH_LOST */ - 0 /* NO_DATA */ - }; - - static const int32_t PRIMARYmode2rate[16] = { - 2800, /* PRIMARY_2800 */ - 7200, /* PRIMARY_7200 */ - 8000, /* PRIMARY_8000 */ - 9600, /* PRIMARY_9600 */ - 13200, /* PRIMARY_13200 */ - 16400, /* PRIMARY_16400 */ - 24400, /* PRIMARY_24400 */ - 32000, /* PRIMARY_32000 */ - 48000, /* PRIMARY_48000 */ - 64000, /* PRIMARY_64000 */ - 96000, /* PRIMARY_96000 */ - 128000, /* PRIMARY_128000 */ - 2400, /* PRIMARY_SID */ - -1, /* PRIMARY_FUT1 */ - 0, /* SPEECH_LOST */ - 0 /* NO_DATA */ - }; - - static const uint16_t evsPayloadProtectedSizes[22] = { - 48, - 56, - 136, - 144, - 160, - 184, - 192, - 256, - 264, - 288, - 320, - 328, - 368, - 400, - 464, - 480, - 488, - 640, - 960, - 1280, - 1920, - 2560 - }; - - static const bool evsPayloadProtectedSizes_isAMRWB_IOmode[22] = { - 0, - 0, /* Special case (see clause A.2.1.3) */ - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - 0 - }; - - static const uint16_t evsPayloadProtectedSizes_frameTypeIndex[22] = { - 12, /* PRIMARY_SID */ - 0, /* Special case (see clause A.2.1.3) */ - 0, /* AMRWB_IO_6600 */ - 1, /* PRIMARY_7200 */ - 2, /* PRIMARY_8000 */ - 1, /* AMRWB_IO_8850 */ - 3, /* PRIMARY_9600 */ - 2, /* AMRWB_IO_1265 */ - 4, /* PRIMARY_13200 */ - 3, /* AMRWB_IO_1425 */ - 4, /* AMRWB_IO_1585 */ - 5, /* PRIMARY_16400 */ - 5, /* AMRWB_IO_1825 */ - 6, /* AMRWB_IO_1985 */ - 7, /* AMRWB_IO_2305 */ - 8, /* AMRWB_IO_2385 */ - 6, /* PRIMARY_24400 */ - 7, /* PRIMARY_32000 */ - 8, /* PRIMARY_48000 */ - 9, /* PRIMARY_64000 */ - 10, /* PRIMARY_96000 */ - 11 /* PRIMARY_128000 */ - }; - - bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); + /* clang-format off */ + +static const int32_t AMRWB_IOmode2rate[16] = { + 6600, /* AMRWB_IO_6600 */ + 8850, /* AMRWB_IO_8850 */ + 12650, /* AMRWB_IO_1265 */ + 14250, /* AMRWB_IO_1425 */ + 15850, /* AMRWB_IO_1585 */ + 18250, /* AMRWB_IO_1825 */ + 19850, /* AMRWB_IO_1985 */ + 23050, /* AMRWB_IO_2305 */ + 23850, /* AMRWB_IO_2385 */ + 1750, /* AMRWB_IO_SID: SID_1k75 followed by STI bit and CMI bits (A.2.2.1.3) */ + -1, /* AMRWB_IO_FUT1 */ + -1, /* AMRWB_IO_FUT2 */ + -1, /* AMRWB_IO_FUT3 */ + -1, /* AMRWB_IO_FUT4 */ + 0, /* SPEECH_LOST */ + 0 /* NO_DATA */ +}; + +static const int32_t PRIMARYmode2rate[16] = { + 2800, /* PRIMARY_2800 */ + 7200, /* PRIMARY_7200 */ + 8000, /* PRIMARY_8000 */ + 9600, /* PRIMARY_9600 */ + 13200, /* PRIMARY_13200 */ + 16400, /* PRIMARY_16400 */ + 24400, /* PRIMARY_24400 */ + 32000, /* PRIMARY_32000 */ + 48000, /* PRIMARY_48000 */ + 64000, /* PRIMARY_64000 */ + 96000, /* PRIMARY_96000 */ + 128000, /* PRIMARY_128000 */ + 2400, /* PRIMARY_SID */ + -1, /* PRIMARY_FUT1 */ + 0, /* SPEECH_LOST */ + 0 /* NO_DATA */ +}; + +static const uint16_t evsPayloadProtectedSizes[22] = { + 48, + 56, + 136, + 144, + 160, + 184, + 192, + 256, + 264, + 288, + 320, + 328, + 368, + 400, + 464, + 480, + 488, + 640, + 960, + 1280, + 1920, + 2560 +}; + +static const bool evsPayloadProtectedSizes_isAMRWB_IOmode[22] = { + 0, + 0, /* Special case (see clause A.2.1.3) */ + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +static const uint16_t evsPayloadProtectedSizes_frameTypeIndex[22] = { + 12, /* PRIMARY_SID */ + 0, /* Special case (see clause A.2.1.3) */ + 0, /* AMRWB_IO_6600 */ + 1, /* PRIMARY_7200 */ + 2, /* PRIMARY_8000 */ + 1, /* AMRWB_IO_8850 */ + 3, /* PRIMARY_9600 */ + 2, /* AMRWB_IO_1265 */ + 4, /* PRIMARY_13200 */ + 3, /* AMRWB_IO_1425 */ + 4, /* AMRWB_IO_1585 */ + 5, /* PRIMARY_16400 */ + 5, /* AMRWB_IO_1825 */ + 6, /* AMRWB_IO_1985 */ + 7, /* AMRWB_IO_2305 */ + 8, /* AMRWB_IO_2385 */ + 6, /* PRIMARY_24400 */ + 7, /* PRIMARY_32000 */ + 8, /* PRIMARY_48000 */ + 9, /* PRIMARY_64000 */ + 10, /* PRIMARY_96000 */ + 11 /* PRIMARY_128000 */ +}; + +bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); #ifdef IVAS_RTPDUMP - void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); +void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #else - static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); +static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #endif - bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); - - typedef struct - { - RTPDUMP_HANDLE rtpdump; - bool hf_only; - RTPDUMP_RTPPACKET rtpPacket; - uint32_t timeoffset_ms; - uint16_t frameIndex; - bool frameFollowing; - } EVS_RTPDUMP_DEPACKER; - - typedef enum - { - EVS_RTPDUMP_DEPACKER_NO_ERROR = 0, - EVS_RTPDUMP_DEPACKER_EOF = -1, - EVS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, - EVS_RTPDUMP_DEPACKER_PAYLOAD_ERROR - } EVS_RTPDUMP_DEPACKER_ERROR; - - EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_open( EVS_RTPDUMP_DEPACKER *self, FILE *file, bool hf_only ); - - EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_readNextFrame( - EVS_RTPDUMP_DEPACKER *self, - uint16_t *rtpSequenceNumber, - uint32_t *rtpTimeStamp, - uint32_t *rcvTime_ms, - bool *isAMRWB_IOmode, - uint16_t *frameTypeIndex, - bool *qBit, - unsigned char **frame, - uint16_t *frameSizeBits ); - - void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); +bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); + +typedef struct +{ + RTPDUMP_HANDLE rtpdump; + bool hf_only; + RTPDUMP_RTPPACKET rtpPacket; + uint32_t timeoffset_ms; + uint16_t frameIndex; + bool frameFollowing; +} EVS_RTPDUMP_DEPACKER; + +typedef enum +{ + EVS_RTPDUMP_DEPACKER_NO_ERROR = 0, + EVS_RTPDUMP_DEPACKER_EOF = -1, + EVS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, + EVS_RTPDUMP_DEPACKER_PAYLOAD_ERROR +} EVS_RTPDUMP_DEPACKER_ERROR; + +EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_open( EVS_RTPDUMP_DEPACKER *self, FILE *file, bool hf_only ); + +EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_readNextFrame( + EVS_RTPDUMP_DEPACKER *self, + uint16_t *rtpSequenceNumber, + uint32_t *rtpTimeStamp, + uint32_t *rcvTime_ms, + bool *isAMRWB_IOmode, + uint16_t *frameTypeIndex, + bool *qBit, + unsigned char **frame, + uint16_t *frameSizeBits ); + +void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); + + /* clang-format on */ #ifdef __cplusplus } diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 2366c65698..132e4d23e2 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -48,8 +48,8 @@ *-----------------------------------------------------------------------*/ static void writeByteToRtp( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - char value /* i : Value to be written to the RTP packet */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + char value /* i : Value to be written to the RTP packet */ ) { self->rtpPacket.data[self->writingIndex] = value; @@ -65,8 +65,8 @@ static void writeByteToRtp( *-----------------------------------------------------------------------*/ static void determineBitrate( - const int32_t numBits, /* i : Number of bits contained in the data frame */ - int32_t *frameBitrate /* o : Bitrate of the data frame */ + const int32_t numBits, /* i : Number of bits contained in the data frame */ + int32_t *frameBitrate /* o : Bitrate of the data frame */ ) { *frameBitrate = numBits * IVAS_NUM_FRAMES_PER_SEC; @@ -80,20 +80,20 @@ static void determineBitrate( *-----------------------------------------------------------------------*/ static void ivasPayload_parseToc( - uint8_t toc, /* i : ToC byte to be parsed */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - int32_t *bitrate /* o : Bitrate of the data frame indicated in the ToC byte */ + uint8_t toc, /* i : ToC byte to be parsed */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + int32_t *bitrate /* o : Bitrate of the data frame indicated in the ToC byte */ ) { bool isIvasToc = ( toc & 0x30 ) == 0x10; // xx01 xxxx if ( !isIvasToc ) { - evsHeaderFullPayload_parseToc(toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, bitrate ); + evsHeaderFullPayload_parseToc( toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, bitrate ); } else { @@ -113,11 +113,11 @@ static void ivasPayload_parseToc( *-----------------------------------------------------------------------*/ static void ivasPayload_parsePItype( - char *PIframeDataBeginning, /* i : Pointer to the beginning of the PI data section in the RTP packet */ - uint16_t PIframeSizeBytes, /* i : PI data frame size in bytes */ - uint16_t parsedPIframeSizeBytes, /* i : Size of previously parsed PI frame data section in bytes */ - uint16_t PIframeType, /* i : Type of the PI data frame */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ + char *PIframeDataBeginning, /* i : Pointer to the beginning of the PI data section in the RTP packet */ + uint16_t PIframeSizeBytes, /* i : PI data frame size in bytes */ + uint16_t parsedPIframeSizeBytes, /* i : Size of previously parsed PI frame data section in bytes */ + uint16_t PIframeType, /* i : Type of the PI data frame */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ ) { if ( PIframeType == PI_SCENE_ORIENTATION ) @@ -173,8 +173,8 @@ static void ivasPayload_parsePItype( *-----------------------------------------------------------------------*/ static void ivasPayload_PIaddHeaderBytes( - uint16_t PIheaderSizeBytes, /* i : Size of the PI header section in bytes */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ + uint16_t PIheaderSizeBytes, /* i : Size of the PI header section in bytes */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ ) { if ( PIdataCurrentFrame->sceneOrientation != NULL ) @@ -211,10 +211,10 @@ static void ivasPayload_PIaddHeaderBytes( *-----------------------------------------------------------------------*/ static bool ivasPayload_parsePIdata( - char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ - uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ - uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ + char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ + uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ + uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ ) { // TODO(pajunen): can this accidentally include zero padding bits? @@ -261,14 +261,14 @@ static bool ivasPayload_parsePIdata( /* general PI data for all frames */ if ( PIframeMarker == PM_GENERAL ) { - ivasPayload_parsePItype(PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); } else { /* current frame */ if ( ( PIframeMarker > 0 ) & ( count_audio == frameIndex ) ) { - ivasPayload_parsePItype(PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); } /* increase audio frame counter */ @@ -294,7 +294,7 @@ static bool ivasPayload_parsePIdata( static float ivasPayload_convertFromQ15( uint16_t Q15Value ) { float temp; - temp = (float)( Q15Value & 0x7FFF ) / 0x7FFF; + temp = (float) ( Q15Value & 0x7FFF ) / 0x7FFF; return ( Q15Value & 0x8000 ) ? -temp : temp; } @@ -308,7 +308,7 @@ static float ivasPayload_convertFromQ15( uint16_t Q15Value ) static uint16_t ivasPayload_convertToQ15( float value ) { uint16_t temp; - temp = (uint16_t)( fabsf( value ) * 0x7FFF ); + temp = (uint16_t) ( fabsf( value ) * 0x7FFF ); if ( value >= 0.0f ) { temp = temp & 0x7FFF; @@ -328,8 +328,8 @@ static uint16_t ivasPayload_convertToQ15( float value ) *-----------------------------------------------------------------------*/ static void ivasPayload_convertOrientationToQ15( - IVAS_QUATERNION *orientation, /* i : Input orientation in quaternion to be converted to Q15 */ - IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ + IVAS_QUATERNION *orientation, /* i : Input orientation in quaternion to be converted to Q15 */ + IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ ) { orientationQ15->w = ivasPayload_convertToQ15( orientation->w ); @@ -346,12 +346,12 @@ static void ivasPayload_convertOrientationToQ15( *-----------------------------------------------------------------------*/ static float ivasPayload_parseQuaternionComponent( - char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ + char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ ) { uint16_t tempQ15value; - tempQ15value = (uint16_t) **PIorientationData; - tempQ15value = ( tempQ15value << 8 ) | (uint16_t) *( *PIorientationData + 1 ); + tempQ15value = ( uint16_t ) * *PIorientationData; + tempQ15value = ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ); *PIorientationData += 2; return ivasPayload_convertFromQ15( tempQ15value ); } @@ -384,14 +384,14 @@ void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) *-----------------------------------------------------------------------*/ bool ivasPayload_parseOrientationPIdata( - char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ - IVAS_QUATERNION *q /* o : Parsed quaternion value */ + char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ + IVAS_QUATERNION *q /* o : Parsed quaternion value */ ) { - q->w = ivasPayload_parseQuaternionComponent(&PIorientationData); - q->x = ivasPayload_parseQuaternionComponent(&PIorientationData); - q->y = ivasPayload_parseQuaternionComponent(&PIorientationData); - q->z = ivasPayload_parseQuaternionComponent(&PIorientationData); + q->w = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->x = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->y = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->z = ivasPayload_parseQuaternionComponent( &PIorientationData ); if ( q->w > 1.0f || q->w < -1.0f || q->x > 1.0f || q->x < -1.0f || @@ -405,18 +405,18 @@ bool ivasPayload_parseOrientationPIdata( } bool ivasPayload_unpackFrame( - char *payload, /* i : RTP payload data */ - uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ - uint16_t frameIndex, /* i : Index for the data frame in the payload */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ + char *payload, /* i : RTP payload data */ + uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ + uint16_t frameIndex, /* i : Index for the data frame in the payload */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeInBits, /* o : Data frame size in bits */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ ) { bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit; @@ -436,12 +436,12 @@ bool ivasPayload_unpackFrame( if ( *payload & E_BYTE ) // 1xxx xxxx { PIdataCurrentFrame->PIdataPresent = false; - while (1) + while ( 1 ) { if ( ( *payload & 0xF0 ) == IVAS_E_CMR ) // 1111 xxxx { /* IVAS CMR/E-byte */ - while (1) + while ( 1 ) { /* parse subsequent E-bytes */ ++payload; @@ -547,7 +547,7 @@ bool ivasPayload_unpackFrame( fprintf( stderr, "Error: expected ToC, found CMR/E-byte\n" ); return false; } - ivasPayload_parseToc(*payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &bitrate ); + ivasPayload_parseToc( *payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &bitrate ); if ( bitrate < 0 ) { fprintf( stderr, "Error: unexpected frameTypeIndex in ToC\n" ); @@ -599,8 +599,8 @@ bool ivasPayload_unpackFrame( } IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( - IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ - FILE *file /* i : Input file containing the rtpdump */ + IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ + FILE *file /* i : Input file containing the rtpdump */ ) { RTPDUMP_ERROR rtpdumpError; @@ -617,17 +617,17 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( } IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( - IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ - uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ - uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ - uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeBits /* o : Data frame size in bits */ + IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ + uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ + uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ + uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeBits /* o : Data frame size in bits */ ) { /* read next RTP packet from rtpdump */ @@ -662,7 +662,7 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( } void IVAS_RTPDUMP_DEPACKER_close( - IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ + IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ ) { if ( !self ) @@ -673,8 +673,8 @@ void IVAS_RTPDUMP_DEPACKER_close( } IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( - IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ - FILE *file /* i : Output file for the rtpdump stream */ + IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ + FILE *file /* i : Output file for the rtpdump stream */ ) { IVAS_RTPDUMP_PACKER *self; @@ -698,12 +698,12 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( } void IVAS_RTPDUMP_PACKER_writeEbytes( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). - * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). + * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ ) { UWord8 initialEbyte, subsequentEbyte; @@ -852,11 +852,11 @@ void IVAS_RTPDUMP_PACKER_writeEbytes( } void IVAS_RTPDUMP_PACKER_writeToc( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t frameBitrate, /* i : Bitrate of the written frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). - * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ - bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t frameBitrate, /* i : Bitrate of the written frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). + * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ + bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ ) { UWord8 H_bit, F_bit, EVS_IVAS_mode_bit, Q_bit, toc; @@ -868,13 +868,13 @@ void IVAS_RTPDUMP_PACKER_writeToc( /* FRAME_NO_DATA frames are written as in TS26.445 */ if ( isImmersiveFormat && frameBitrate != FRAME_NO_DATA ) { - toc = (UWord8)( rate2IVASmode( frameBitrate ) ); + toc = (UWord8) ( rate2IVASmode( frameBitrate ) ); EVS_IVAS_mode_bit = 0; Q_bit = 1; } else { - toc = (UWord8)( rate2EVSmode( frameBitrate, &is_amr_wb ) ); + toc = (UWord8) ( rate2EVSmode( frameBitrate, &is_amr_wb ) ); EVS_IVAS_mode_bit = (UWord8) is_amr_wb; Q_bit = 0; } @@ -883,14 +883,14 @@ void IVAS_RTPDUMP_PACKER_writeToc( } IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ - int32_t numBits, /* i : Number of bits contained in the data frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ + int32_t numBits, /* i : Number of bits contained in the data frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ ) { @@ -905,13 +905,15 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( determineBitrate( numBits, &frameBitrate ); /* payload header */ - IVAS_RTPDUMP_PACKER_writeEbytes(self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); + IVAS_RTPDUMP_PACKER_writeEbytes( self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); IVAS_RTPDUMP_PACKER_writeToc( self, frameBitrate, isImmersiveFormat, false ); /* frame data */ - for (i = 0; i < numBits; i += 8) { + for ( i = 0; i < numBits; i += 8 ) + { tempData = 0; - for (k = 0; k < 8; ++k) { + for ( k = 0; k < 8; ++k ) + { tempData = ( tempData << 1 ) | (char) bitStream[i + k]; } writeByteToRtp( self, tempData ); @@ -936,7 +938,7 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( } void IVAS_RTPDUMP_PACKER_writePIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { if ( !self->piDataPresent ) @@ -1036,7 +1038,7 @@ void IVAS_RTPDUMP_PACKER_writePIdata( } void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { if ( self->piDataPacker.disableHeadtracking == self->piDataPackerState.previousDisableHeadtrackingState ) @@ -1058,7 +1060,7 @@ void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( } void IVAS_RTPDUMP_PACKER_determinePIpresence( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { self->piDataPresent = false; @@ -1072,7 +1074,7 @@ void IVAS_RTPDUMP_PACKER_determinePIpresence( } void IVAS_RTPDUMP_PACKER_resetPIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { self->piDataPresent = false; @@ -1083,7 +1085,7 @@ void IVAS_RTPDUMP_PACKER_resetPIdata( } void IVAS_RTPDUMP_PACKER_close( - IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ ) { if ( !self || !( *self ) ) diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 159e18a384..101a7805dd 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -48,6 +48,7 @@ extern "C" { #endif + /* clang-format off */ static const int32_t IVASmode2rate[16] = { 13200, @@ -64,8 +65,8 @@ static const int32_t IVASmode2rate[16] = { 256000, 384000, 512000, - -1, /* Reserved */ - 5200 /* SID */ + -1, /* Reserved */ + 5200 /* SID */ }; typedef enum @@ -229,188 +230,191 @@ typedef enum /*-----------------------------------------------------------------------* - * ivasPayload_unpackFrame() - * - * Parse a single IVAS frame from the payload. Also read the IVAS payload header - * and parse the PI data associated with the IVAS frame. - *-----------------------------------------------------------------------*/ + * ivasPayload_unpackFrame() + * + * Parse a single IVAS frame from the payload. Also read the IVAS payload header + * and parse the PI data associated with the IVAS frame. + *-----------------------------------------------------------------------*/ bool ivasPayload_unpackFrame( - char *payload, /* i : RTP payload data */ - uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ - uint16_t frameIndex, /* i : Index for the data frame in the payload */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ + char *payload, /* i : RTP payload data */ + uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ + uint16_t frameIndex, /* i : Index for the data frame in the payload */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeInBits, /* o : Data frame size in bits */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_open() - * - * Open IVAS rtpdump depacker. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_DEPACKER_open() + * + * Open IVAS rtpdump depacker. + *-----------------------------------------------------------------------*/ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( - IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ - FILE *file /* i : Input file containing the rtpdump */ + IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ + FILE *file /* i : Input file containing the rtpdump */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_readNextFrame() - * - * Unpack next frame from the RTP packet. If the packet is depleted, read the next RTP packet from the rtpdump. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_DEPACKER_readNextFrame() + * + * Unpack next frame from the RTP packet. If the packet is depleted, read the next RTP packet from the rtpdump. + *-----------------------------------------------------------------------*/ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( - IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ - uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ - uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ - uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeBits /* o : Data frame size in bits */ + IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ + uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ + uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ + uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeBits /* o : Data frame size in bits */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_close() - * - * Close IVAS rtpdump depacker. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_DEPACKER_close() + * + * Close IVAS rtpdump depacker. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_DEPACKER_close( - IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ + IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_open() - * - * Open IVAS rtpdump packer. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_open() + * + * Open IVAS rtpdump packer. + *-----------------------------------------------------------------------*/ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( - IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ - FILE *file /* i : Output file for the rtpdump stream */ + IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ + FILE *file /* i : Output file for the rtpdump stream */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeEbytes() - * - * Write E-bytes (CMR and subsequent E-bytes) to the RTP packet. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_writeEbytes() + * + * Write E-bytes (CMR and subsequent E-bytes) to the RTP packet. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_writeEbytes( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). - * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). + * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeToc() - * - * Write a single ToC byte to the RTP packet. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_writeToc() + * + * Write a single ToC byte to the RTP packet. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_writeToc( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t frameBitrate, /* i : Bitrate of the written frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). - * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ - bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + int32_t frameBitrate, /* i : Bitrate of the written frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). + * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ + bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeNextFrame() - * - * Write IVAS payload header, a single frame data and optional PI data to an RTP packet. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_writeNextFrame() + * + * Write IVAS payload header, a single frame data and optional PI data to an RTP packet. + *-----------------------------------------------------------------------*/ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ - int32_t numBits, /* i : Number of bits contained in the data frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ + IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ + const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ + int32_t numBits, /* i : Number of bits contained in the data frame */ + bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ + int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ + bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ + IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ + IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writePIdata() - * - * Write PI data section (PI headers and PI frame data) to the RTP packet. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_writePIdata() + * + * Write PI data section (PI headers and PI frame data) to the RTP packet. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_writePIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState() - * - * Check for disable headtracking input and previously set disable/enable headtracking state. - * Set the current PI headtracking state accordingly. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState() + * + * Check for disable headtracking input and previously set disable/enable headtracking state. + * Set the current PI headtracking state accordingly. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_determinePIpresence() - * - * Check if there is PI data present for the current frame. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_determinePIpresence() + * + * Check if there is PI data present for the current frame. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_determinePIpresence( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_resetPIdata() - * - * Reset PI data packer handle. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_resetPIdata() + * + * Reset PI data packer handle. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_resetPIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ); /*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_close() - * - * Close IVAS rtpdump packer. - *-----------------------------------------------------------------------*/ + * IVAS_RTPDUMP_PACKER_close() + * + * Close IVAS rtpdump packer. + *-----------------------------------------------------------------------*/ void IVAS_RTPDUMP_PACKER_close( - IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ + IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ ); + + /* clang-format on */ + #ifdef __cplusplus } #endif -- GitLab From 5b2068a77deceb65003ac7c03ea62c75bb25450e Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 23 Apr 2025 14:59:26 +0300 Subject: [PATCH 011/340] Revert clang format changes to evs_rtp_payload.h --- lib_util/evs_rtp_payload.h | 304 ++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 154 deletions(-) diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 490f9560ff..74c4a56a56 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -45,165 +45,161 @@ extern "C" { #endif - /* clang-format off */ - -static const int32_t AMRWB_IOmode2rate[16] = { - 6600, /* AMRWB_IO_6600 */ - 8850, /* AMRWB_IO_8850 */ - 12650, /* AMRWB_IO_1265 */ - 14250, /* AMRWB_IO_1425 */ - 15850, /* AMRWB_IO_1585 */ - 18250, /* AMRWB_IO_1825 */ - 19850, /* AMRWB_IO_1985 */ - 23050, /* AMRWB_IO_2305 */ - 23850, /* AMRWB_IO_2385 */ - 1750, /* AMRWB_IO_SID: SID_1k75 followed by STI bit and CMI bits (A.2.2.1.3) */ - -1, /* AMRWB_IO_FUT1 */ - -1, /* AMRWB_IO_FUT2 */ - -1, /* AMRWB_IO_FUT3 */ - -1, /* AMRWB_IO_FUT4 */ - 0, /* SPEECH_LOST */ - 0 /* NO_DATA */ -}; - -static const int32_t PRIMARYmode2rate[16] = { - 2800, /* PRIMARY_2800 */ - 7200, /* PRIMARY_7200 */ - 8000, /* PRIMARY_8000 */ - 9600, /* PRIMARY_9600 */ - 13200, /* PRIMARY_13200 */ - 16400, /* PRIMARY_16400 */ - 24400, /* PRIMARY_24400 */ - 32000, /* PRIMARY_32000 */ - 48000, /* PRIMARY_48000 */ - 64000, /* PRIMARY_64000 */ - 96000, /* PRIMARY_96000 */ - 128000, /* PRIMARY_128000 */ - 2400, /* PRIMARY_SID */ - -1, /* PRIMARY_FUT1 */ - 0, /* SPEECH_LOST */ - 0 /* NO_DATA */ -}; - -static const uint16_t evsPayloadProtectedSizes[22] = { - 48, - 56, - 136, - 144, - 160, - 184, - 192, - 256, - 264, - 288, - 320, - 328, - 368, - 400, - 464, - 480, - 488, - 640, - 960, - 1280, - 1920, - 2560 -}; - -static const bool evsPayloadProtectedSizes_isAMRWB_IOmode[22] = { - 0, - 0, /* Special case (see clause A.2.1.3) */ - 1, - 0, - 0, - 1, - 0, - 1, - 0, - 1, - 1, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - 0 -}; - -static const uint16_t evsPayloadProtectedSizes_frameTypeIndex[22] = { - 12, /* PRIMARY_SID */ - 0, /* Special case (see clause A.2.1.3) */ - 0, /* AMRWB_IO_6600 */ - 1, /* PRIMARY_7200 */ - 2, /* PRIMARY_8000 */ - 1, /* AMRWB_IO_8850 */ - 3, /* PRIMARY_9600 */ - 2, /* AMRWB_IO_1265 */ - 4, /* PRIMARY_13200 */ - 3, /* AMRWB_IO_1425 */ - 4, /* AMRWB_IO_1585 */ - 5, /* PRIMARY_16400 */ - 5, /* AMRWB_IO_1825 */ - 6, /* AMRWB_IO_1985 */ - 7, /* AMRWB_IO_2305 */ - 8, /* AMRWB_IO_2385 */ - 6, /* PRIMARY_24400 */ - 7, /* PRIMARY_32000 */ - 8, /* PRIMARY_48000 */ - 9, /* PRIMARY_64000 */ - 10, /* PRIMARY_96000 */ - 11 /* PRIMARY_128000 */ -}; - -bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); + static const int32_t AMRWB_IOmode2rate[16] = { + 6600, /* AMRWB_IO_6600 */ + 8850, /* AMRWB_IO_8850 */ + 12650, /* AMRWB_IO_1265 */ + 14250, /* AMRWB_IO_1425 */ + 15850, /* AMRWB_IO_1585 */ + 18250, /* AMRWB_IO_1825 */ + 19850, /* AMRWB_IO_1985 */ + 23050, /* AMRWB_IO_2305 */ + 23850, /* AMRWB_IO_2385 */ + 1750, /* AMRWB_IO_SID: SID_1k75 followed by STI bit and CMI bits (A.2.2.1.3) */ + -1, /* AMRWB_IO_FUT1 */ + -1, /* AMRWB_IO_FUT2 */ + -1, /* AMRWB_IO_FUT3 */ + -1, /* AMRWB_IO_FUT4 */ + 0, /* SPEECH_LOST */ + 0 /* NO_DATA */ + }; + + static const int32_t PRIMARYmode2rate[16] = { + 2800, /* PRIMARY_2800 */ + 7200, /* PRIMARY_7200 */ + 8000, /* PRIMARY_8000 */ + 9600, /* PRIMARY_9600 */ + 13200, /* PRIMARY_13200 */ + 16400, /* PRIMARY_16400 */ + 24400, /* PRIMARY_24400 */ + 32000, /* PRIMARY_32000 */ + 48000, /* PRIMARY_48000 */ + 64000, /* PRIMARY_64000 */ + 96000, /* PRIMARY_96000 */ + 128000, /* PRIMARY_128000 */ + 2400, /* PRIMARY_SID */ + -1, /* PRIMARY_FUT1 */ + 0, /* SPEECH_LOST */ + 0 /* NO_DATA */ + }; + + static const uint16_t evsPayloadProtectedSizes[22] = { + 48, + 56, + 136, + 144, + 160, + 184, + 192, + 256, + 264, + 288, + 320, + 328, + 368, + 400, + 464, + 480, + 488, + 640, + 960, + 1280, + 1920, + 2560 + }; + + static const bool evsPayloadProtectedSizes_isAMRWB_IOmode[22] = { + 0, + 0, /* Special case (see clause A.2.1.3) */ + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0 + }; + + static const uint16_t evsPayloadProtectedSizes_frameTypeIndex[22] = { + 12, /* PRIMARY_SID */ + 0, /* Special case (see clause A.2.1.3) */ + 0, /* AMRWB_IO_6600 */ + 1, /* PRIMARY_7200 */ + 2, /* PRIMARY_8000 */ + 1, /* AMRWB_IO_8850 */ + 3, /* PRIMARY_9600 */ + 2, /* AMRWB_IO_1265 */ + 4, /* PRIMARY_13200 */ + 3, /* AMRWB_IO_1425 */ + 4, /* AMRWB_IO_1585 */ + 5, /* PRIMARY_16400 */ + 5, /* AMRWB_IO_1825 */ + 6, /* AMRWB_IO_1985 */ + 7, /* AMRWB_IO_2305 */ + 8, /* AMRWB_IO_2385 */ + 6, /* PRIMARY_24400 */ + 7, /* PRIMARY_32000 */ + 8, /* PRIMARY_48000 */ + 9, /* PRIMARY_64000 */ + 10, /* PRIMARY_96000 */ + 11 /* PRIMARY_128000 */ + }; + + bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); #ifdef IVAS_RTPDUMP -void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); + void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #else -static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); + static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #endif -bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); - -typedef struct -{ - RTPDUMP_HANDLE rtpdump; - bool hf_only; - RTPDUMP_RTPPACKET rtpPacket; - uint32_t timeoffset_ms; - uint16_t frameIndex; - bool frameFollowing; -} EVS_RTPDUMP_DEPACKER; - -typedef enum -{ - EVS_RTPDUMP_DEPACKER_NO_ERROR = 0, - EVS_RTPDUMP_DEPACKER_EOF = -1, - EVS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, - EVS_RTPDUMP_DEPACKER_PAYLOAD_ERROR -} EVS_RTPDUMP_DEPACKER_ERROR; - -EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_open( EVS_RTPDUMP_DEPACKER *self, FILE *file, bool hf_only ); - -EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_readNextFrame( - EVS_RTPDUMP_DEPACKER *self, - uint16_t *rtpSequenceNumber, - uint32_t *rtpTimeStamp, - uint32_t *rcvTime_ms, - bool *isAMRWB_IOmode, - uint16_t *frameTypeIndex, - bool *qBit, - unsigned char **frame, - uint16_t *frameSizeBits ); - -void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); - - /* clang-format on */ + bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); + + typedef struct + { + RTPDUMP_HANDLE rtpdump; + bool hf_only; + RTPDUMP_RTPPACKET rtpPacket; + uint32_t timeoffset_ms; + uint16_t frameIndex; + bool frameFollowing; + } EVS_RTPDUMP_DEPACKER; + + typedef enum + { + EVS_RTPDUMP_DEPACKER_NO_ERROR = 0, + EVS_RTPDUMP_DEPACKER_EOF = -1, + EVS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, + EVS_RTPDUMP_DEPACKER_PAYLOAD_ERROR + } EVS_RTPDUMP_DEPACKER_ERROR; + + EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_open( EVS_RTPDUMP_DEPACKER *self, FILE *file, bool hf_only ); + + EVS_RTPDUMP_DEPACKER_ERROR EVS_RTPDUMP_DEPACKER_readNextFrame( + EVS_RTPDUMP_DEPACKER *self, + uint16_t *rtpSequenceNumber, + uint32_t *rtpTimeStamp, + uint32_t *rcvTime_ms, + bool *isAMRWB_IOmode, + uint16_t *frameTypeIndex, + bool *qBit, + unsigned char **frame, + uint16_t *frameSizeBits ); + + void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); #ifdef __cplusplus } -- GitLab From fe17179440d9dc362143313a1cf746fff54d036b Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 23 Apr 2025 15:00:51 +0300 Subject: [PATCH 012/340] Clang format --- lib_util/evs_rtp_payload.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 74c4a56a56..21bb10f3f3 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -164,7 +164,7 @@ extern "C" #ifdef IVAS_RTPDUMP void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #else - static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); +static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #endif bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); -- GitLab From f40f758fd02d391ea57d205b86b53262b4f3b4fd Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 24 Apr 2025 11:00:31 +0300 Subject: [PATCH 013/340] Change PI data pointers to variables --- apps/encoder.c | 10 ++++---- lib_util/ivas_rtp_payload.c | 47 ++++++++++++++++++++++++------------- lib_util/ivas_rtp_payload.h | 6 +++-- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/apps/encoder.c b/apps/encoder.c index 3d1e160f7d..13dd477363 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -859,25 +859,23 @@ int main( /* scene orientation */ if ( sceneOrientationFileReader ) { - IVAS_QUATERNION sceneOrientationQuat; - if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &sceneOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &rtpdumpPacker->piDataPacker.sceneOrientationQuat, NULL ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while reading scene orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( sceneOrientationFileReader ) ); goto cleanup; } - rtpdumpPacker->piDataPacker.sceneOrientationQuat = &sceneOrientationQuat; + rtpdumpPacker->piDataPacker.sceneOrientationPresent = true; } /* device orientation */ if ( deviceOrientationFileReader ) { - IVAS_QUATERNION deviceOrientationQuat; - if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &deviceOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &rtpdumpPacker->piDataPacker.deviceOrientationQuat, NULL ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while reading device orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( deviceOrientationFileReader ) ); goto cleanup; } - rtpdumpPacker->piDataPacker.deviceOrientationQuat = &deviceOrientationQuat; + rtpdumpPacker->piDataPacker.deviceOrientationPresent = true; } /* disable headtracking */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 132e4d23e2..6bac9a066d 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -328,14 +328,14 @@ static uint16_t ivasPayload_convertToQ15( float value ) *-----------------------------------------------------------------------*/ static void ivasPayload_convertOrientationToQ15( - IVAS_QUATERNION *orientation, /* i : Input orientation in quaternion to be converted to Q15 */ + IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ ) { - orientationQ15->w = ivasPayload_convertToQ15( orientation->w ); - orientationQ15->x = ivasPayload_convertToQ15( orientation->x ); - orientationQ15->y = ivasPayload_convertToQ15( orientation->y ); - orientationQ15->z = ivasPayload_convertToQ15( orientation->z ); + orientationQ15->w = ivasPayload_convertToQ15( orientation.w ); + orientationQ15->x = ivasPayload_convertToQ15( orientation.x ); + orientationQ15->y = ivasPayload_convertToQ15( orientation.y ); + orientationQ15->z = ivasPayload_convertToQ15( orientation.z ); } @@ -679,6 +679,11 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( { IVAS_RTPDUMP_PACKER *self; RTPDUMP_ERROR rtpdumpError; + IVAS_QUATERNION identity; + identity.w = 1.0f; + identity.x = 0.0f; + identity.y = 0.0f; + identity.z = 0.0f; self = calloc( sizeof( IVAS_RTPDUMP_PACKER ), 1 ); rtpdumpError = RTPDUMP_OpenWithFileToWrite( &self->rtpdump, file ); if ( rtpdumpError != RTPDUMP_NO_ERROR ) @@ -687,8 +692,10 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( } RTPDUMP_SetDefaultRtpPacketHeader( &self->rtpPacket ); memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); - self->piDataPacker.sceneOrientationQuat = NULL; - self->piDataPacker.deviceOrientationQuat = NULL; + self->piDataPacker.sceneOrientationQuat = identity; + self->piDataPacker.deviceOrientationQuat = identity; + self->piDataPacker.sceneOrientationPresent = false; + self->piDataPacker.deviceOrientationPresent = false; self->piDataPacker.enableHeadtracking = false; self->piDataPacker.disableHeadtracking = false; self->piDataPackerState.previousDisableHeadtrackingState = false; @@ -953,9 +960,9 @@ void IVAS_RTPDUMP_PACKER_writePIdata( /* PI header section */ /* scene orientation */ - if ( self->piDataPacker.sceneOrientationQuat ) + if ( self->piDataPacker.sceneOrientationPresent ) { - if ( self->piDataPacker.deviceOrientationQuat || self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) + if ( self->piDataPacker.deviceOrientationPresent || self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) { somePIfollowing = true; } @@ -972,7 +979,7 @@ void IVAS_RTPDUMP_PACKER_writePIdata( } /* device orientation */ - if ( self->piDataPacker.deviceOrientationQuat ) + if ( self->piDataPacker.deviceOrientationPresent ) { if ( self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) { @@ -1010,7 +1017,7 @@ void IVAS_RTPDUMP_PACKER_writePIdata( /* PI frame data section */ - if ( self->piDataPacker.sceneOrientationQuat ) + if ( self->piDataPacker.sceneOrientationPresent ) { ivasPayload_convertOrientationToQ15( self->piDataPacker.sceneOrientationQuat, &sceneOrientationQ15 ); writeByteToRtp( self, (char) ( sceneOrientationQ15.w >> 8 ) ); @@ -1023,7 +1030,7 @@ void IVAS_RTPDUMP_PACKER_writePIdata( writeByteToRtp( self, (char) ( sceneOrientationQ15.z & 0x00FF ) ); } - if ( self->piDataPacker.deviceOrientationQuat ) + if ( self->piDataPacker.deviceOrientationPresent ) { ivasPayload_convertOrientationToQ15( self->piDataPacker.deviceOrientationQuat, &deviceOrientationQ15 ); writeByteToRtp( self, (char) ( deviceOrientationQ15.w >> 8 ) ); @@ -1064,8 +1071,8 @@ void IVAS_RTPDUMP_PACKER_determinePIpresence( ) { self->piDataPresent = false; - if ( self->piDataPacker.sceneOrientationQuat || - self->piDataPacker.deviceOrientationQuat || + if ( self->piDataPacker.sceneOrientationPresent || + self->piDataPacker.deviceOrientationPresent || self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) { @@ -1077,9 +1084,17 @@ void IVAS_RTPDUMP_PACKER_resetPIdata( IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { + IVAS_QUATERNION identity; + identity.w = 1.0f; + identity.x = 0.0f; + identity.y = 0.0f; + identity.z = 0.0f; + self->piDataPresent = false; - self->piDataPacker.sceneOrientationQuat = NULL; - self->piDataPacker.deviceOrientationQuat = NULL; + self->piDataPacker.sceneOrientationQuat = identity; + self->piDataPacker.deviceOrientationQuat = identity; + self->piDataPacker.sceneOrientationPresent = false; + self->piDataPacker.deviceOrientationPresent = false; self->piDataPacker.enableHeadtracking = false; self->piDataPacker.disableHeadtracking = false; } diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 101a7805dd..bbe939e036 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -109,8 +109,10 @@ typedef enum typedef struct { - IVAS_QUATERNION *sceneOrientationQuat; - IVAS_QUATERNION *deviceOrientationQuat; + IVAS_QUATERNION sceneOrientationQuat; + IVAS_QUATERNION deviceOrientationQuat; + bool sceneOrientationPresent; + bool deviceOrientationPresent; bool enableHeadtracking; bool disableHeadtracking; } PI_DATA_PACKER; -- GitLab From cb4ca3744617e8456327b4546429b605c6ff8a89 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 24 Apr 2025 11:41:04 +0300 Subject: [PATCH 014/340] Clang format --- lib_util/ivas_rtp_payload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 6bac9a066d..12c37b4d57 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -328,7 +328,7 @@ static uint16_t ivasPayload_convertToQ15( float value ) *-----------------------------------------------------------------------*/ static void ivasPayload_convertOrientationToQ15( - IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ + IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ ) { -- GitLab From aaf31eb2cc9068714ef39a47918062b7ec176656 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 24 Apr 2025 13:59:43 +0300 Subject: [PATCH 015/340] Fix usan errors --- lib_util/ivas_rtp_payload.c | 2 +- lib_util/rtpdump.c | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 12c37b4d57..dfa2ffd901 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -921,7 +921,7 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( tempData = 0; for ( k = 0; k < 8; ++k ) { - tempData = ( tempData << 1 ) | (char) bitStream[i + k]; + tempData = (char) ( ( tempData << 1 ) | (char) bitStream[i + k] ); } writeByteToRtp( self, tempData ); } diff --git a/lib_util/rtpdump.c b/lib_util/rtpdump.c index a01bc112a3..2ce8580a86 100644 --- a/lib_util/rtpdump.c +++ b/lib_util/rtpdump.c @@ -110,10 +110,17 @@ static int readShort( FILE *file, unsigned short *value ) static int writeLong( FILE *file, unsigned int value ) { char buffer[4] = { 0 }; +#ifdef IVAS_RTPDUMP + buffer[3] = (char) ( value & 0xff ); + buffer[2] = (char) ( ( value >> 8 ) & 0xff ); + buffer[1] = (char) ( ( value >> 16 ) & 0xff ); + buffer[0] = (char) ( ( value >> 24 ) & 0xff ); +#else buffer[3] = value & 0xff; buffer[2] = ( value >> 8 ) & 0xff; buffer[1] = ( value >> 16 ) & 0xff; buffer[0] = ( value >> 24 ) & 0xff; +#endif if ( fwrite( buffer, 4, 1, file ) != 1U ) { return -1; @@ -125,8 +132,13 @@ static int writeLong( FILE *file, unsigned int value ) static int writeShort( FILE *file, unsigned short value ) { char buffer[2] = { 0 }; +#ifdef IVAS_RTPDUMP + buffer[1] = (char) ( value & 0xff ); + buffer[0] = (char) ( ( value >> 8 ) & 0xff ); +#else buffer[1] = value & 0xff; buffer[0] = ( value >> 8 ) & 0xff; +#endif if ( fwrite( buffer, 2, 1, file ) != 1U ) { return -1; -- GitLab From 02626ffa5e7b885ccc55e8844ac8666b750b33d1 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 24 Apr 2025 15:31:02 +0300 Subject: [PATCH 016/340] Fix usan errors --- lib_util/ivas_rtp_payload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index dfa2ffd901..3e9fd71532 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -351,7 +351,7 @@ static float ivasPayload_parseQuaternionComponent( { uint16_t tempQ15value; tempQ15value = ( uint16_t ) * *PIorientationData; - tempQ15value = ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ); + tempQ15value = ( uint16_t ) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); *PIorientationData += 2; return ivasPayload_convertFromQ15( tempQ15value ); } -- GitLab From 1b89fe7eb4bbe1694dabe1d40ed5c03d2863314c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 24 Apr 2025 15:34:26 +0300 Subject: [PATCH 017/340] Clang format --- lib_util/ivas_rtp_payload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 3e9fd71532..22d160cebd 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -351,7 +351,7 @@ static float ivasPayload_parseQuaternionComponent( { uint16_t tempQ15value; tempQ15value = ( uint16_t ) * *PIorientationData; - tempQ15value = ( uint16_t ) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); + tempQ15value = (uint16_t) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); *PIorientationData += 2; return ivasPayload_convertFromQ15( tempQ15value ); } -- GitLab From 5d8d12e3564942f55932b9278a69917d0aa8ef82 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 15 Apr 2025 11:13:25 +0200 Subject: [PATCH 018/340] Port SR + VoIP pytests --- scripts/split_rendering/__init__.py | 0 scripts/split_rendering/isar_bstool.py | 384 ++ .../rotate_euler_quaternion_30s.csv | 6000 ++++++++++++++++ .../rotate_euler_quaternion_30s_delayed.csv | 6020 ++++++++++++++++ ...tate_euler_quaternion_30s_delayed_voip.csv | 6032 +++++++++++++++++ .../rotate_euler_quaternion_30s_voip.csv | 6012 ++++++++++++++++ .../rotate_euler_quaternion_5s.csv | 1000 --- .../rotate_euler_quaternion_5s_delayed.csv | 1020 --- tests/conftest.py | 225 + tests/split_rendering/constants.py | 2 +- tests/split_rendering/test_split_rendering.py | 118 +- tests/test_be_for_jbm_neutral_dly_profile.py | 153 +- 12 files changed, 24924 insertions(+), 2042 deletions(-) create mode 100644 scripts/split_rendering/__init__.py create mode 100755 scripts/split_rendering/isar_bstool.py create mode 100644 scripts/trajectories/rotate_euler_quaternion_30s.csv create mode 100644 scripts/trajectories/rotate_euler_quaternion_30s_delayed.csv create mode 100644 scripts/trajectories/rotate_euler_quaternion_30s_delayed_voip.csv create mode 100644 scripts/trajectories/rotate_euler_quaternion_30s_voip.csv delete mode 100644 scripts/trajectories/rotate_euler_quaternion_5s.csv delete mode 100644 scripts/trajectories/rotate_euler_quaternion_5s_delayed.csv diff --git a/scripts/split_rendering/__init__.py b/scripts/split_rendering/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/split_rendering/isar_bstool.py b/scripts/split_rendering/isar_bstool.py new file mode 100755 index 0000000000..6b1e53b662 --- /dev/null +++ b/scripts/split_rendering/isar_bstool.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 + +""" + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. +""" + +from __future__ import annotations +import argparse +import math +import sys +from pathlib import Path + + +class IsarBstoolError(Exception): + pass + + +class IsarBitstream: + def __init__(self, file_path: Path) -> None: + self.file_path = file_path + + with open(file_path, "rb") as reader: + self.header = IsarFileHeader(reader) + self.frames = [] + + while reader.peek(1): + self.frames.append(IsarFileFrame(reader)) + + @property + def duration_seconds(self): + return self.num_frames * self.isar_frame_size_ms / 1000 + + @property + def duration_samples(self): + return int(self.duration_seconds * self.sample_rate) + + @property + def num_frames(self): + return len(self.frames) + + @property + def num_empty_frames(self): + return sum(frame.num_bits == 0 for frame in self.frames) + + @property + def sample_rate(self): + return self.header.sample_rate + + @property + def delay_ns(self): + return self.header.delay_ns + + @property + def delay_samples(self): + return round(self.header.delay_ns * self.sample_rate / 10**9) + + @property + def isar_frame_size_ms(self): + return self.header.isar_frame_size_ms + + @property + def isar_frame_size_samples(self): + return self.header.isar_frame_size_ms * self.sample_rate // 1000 + + @property + def pose_correction(self): + return self.header.pose_correction + + @property + def avg_bitrate_bps(self): + return sum(frame.num_bits for frame in self.frames) / self.duration_seconds + + @property + def avg_bitrate_non_empty_frames_bps(self): + return sum(frame.num_bits for frame in self.frames) / ( + (self.num_frames - self.num_empty_frames) * self.isar_frame_size_ms / 1000 + ) + + @property + def codec(self): + return self.header.codec + + @property + def codec_frame_size_ms(self): + return self.header.codec_frame_size_ms + + @property + def codec_frame_size_samples(self): + return self.header.codec_frame_size_ms * self.sample_rate // 1000 + + @property + def lc3plus_hires(self): + return self.header.lc3plus_hires + + def info(self): + return ( + "\n" + f"File : {self.file_path}\n" + f"Duration : {self.duration_seconds} s = {self.duration_samples} samples\n" + f"Frames : {self.num_frames} (incl. {self.num_empty_frames} empty)\n" + f"Sample Rate : {self.sample_rate} Hz\n" + f"Delay : {self.delay_ns} ns = {self.delay_samples} samples\n" + f"ISAR Frame Size : {self.isar_frame_size_ms} ms = {self.isar_frame_size_samples} samples\n" + f"Pose Correction : {self.pose_correction}\n" + f"Bitrate : {self.avg_bitrate_bps:.2f} bps (avg), {self.avg_bitrate_non_empty_frames_bps:.2f} bps (avg non-empty)\n" + f"Codec : {self.codec}\n" + f"Codec Frame Size : {self.codec_frame_size_ms} ms = {self.codec_frame_size_samples} samples\n" + f"LC3plus HIRES : {'ON' if self.lc3plus_hires else 'OFF'}\n" + ) + + def write(self, file_path: Path): + self.file_path = file_path + + with open(file_path, "wb") as writer: + writer.write(self.header.as_bytes) + + for frame in self.frames: + writer.write(frame.as_bytes) + + def trim(self, start_time_s: float, length_s: float | None = None) -> IsarBitstream: + if length_s is None: + length_s = self.duration_seconds + + start_time_ms = start_time_s * 1000 + length_ms = length_s * 1000 + + # Check for unusable values + if math.isinf(start_time_s) or math.isnan(start_time_s): + raise IsarBstoolError(f"start_time ({start_time_s} s) has unusable value") + if math.isinf(length_s) or math.isnan(length_s): + raise IsarBstoolError(f"length ({length_s} s) has unusable value") + + # Ensure times are not negative + if start_time_s < 0: + raise IsarBstoolError(f"start_time ({start_time_s} s) can't be negative") + if length_s < 0: + raise IsarBstoolError(f"length ({length_s} s) can't be negative") + + # We can only remove entire frames + if start_time_ms % self.isar_frame_size_ms != 0: + raise IsarBstoolError( + f"start_time ({start_time_s} s) must be an integer multiple of ISAR frame duration ({self.isar_frame_size_ms} ms)" + ) + if length_ms % self.isar_frame_size_ms != 0: + raise IsarBstoolError( + f"length ({length_s} s) must be an integer multiple of ISAR frame duration ({self.isar_frame_size_ms} ms)" + ) + + start_idx = int(start_time_ms / self.isar_frame_size_ms) + end_idx = start_idx + int(length_ms / self.isar_frame_size_ms) + self.frames = self.frames[start_idx : min(end_idx, len(self.frames))] + + return self + + def is_same_as(self, other: IsarBitstream) -> bool: + return self.header == other.header and self.frames == other.frames + + +class _AsBytes: + def __init__(self) -> None: + self.as_bytes = bytearray() + + def _read(self, reader, num_bytes): + bytes_ = reader.read(num_bytes) + self.as_bytes.extend(bytes_) + return bytes_ + + def __eq__(self, value: object, /) -> bool: + if not isinstance(value, _AsBytes): + return False + return self.as_bytes == value.as_bytes + + +class IsarFileHeader(_AsBytes): + def __init__(self, reader) -> None: + super().__init__() + + FILE_HEADER = b"MAIN_SPLITH" + file_header_top = self._read(reader, len(FILE_HEADER)) + if file_header_top != FILE_HEADER: + raise IsarBstoolError(f"Not a valid ISAR file: {reader.name}") + + self.delay_ns = _int_from_bytes(self._read(reader, 4)) + self.codec = _codec_from_bytes(self._read(reader, 4)) + self.pose_correction = _pose_corr_from_bytes(self._read(reader, 4)) + self.codec_frame_size_ms = _int_from_bytes(self._read(reader, 2)) + self.isar_frame_size_ms = _int_from_bytes(self._read(reader, 2)) + self.sample_rate = _int_from_bytes(self._read(reader, 4)) + self.lc3plus_hires = bool(_int_from_bytes(self._read(reader, 2))) + + +class IsarFileFrame(_AsBytes): + def __init__(self, reader) -> None: + super().__init__() + + FRAME_HEADER = b"SPLIT_FRAME" + frame_header = self._read(reader, len(FRAME_HEADER)) + if frame_header != FRAME_HEADER: + raise IsarBstoolError(f"Not a valid ISAR file: {reader.name}") + + version = _int_from_bytes(self._read(reader, 1)) + if version != 0: + raise IsarBstoolError( + f"Unupported version of ISAR file format: {reader.name}" + ) + + self.num_bits = _int_from_bytes(self._read(reader, 4)) + + payload_size = math.ceil(self.num_bits / 8) + self.payload = self._read(reader, payload_size) + + +###################################################################################### +# utilities +###################################################################################### + + +def _int_from_bytes(bytes_): + return int.from_bytes(bytes_, byteorder="little") + + +def _codec_from_bytes(bytes_): + # Refer to ISAR_SPLIT_REND_CODEC enum in C code + CODECS = ["LCLD", "LC3PLUS", "DEFAULT", "NONE"] + x = _int_from_bytes(bytes_) + + if x < len(CODECS): + return CODECS[x] + + return "UNKNOWN" + + +def _pose_corr_from_bytes(bytes_): + # Refer to ISAR_SPLIT_REND_POSE_CORRECTION_MODE enum in C code + POSE_CORR_MODES = ["NONE", "CLDFB"] + x = _int_from_bytes(bytes_) + + if x < len(POSE_CORR_MODES): + return POSE_CORR_MODES[x] + + return "UNKNOWN" + + +###################################################################################### +# subcommand functions +###################################################################################### + + +def _subcmd_info(args): + bs = IsarBitstream(args.file_in) + + match args.only: + case "duration_seconds": + print(bs.duration_seconds) + case "duration_samples": + print(bs.duration_samples) + case "num_frames": + print(bs.num_frames) + case "num_empty_frames": + print(bs.num_empty_frames) + case "sample_rate": + print(bs.sample_rate) + case "delay_ns": + print(bs.delay_ns) + case "delay_samples": + print(bs.delay_samples) + case "isar_frame_size_ms": + print(bs.isar_frame_size_ms) + case "isar_frame_size_samples": + print(bs.isar_frame_size_samples) + case "pose_correction": + print(bs.pose_correction) + case "avg_bitrate": + print(bs.avg_bitrate_bps) + case "avg_bitrate_non_empty_frames": + print(bs.avg_bitrate_non_empty_frames_bps) + case "codec": + print(bs.codec) + case "codec_frame_size_ms": + print(bs.codec_frame_size_ms) + case "codec_frame_size_samples": + print(bs.codec_frame_size_samples) + case "lc3plus_hires": + print("ON" if bs.lc3plus_hires else "OFF") + case None: + print(bs.info()) + case _: + raise IsarBstoolError(f"Not a valid parameter value: '{args.only}'") + + +def _subcmd_trim(args): + bs = IsarBitstream(args.file_in) + bs.trim(float(args.start_time), float(args.length) if args.length else None) + bs.write(args.file_out) + + +###################################################################################### +# main +###################################################################################### + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="isar_bstool", + description="Utility for inspecting and modifying ISAR bitstreams", + ) + parser.set_defaults(func=lambda _: parser.print_help()) + subparsers = parser.add_subparsers(title="Commands") + + info = subparsers.add_parser("info", help="Print information about a bitstream") + info.add_argument("file_in", help="Path to input file") + info.add_argument( + "--only", + help="Print only a specific parameter", + default=None, + choices=[ + "duration_seconds", + "duration_samples", + "num_frames", + "num_empty_frames", + "sample_rate", + "delay_ns", + "delay_samples", + "isar_frame_size_ms", + "isar_frame_size_samples", + "pose_correction", + "avg_bitrate", + "avg_bitrate_non_empty_frames", + "codec", + "codec_frame_size_ms", + "codec_frame_size_samples", + "lc3plus_hires", + ], + ) + info.set_defaults(func=_subcmd_info) + + trim = subparsers.add_parser( + "trim", help="Remove initial frames from a bitstream file" + ) + trim.add_argument("file_in", help="Path to input file") + trim.add_argument("file_out", help="Path to output file") + trim.add_argument( + "start_time", + help="Start point (in s) from which content should be copied to the output.", + ) + trim.add_argument( + "--length", + help="Amount of time (in s) to copy to the output. If not given, content is copied until the end of the input is reached.", + default=None, + ) + trim.set_defaults(func=_subcmd_trim) + + args = parser.parse_args() + + try: + args.func(args) + except (FileNotFoundError, PermissionError, IsarBstoolError) as e: + print(e, file=sys.stderr) + sys.exit(1) diff --git a/scripts/trajectories/rotate_euler_quaternion_30s.csv b/scripts/trajectories/rotate_euler_quaternion_30s.csv new file mode 100644 index 0000000000..d4fdb053c1 --- /dev/null +++ b/scripts/trajectories/rotate_euler_quaternion_30s.csv @@ -0,0 +1,6000 @@ +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 diff --git a/scripts/trajectories/rotate_euler_quaternion_30s_delayed.csv b/scripts/trajectories/rotate_euler_quaternion_30s_delayed.csv new file mode 100644 index 0000000000..7df0cd9c9a --- /dev/null +++ b/scripts/trajectories/rotate_euler_quaternion_30s_delayed.csv @@ -0,0 +1,6020 @@ +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 diff --git a/scripts/trajectories/rotate_euler_quaternion_30s_delayed_voip.csv b/scripts/trajectories/rotate_euler_quaternion_30s_delayed_voip.csv new file mode 100644 index 0000000000..cd42f9f9ae --- /dev/null +++ b/scripts/trajectories/rotate_euler_quaternion_30s_delayed_voip.csv @@ -0,0 +1,6032 @@ +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 diff --git a/scripts/trajectories/rotate_euler_quaternion_30s_voip.csv b/scripts/trajectories/rotate_euler_quaternion_30s_voip.csv new file mode 100644 index 0000000000..ee015a29bf --- /dev/null +++ b/scripts/trajectories/rotate_euler_quaternion_30s_voip.csv @@ -0,0 +1,6012 @@ +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,0,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 +-3,360,0,0 +-3,359.1,0,0 +-3,358.2,0,0 +-3,357.3,0,0 +-3,356.4,0,0 +-3,355.5,0,0 +-3,354.6,0,0 +-3,353.7,0,0 +-3,352.8,0,0 +-3,351.9,0,0 +-3,351,0,0 +-3,350.1,0,0 +-3,349.2,0,0 +-3,348.3,0,0 +-3,347.4,0,0 +-3,346.5,0,0 +-3,345.6,0,0 +-3,344.7,0,0 +-3,343.8,0,0 +-3,342.9,0,0 +-3,342,0,0 +-3,341.1,0,0 +-3,340.2,0,0 +-3,339.2,0,0 +-3,338.3,0,0 +-3,337.4,0,0 +-3,336.5,0,0 +-3,335.6,0,0 +-3,334.7,0,0 +-3,333.8,0,0 +-3,332.9,0,0 +-3,332,0,0 +-3,331.1,0,0 +-3,330.2,0,0 +-3,329.3,0,0 +-3,328.4,0,0 +-3,327.5,0,0 +-3,326.6,0,0 +-3,325.7,0,0 +-3,324.8,0,0 +-3,323.9,0,0 +-3,323,0,0 +-3,322.1,0,0 +-3,321.2,0,0 +-3,320.3,0,0 +-3,319.4,0,0 +-3,318.5,0,0 +-3,317.6,0,0 +-3,316.7,0,0 +-3,315.8,0,0 +-3,314.9,0,0 +-3,314,0,0 +-3,313.1,0,0 +-3,312.2,0,0 +-3,311.3,0,0 +-3,310.4,0,0 +-3,309.5,0,0 +-3,308.6,0,0 +-3,307.7,0,0 +-3,306.8,0,0 +-3,305.9,0,0 +-3,305,0,0 +-3,304.1,0,0 +-3,303.2,0,0 +-3,302.3,0,0 +-3,301.4,0,0 +-3,300.5,0,0 +-3,299.5,0,0 +-3,298.6,0,0 +-3,297.7,0,0 +-3,296.8,0,0 +-3,295.9,0,0 +-3,295,0,0 +-3,294.1,0,0 +-3,293.2,0,0 +-3,292.3,0,0 +-3,291.4,0,0 +-3,290.5,0,0 +-3,289.6,0,0 +-3,288.7,0,0 +-3,287.8,0,0 +-3,286.9,0,0 +-3,286,0,0 +-3,285.1,0,0 +-3,284.2,0,0 +-3,283.3,0,0 +-3,282.4,0,0 +-3,281.5,0,0 +-3,280.6,0,0 +-3,279.7,0,0 +-3,278.8,0,0 +-3,277.9,0,0 +-3,277,0,0 +-3,276.1,0,0 +-3,275.2,0,0 +-3,274.3,0,0 +-3,273.4,0,0 +-3,272.5,0,0 +-3,271.6,0,0 +-3,270.7,0,0 +-3,269.8,0,0 +-3,268.9,0,0 +-3,268,0,0 +-3,267.1,0,0 +-3,266.2,0,0 +-3,265.3,0,0 +-3,264.4,0,0 +-3,263.5,0,0 +-3,262.6,0,0 +-3,261.7,0,0 +-3,260.8,0,0 +-3,259.8,0,0 +-3,258.9,0,0 +-3,258,0,0 +-3,257.1,0,0 +-3,256.2,0,0 +-3,255.3,0,0 +-3,254.4,0,0 +-3,253.5,0,0 +-3,252.6,0,0 +-3,251.7,0,0 +-3,250.8,0,0 +-3,249.9,0,0 +-3,249,0,0 +-3,248.1,0,0 +-3,247.2,0,0 +-3,246.3,0,0 +-3,245.4,0,0 +-3,244.5,0,0 +-3,243.6,0,0 +-3,242.7,0,0 +-3,241.8,0,0 +-3,240.9,0,0 +-3,240,0,0 +-3,239.1,0,0 +-3,238.2,0,0 +-3,237.3,0,0 +-3,236.4,0,0 +-3,235.5,0,0 +-3,234.6,0,0 +-3,233.7,0,0 +-3,232.8,0,0 +-3,231.9,0,0 +-3,231,0,0 +-3,230.1,0,0 +-3,229.2,0,0 +-3,228.3,0,0 +-3,227.4,0,0 +-3,226.5,0,0 +-3,225.6,0,0 +-3,224.7,0,0 +-3,223.8,0,0 +-3,222.9,0,0 +-3,222,0,0 +-3,221.1,0,0 +-3,220.2,0,0 +-3,219.2,0,0 +-3,218.3,0,0 +-3,217.4,0,0 +-3,216.5,0,0 +-3,215.6,0,0 +-3,214.7,0,0 +-3,213.8,0,0 +-3,212.9,0,0 +-3,212,0,0 +-3,211.1,0,0 +-3,210.2,0,0 +-3,209.3,0,0 +-3,208.4,0,0 +-3,207.5,0,0 +-3,206.6,0,0 +-3,205.7,0,0 +-3,204.8,0,0 +-3,203.9,0,0 +-3,203,0,0 +-3,202.1,0,0 +-3,201.2,0,0 +-3,200.3,0,0 +-3,199.4,0,0 +-3,198.5,0,0 +-3,197.6,0,0 +-3,196.7,0,0 +-3,195.8,0,0 +-3,194.9,0,0 +-3,194,0,0 +-3,193.1,0,0 +-3,192.2,0,0 +-3,191.3,0,0 +-3,190.4,0,0 +-3,189.5,0,0 +-3,188.6,0,0 +-3,187.7,0,0 +-3,186.8,0,0 +-3,185.9,0,0 +-3,185,0,0 +-3,184.1,0,0 +-3,183.2,0,0 +-3,182.3,0,0 +-3,181.4,0,0 +-3,180.5,0,0 +-3,179.5,-90,0 +-3,178.6,-89.5,0 +-3,177.7,-89.1,0 +-3,176.8,-88.6,0 +-3,175.9,-88.2,0 +-3,175,-87.7,0 +-3,174.1,-87.3,0 +-3,173.2,-86.8,0 +-3,172.3,-86.4,0 +-3,171.4,-85.9,0 +-3,170.5,-85.5,0 +-3,169.6,-85,0 +-3,168.7,-84.6,0 +-3,167.8,-84.1,0 +-3,166.9,-83.7,0 +-3,166,-83.2,0 +-3,165.1,-82.8,0 +-3,164.2,-82.3,0 +-3,163.3,-81.9,0 +-3,162.4,-81.4,0 +-3,161.5,-81,0 +-3,160.6,-80.5,0 +-3,159.7,-80.1,0 +-3,158.8,-79.6,0 +-3,157.9,-79.1,0 +-3,157,-78.7,0 +-3,156.1,-78.2,0 +-3,155.2,-77.8,0 +-3,154.3,-77.3,0 +-3,153.4,-76.9,0 +-3,152.5,-76.4,0 +-3,151.6,-76,0 +-3,150.7,-75.5,0 +-3,149.8,-75.1,0 +-3,148.9,-74.6,0 +-3,148,-74.2,0 +-3,147.1,-73.7,0 +-3,146.2,-73.3,0 +-3,145.3,-72.8,0 +-3,144.4,-72.4,0 +-3,143.5,-71.9,0 +-3,142.6,-71.5,0 +-3,141.7,-71,0 +-3,140.8,-70.6,0 +-3,139.8,-70.1,0 +-3,138.9,-69.6,0 +-3,138,-69.2,0 +-3,137.1,-68.7,0 +-3,136.2,-68.3,0 +-3,135.3,-67.8,0 +-3,134.4,-67.4,0 +-3,133.5,-66.9,0 +-3,132.6,-66.5,0 +-3,131.7,-66,0 +-3,130.8,-65.6,0 +-3,129.9,-65.1,0 +-3,129,-64.7,0 +-3,128.1,-64.2,0 +-3,127.2,-63.8,0 +-3,126.3,-63.3,0 +-3,125.4,-62.9,0 +-3,124.5,-62.4,0 +-3,123.6,-62,0 +-3,122.7,-61.5,0 +-3,121.8,-61.1,0 +-3,120.9,-60.6,0 +-3,120,-60.2,0 +-3,119.1,-59.7,0 +-3,118.2,-59.2,0 +-3,117.3,-58.8,0 +-3,116.4,-58.3,0 +-3,115.5,-57.9,0 +-3,114.6,-57.4,0 +-3,113.7,-57,0 +-3,112.8,-56.5,0 +-3,111.9,-56.1,0 +-3,111,-55.6,0 +-3,110.1,-55.2,0 +-3,109.2,-54.7,0 +-3,108.3,-54.3,0 +-3,107.4,-53.8,0 +-3,106.5,-53.4,0 +-3,105.6,-52.9,0 +-3,104.7,-52.5,0 +-3,103.8,-52,0 +-3,102.9,-51.6,0 +-3,102,-51.1,0 +-3,101.1,-50.7,0 +-3,100.2,-50.2,0 +-3,99.2,-49.7,0 +-3,98.3,-49.3,0 +-3,97.4,-48.8,0 +-3,96.5,-48.4,0 +-3,95.6,-47.9,0 +-3,94.7,-47.5,0 +-3,93.8,-47,0 +-3,92.9,-46.6,0 +-3,92,-46.1,0 +-3,91.1,-45.7,0 +-3,90.2,-45.2,0 +-3,89.3,-44.8,90 +-3,88.4,-44.3,89.1 +-3,87.5,-43.9,88.2 +-3,86.6,-43.4,87.3 +-3,85.7,-43,86.4 +-3,84.8,-42.5,85.5 +-3,83.9,-42.1,84.5 +-3,83,-41.6,83.6 +-3,82.1,-41.2,82.7 +-3,81.2,-40.7,81.8 +-3,80.3,-40.3,80.9 +-3,79.4,-39.8,80 +-3,78.5,-39.3,79.1 +-3,77.6,-38.9,78.2 +-3,76.7,-38.4,77.3 +-3,75.8,-38,76.4 +-3,74.9,-37.5,75.5 +-3,74,-37.1,74.5 +-3,73.1,-36.6,73.6 +-3,72.2,-36.2,72.7 +-3,71.3,-35.7,71.8 +-3,70.4,-35.3,70.9 +-3,69.5,-34.8,70 +-3,68.6,-34.4,69.1 +-3,67.7,-33.9,68.2 +-3,66.8,-33.5,67.3 +-3,65.9,-33,66.4 +-3,65,-32.6,65.5 +-3,64.1,-32.1,64.5 +-3,63.2,-31.7,63.6 +-3,62.3,-31.2,62.7 +-3,61.4,-30.8,61.8 +-3,60.5,-30.3,60.9 +-3,59.5,-29.8,60 +-3,58.6,-29.4,59.1 +-3,57.7,-28.9,58.2 +-3,56.8,-28.5,57.3 +-3,55.9,-28,56.4 +-3,55,-27.6,55.5 +-3,54.1,-27.1,54.5 +-3,53.2,-26.7,53.6 +-3,52.3,-26.2,52.7 +-3,51.4,-25.8,51.8 +-3,50.5,-25.3,50.9 +-3,49.6,-24.9,50 +-3,48.7,-24.4,49.1 +-3,47.8,-24,48.2 +-3,46.9,-23.5,47.3 +-3,46,-23.1,46.4 +-3,45.1,-22.6,45.5 +-3,44.2,-22.2,44.5 +-3,43.3,-21.7,43.6 +-3,42.4,-21.3,42.7 +-3,41.5,-20.8,41.8 +-3,40.6,-20.4,40.9 +-3,39.7,-19.9,40 +-3,38.8,-19.4,39.1 +-3,37.9,-19,38.2 +-3,37,-18.5,37.3 +-3,36.1,-18.1,36.4 +-3,35.2,-17.6,35.5 +-3,34.3,-17.2,34.5 +-3,33.4,-16.7,33.6 +-3,32.5,-16.3,32.7 +-3,31.6,-15.8,31.8 +-3,30.7,-15.4,30.9 +-3,29.8,-14.9,30 +-3,28.9,-14.5,29.1 +-3,28,-14,28.2 +-3,27.1,-13.6,27.3 +-3,26.2,-13.1,26.4 +-3,25.3,-12.7,25.5 +-3,24.4,-12.2,24.5 +-3,23.5,-11.8,23.6 +-3,22.6,-11.3,22.7 +-3,21.7,-10.9,21.8 +-3,20.8,-10.4,20.9 +-3,19.8,-9.9,20 +-3,18.9,-9.5,19.1 +-3,18,-9,18.2 +-3,17.1,-8.6,17.3 +-3,16.2,-8.1,16.4 +-3,15.3,-7.7,15.5 +-3,14.4,-7.2,14.5 +-3,13.5,-6.8,13.6 +-3,12.6,-6.3,12.7 +-3,11.7,-5.9,11.8 +-3,10.8,-5.4,10.9 +-3,9.9,-5,10 +-3,9,-4.5,9.1 +-3,8.1,-4.1,8.2 +-3,7.2,-3.6,7.3 +-3,6.3,-3.2,6.4 +-3,5.4,-2.7,5.5 +-3,4.5,-2.3,4.5 +-3,3.6,-1.8,3.6 +-3,2.7,-1.4,2.7 +-3,1.8,-0.9,1.8 +-3,0.9,-0.5,0.9 +-3,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.3 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.1,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.2,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,1 +0.3,0,0,0.9 +0.3,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.4,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.9 +0.5,0,0,0.8 +0.5,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.6,0,0,0.8 +0.7,0,0,0.8 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.7,0,0,0.7 +0.8,0,0,0.7 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.6 +0.8,0,0,0.5 +0.8,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.5 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.4 +0.9,0,0,0.3 +0.9,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.3 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.2 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0.1 +1,0,0,0 +1,0,0,0 +1,0,0,0 +1,0,0,0 +-3,0,0,0 +-3,0.9,-0.5,0.9 +-3,1.8,-0.9,1.8 +-3,2.7,-1.4,2.7 +-3,3.6,-1.8,3.6 +-3,4.5,-2.3,4.5 +-3,5.4,-2.7,5.5 +-3,6.3,-3.2,6.4 +-3,7.2,-3.6,7.3 +-3,8.1,-4.1,8.2 +-3,9,-4.5,9.1 +-3,9.9,-5,10 +-3,10.8,-5.4,10.9 +-3,11.7,-5.9,11.8 +-3,12.6,-6.3,12.7 +-3,13.5,-6.8,13.6 +-3,14.4,-7.2,14.5 +-3,15.3,-7.7,15.5 +-3,16.2,-8.1,16.4 +-3,17.1,-8.6,17.3 +-3,18,-9,18.2 +-3,18.9,-9.5,19.1 +-3,19.8,-9.9,20 +-3,20.8,-10.4,20.9 +-3,21.7,-10.9,21.8 +-3,22.6,-11.3,22.7 +-3,23.5,-11.8,23.6 +-3,24.4,-12.2,24.5 +-3,25.3,-12.7,25.5 +-3,26.2,-13.1,26.4 +-3,27.1,-13.6,27.3 +-3,28,-14,28.2 +-3,28.9,-14.5,29.1 +-3,29.8,-14.9,30 +-3,30.7,-15.4,30.9 +-3,31.6,-15.8,31.8 +-3,32.5,-16.3,32.7 +-3,33.4,-16.7,33.6 +-3,34.3,-17.2,34.5 +-3,35.2,-17.6,35.5 +-3,36.1,-18.1,36.4 +-3,37,-18.5,37.3 +-3,37.9,-19,38.2 +-3,38.8,-19.4,39.1 +-3,39.7,-19.9,40 +-3,40.6,-20.4,40.9 +-3,41.5,-20.8,41.8 +-3,42.4,-21.3,42.7 +-3,43.3,-21.7,43.6 +-3,44.2,-22.2,44.5 +-3,45.1,-22.6,45.5 +-3,46,-23.1,46.4 +-3,46.9,-23.5,47.3 +-3,47.8,-24,48.2 +-3,48.7,-24.4,49.1 +-3,49.6,-24.9,50 +-3,50.5,-25.3,50.9 +-3,51.4,-25.8,51.8 +-3,52.3,-26.2,52.7 +-3,53.2,-26.7,53.6 +-3,54.1,-27.1,54.5 +-3,55,-27.6,55.5 +-3,55.9,-28,56.4 +-3,56.8,-28.5,57.3 +-3,57.7,-28.9,58.2 +-3,58.6,-29.4,59.1 +-3,59.5,-29.8,60 +-3,60.5,-30.3,60.9 +-3,61.4,-30.8,61.8 +-3,62.3,-31.2,62.7 +-3,63.2,-31.7,63.6 +-3,64.1,-32.1,64.5 +-3,65,-32.6,65.5 +-3,65.9,-33,66.4 +-3,66.8,-33.5,67.3 +-3,67.7,-33.9,68.2 +-3,68.6,-34.4,69.1 +-3,69.5,-34.8,70 +-3,70.4,-35.3,70.9 +-3,71.3,-35.7,71.8 +-3,72.2,-36.2,72.7 +-3,73.1,-36.6,73.6 +-3,74,-37.1,74.5 +-3,74.9,-37.5,75.5 +-3,75.8,-38,76.4 +-3,76.7,-38.4,77.3 +-3,77.6,-38.9,78.2 +-3,78.5,-39.3,79.1 +-3,79.4,-39.8,80 +-3,80.3,-40.3,80.9 +-3,81.2,-40.7,81.8 +-3,82.1,-41.2,82.7 +-3,83,-41.6,83.6 +-3,83.9,-42.1,84.5 +-3,84.8,-42.5,85.5 +-3,85.7,-43,86.4 +-3,86.6,-43.4,87.3 +-3,87.5,-43.9,88.2 +-3,88.4,-44.3,89.1 +-3,89.3,-44.8,90 +-3,90.2,-45.2,0 +-3,91.1,-45.7,0 +-3,92,-46.1,0 +-3,92.9,-46.6,0 +-3,93.8,-47,0 +-3,94.7,-47.5,0 +-3,95.6,-47.9,0 +-3,96.5,-48.4,0 +-3,97.4,-48.8,0 +-3,98.3,-49.3,0 +-3,99.2,-49.7,0 +-3,100.2,-50.2,0 +-3,101.1,-50.7,0 +-3,102,-51.1,0 +-3,102.9,-51.6,0 +-3,103.8,-52,0 +-3,104.7,-52.5,0 +-3,105.6,-52.9,0 +-3,106.5,-53.4,0 +-3,107.4,-53.8,0 +-3,108.3,-54.3,0 +-3,109.2,-54.7,0 +-3,110.1,-55.2,0 +-3,111,-55.6,0 +-3,111.9,-56.1,0 +-3,112.8,-56.5,0 +-3,113.7,-57,0 +-3,114.6,-57.4,0 +-3,115.5,-57.9,0 +-3,116.4,-58.3,0 +-3,117.3,-58.8,0 +-3,118.2,-59.2,0 +-3,119.1,-59.7,0 +-3,120,-60.2,0 +-3,120.9,-60.6,0 +-3,121.8,-61.1,0 +-3,122.7,-61.5,0 +-3,123.6,-62,0 +-3,124.5,-62.4,0 +-3,125.4,-62.9,0 +-3,126.3,-63.3,0 +-3,127.2,-63.8,0 +-3,128.1,-64.2,0 +-3,129,-64.7,0 +-3,129.9,-65.1,0 +-3,130.8,-65.6,0 +-3,131.7,-66,0 +-3,132.6,-66.5,0 +-3,133.5,-66.9,0 +-3,134.4,-67.4,0 +-3,135.3,-67.8,0 +-3,136.2,-68.3,0 +-3,137.1,-68.7,0 +-3,138,-69.2,0 +-3,138.9,-69.6,0 +-3,139.8,-70.1,0 +-3,140.8,-70.6,0 +-3,141.7,-71,0 +-3,142.6,-71.5,0 +-3,143.5,-71.9,0 +-3,144.4,-72.4,0 +-3,145.3,-72.8,0 +-3,146.2,-73.3,0 +-3,147.1,-73.7,0 +-3,148,-74.2,0 +-3,148.9,-74.6,0 +-3,149.8,-75.1,0 +-3,150.7,-75.5,0 +-3,151.6,-76,0 +-3,152.5,-76.4,0 +-3,153.4,-76.9,0 +-3,154.3,-77.3,0 +-3,155.2,-77.8,0 +-3,156.1,-78.2,0 +-3,157,-78.7,0 +-3,157.9,-79.1,0 +-3,158.8,-79.6,0 +-3,159.7,-80.1,0 +-3,160.6,-80.5,0 +-3,161.5,-81,0 +-3,162.4,-81.4,0 +-3,163.3,-81.9,0 +-3,164.2,-82.3,0 +-3,165.1,-82.8,0 +-3,166,-83.2,0 +-3,166.9,-83.7,0 +-3,167.8,-84.1,0 +-3,168.7,-84.6,0 +-3,169.6,-85,0 +-3,170.5,-85.5,0 +-3,171.4,-85.9,0 +-3,172.3,-86.4,0 +-3,173.2,-86.8,0 +-3,174.1,-87.3,0 +-3,175,-87.7,0 +-3,175.9,-88.2,0 +-3,176.8,-88.6,0 +-3,177.7,-89.1,0 +-3,178.6,-89.5,0 +-3,179.5,-90,0 +-3,180.5,0,0 +-3,181.4,0,0 +-3,182.3,0,0 +-3,183.2,0,0 +-3,184.1,0,0 +-3,185,0,0 +-3,185.9,0,0 +-3,186.8,0,0 +-3,187.7,0,0 +-3,188.6,0,0 +-3,189.5,0,0 +-3,190.4,0,0 +-3,191.3,0,0 +-3,192.2,0,0 +-3,193.1,0,0 +-3,194,0,0 +-3,194.9,0,0 +-3,195.8,0,0 +-3,196.7,0,0 +-3,197.6,0,0 +-3,198.5,0,0 +-3,199.4,0,0 +-3,200.3,0,0 +-3,201.2,0,0 +-3,202.1,0,0 +-3,203,0,0 +-3,203.9,0,0 +-3,204.8,0,0 +-3,205.7,0,0 +-3,206.6,0,0 +-3,207.5,0,0 +-3,208.4,0,0 +-3,209.3,0,0 +-3,210.2,0,0 +-3,211.1,0,0 +-3,212,0,0 +-3,212.9,0,0 +-3,213.8,0,0 +-3,214.7,0,0 +-3,215.6,0,0 +-3,216.5,0,0 +-3,217.4,0,0 +-3,218.3,0,0 +-3,219.2,0,0 +-3,220.2,0,0 +-3,221.1,0,0 +-3,222,0,0 +-3,222.9,0,0 +-3,223.8,0,0 +-3,224.7,0,0 +-3,225.6,0,0 +-3,226.5,0,0 +-3,227.4,0,0 +-3,228.3,0,0 +-3,229.2,0,0 +-3,230.1,0,0 +-3,231,0,0 +-3,231.9,0,0 +-3,232.8,0,0 +-3,233.7,0,0 +-3,234.6,0,0 +-3,235.5,0,0 +-3,236.4,0,0 +-3,237.3,0,0 +-3,238.2,0,0 +-3,239.1,0,0 +-3,240,0,0 +-3,240.9,0,0 +-3,241.8,0,0 +-3,242.7,0,0 +-3,243.6,0,0 +-3,244.5,0,0 +-3,245.4,0,0 +-3,246.3,0,0 +-3,247.2,0,0 +-3,248.1,0,0 +-3,249,0,0 +-3,249.9,0,0 +-3,250.8,0,0 +-3,251.7,0,0 +-3,252.6,0,0 +-3,253.5,0,0 +-3,254.4,0,0 +-3,255.3,0,0 +-3,256.2,0,0 +-3,257.1,0,0 +-3,258,0,0 +-3,258.9,0,0 +-3,259.8,0,0 +-3,260.8,0,0 +-3,261.7,0,0 +-3,262.6,0,0 +-3,263.5,0,0 +-3,264.4,0,0 +-3,265.3,0,0 +-3,266.2,0,0 +-3,267.1,0,0 +-3,268,0,0 +-3,268.9,0,0 +-3,269.8,0,0 +-3,270.7,0,0 +-3,271.6,0,0 +-3,272.5,0,0 +-3,273.4,0,0 +-3,274.3,0,0 +-3,275.2,0,0 +-3,276.1,0,0 +-3,277,0,0 +-3,277.9,0,0 +-3,278.8,0,0 +-3,279.7,0,0 +-3,280.6,0,0 +-3,281.5,0,0 +-3,282.4,0,0 +-3,283.3,0,0 +-3,284.2,0,0 +-3,285.1,0,0 +-3,286,0,0 +-3,286.9,0,0 +-3,287.8,0,0 +-3,288.7,0,0 +-3,289.6,0,0 +-3,290.5,0,0 +-3,291.4,0,0 +-3,292.3,0,0 +-3,293.2,0,0 +-3,294.1,0,0 +-3,295,0,0 +-3,295.9,0,0 +-3,296.8,0,0 +-3,297.7,0,0 +-3,298.6,0,0 +-3,299.5,0,0 +-3,300.5,0,0 +-3,301.4,0,0 +-3,302.3,0,0 +-3,303.2,0,0 +-3,304.1,0,0 +-3,305,0,0 +-3,305.9,0,0 +-3,306.8,0,0 +-3,307.7,0,0 +-3,308.6,0,0 +-3,309.5,0,0 +-3,310.4,0,0 +-3,311.3,0,0 +-3,312.2,0,0 +-3,313.1,0,0 +-3,314,0,0 +-3,314.9,0,0 +-3,315.8,0,0 +-3,316.7,0,0 +-3,317.6,0,0 +-3,318.5,0,0 +-3,319.4,0,0 +-3,320.3,0,0 +-3,321.2,0,0 +-3,322.1,0,0 +-3,323,0,0 +-3,323.9,0,0 +-3,324.8,0,0 +-3,325.7,0,0 +-3,326.6,0,0 +-3,327.5,0,0 +-3,328.4,0,0 +-3,329.3,0,0 +-3,330.2,0,0 +-3,331.1,0,0 +-3,332,0,0 +-3,332.9,0,0 +-3,333.8,0,0 +-3,334.7,0,0 +-3,335.6,0,0 +-3,336.5,0,0 +-3,337.4,0,0 +-3,338.3,0,0 +-3,339.2,0,0 +-3,340.2,0,0 +-3,341.1,0,0 +-3,342,0,0 +-3,342.9,0,0 +-3,343.8,0,0 +-3,344.7,0,0 +-3,345.6,0,0 +-3,346.5,0,0 +-3,347.4,0,0 +-3,348.3,0,0 +-3,349.2,0,0 +-3,350.1,0,0 +-3,351,0,0 +-3,351.9,0,0 +-3,352.8,0,0 +-3,353.7,0,0 +-3,354.6,0,0 +-3,355.5,0,0 +-3,356.4,0,0 +-3,357.3,0,0 +-3,358.2,0,0 +-3,359.1,0,0 +-3,360,0,0 diff --git a/scripts/trajectories/rotate_euler_quaternion_5s.csv b/scripts/trajectories/rotate_euler_quaternion_5s.csv deleted file mode 100644 index 0052e3d7c7..0000000000 --- a/scripts/trajectories/rotate_euler_quaternion_5s.csv +++ /dev/null @@ -1,1000 +0,0 @@ --3,360,0,0 --3,359.1,0,0 --3,358.2,0,0 --3,357.3,0,0 --3,356.4,0,0 --3,355.5,0,0 --3,354.6,0,0 --3,353.7,0,0 --3,352.8,0,0 --3,351.9,0,0 --3,351,0,0 --3,350.1,0,0 --3,349.2,0,0 --3,348.3,0,0 --3,347.4,0,0 --3,346.5,0,0 --3,345.6,0,0 --3,344.7,0,0 --3,343.8,0,0 --3,342.9,0,0 --3,342,0,0 --3,341.1,0,0 --3,340.2,0,0 --3,339.2,0,0 --3,338.3,0,0 --3,337.4,0,0 --3,336.5,0,0 --3,335.6,0,0 --3,334.7,0,0 --3,333.8,0,0 --3,332.9,0,0 --3,332,0,0 --3,331.1,0,0 --3,330.2,0,0 --3,329.3,0,0 --3,328.4,0,0 --3,327.5,0,0 --3,326.6,0,0 --3,325.7,0,0 --3,324.8,0,0 --3,323.9,0,0 --3,323,0,0 --3,322.1,0,0 --3,321.2,0,0 --3,320.3,0,0 --3,319.4,0,0 --3,318.5,0,0 --3,317.6,0,0 --3,316.7,0,0 --3,315.8,0,0 --3,314.9,0,0 --3,314,0,0 --3,313.1,0,0 --3,312.2,0,0 --3,311.3,0,0 --3,310.4,0,0 --3,309.5,0,0 --3,308.6,0,0 --3,307.7,0,0 --3,306.8,0,0 --3,305.9,0,0 --3,305,0,0 --3,304.1,0,0 --3,303.2,0,0 --3,302.3,0,0 --3,301.4,0,0 --3,300.5,0,0 --3,299.5,0,0 --3,298.6,0,0 --3,297.7,0,0 --3,296.8,0,0 --3,295.9,0,0 --3,295,0,0 --3,294.1,0,0 --3,293.2,0,0 --3,292.3,0,0 --3,291.4,0,0 --3,290.5,0,0 --3,289.6,0,0 --3,288.7,0,0 --3,287.8,0,0 --3,286.9,0,0 --3,286,0,0 --3,285.1,0,0 --3,284.2,0,0 --3,283.3,0,0 --3,282.4,0,0 --3,281.5,0,0 --3,280.6,0,0 --3,279.7,0,0 --3,278.8,0,0 --3,277.9,0,0 --3,277,0,0 --3,276.1,0,0 --3,275.2,0,0 --3,274.3,0,0 --3,273.4,0,0 --3,272.5,0,0 --3,271.6,0,0 --3,270.7,0,0 --3,269.8,0,0 --3,268.9,0,0 --3,268,0,0 --3,267.1,0,0 --3,266.2,0,0 --3,265.3,0,0 --3,264.4,0,0 --3,263.5,0,0 --3,262.6,0,0 --3,261.7,0,0 --3,260.8,0,0 --3,259.8,0,0 --3,258.9,0,0 --3,258,0,0 --3,257.1,0,0 --3,256.2,0,0 --3,255.3,0,0 --3,254.4,0,0 --3,253.5,0,0 --3,252.6,0,0 --3,251.7,0,0 --3,250.8,0,0 --3,249.9,0,0 --3,249,0,0 --3,248.1,0,0 --3,247.2,0,0 --3,246.3,0,0 --3,245.4,0,0 --3,244.5,0,0 --3,243.6,0,0 --3,242.7,0,0 --3,241.8,0,0 --3,240.9,0,0 --3,240,0,0 --3,239.1,0,0 --3,238.2,0,0 --3,237.3,0,0 --3,236.4,0,0 --3,235.5,0,0 --3,234.6,0,0 --3,233.7,0,0 --3,232.8,0,0 --3,231.9,0,0 --3,231,0,0 --3,230.1,0,0 --3,229.2,0,0 --3,228.3,0,0 --3,227.4,0,0 --3,226.5,0,0 --3,225.6,0,0 --3,224.7,0,0 --3,223.8,0,0 --3,222.9,0,0 --3,222,0,0 --3,221.1,0,0 --3,220.2,0,0 --3,219.2,0,0 --3,218.3,0,0 --3,217.4,0,0 --3,216.5,0,0 --3,215.6,0,0 --3,214.7,0,0 --3,213.8,0,0 --3,212.9,0,0 --3,212,0,0 --3,211.1,0,0 --3,210.2,0,0 --3,209.3,0,0 --3,208.4,0,0 --3,207.5,0,0 --3,206.6,0,0 --3,205.7,0,0 --3,204.8,0,0 --3,203.9,0,0 --3,203,0,0 --3,202.1,0,0 --3,201.2,0,0 --3,200.3,0,0 --3,199.4,0,0 --3,198.5,0,0 --3,197.6,0,0 --3,196.7,0,0 --3,195.8,0,0 --3,194.9,0,0 --3,194,0,0 --3,193.1,0,0 --3,192.2,0,0 --3,191.3,0,0 --3,190.4,0,0 --3,189.5,0,0 --3,188.6,0,0 --3,187.7,0,0 --3,186.8,0,0 --3,185.9,0,0 --3,185,0,0 --3,184.1,0,0 --3,183.2,0,0 --3,182.3,0,0 --3,181.4,0,0 --3,180.5,0,0 --3,179.5,-90,0 --3,178.6,-89.5,0 --3,177.7,-89.1,0 --3,176.8,-88.6,0 --3,175.9,-88.2,0 --3,175,-87.7,0 --3,174.1,-87.3,0 --3,173.2,-86.8,0 --3,172.3,-86.4,0 --3,171.4,-85.9,0 --3,170.5,-85.5,0 --3,169.6,-85,0 --3,168.7,-84.6,0 --3,167.8,-84.1,0 --3,166.9,-83.7,0 --3,166,-83.2,0 --3,165.1,-82.8,0 --3,164.2,-82.3,0 --3,163.3,-81.9,0 --3,162.4,-81.4,0 --3,161.5,-81,0 --3,160.6,-80.5,0 --3,159.7,-80.1,0 --3,158.8,-79.6,0 --3,157.9,-79.1,0 --3,157,-78.7,0 --3,156.1,-78.2,0 --3,155.2,-77.8,0 --3,154.3,-77.3,0 --3,153.4,-76.9,0 --3,152.5,-76.4,0 --3,151.6,-76,0 --3,150.7,-75.5,0 --3,149.8,-75.1,0 --3,148.9,-74.6,0 --3,148,-74.2,0 --3,147.1,-73.7,0 --3,146.2,-73.3,0 --3,145.3,-72.8,0 --3,144.4,-72.4,0 --3,143.5,-71.9,0 --3,142.6,-71.5,0 --3,141.7,-71,0 --3,140.8,-70.6,0 --3,139.8,-70.1,0 --3,138.9,-69.6,0 --3,138,-69.2,0 --3,137.1,-68.7,0 --3,136.2,-68.3,0 --3,135.3,-67.8,0 --3,134.4,-67.4,0 --3,133.5,-66.9,0 --3,132.6,-66.5,0 --3,131.7,-66,0 --3,130.8,-65.6,0 --3,129.9,-65.1,0 --3,129,-64.7,0 --3,128.1,-64.2,0 --3,127.2,-63.8,0 --3,126.3,-63.3,0 --3,125.4,-62.9,0 --3,124.5,-62.4,0 --3,123.6,-62,0 --3,122.7,-61.5,0 --3,121.8,-61.1,0 --3,120.9,-60.6,0 --3,120,-60.2,0 --3,119.1,-59.7,0 --3,118.2,-59.2,0 --3,117.3,-58.8,0 --3,116.4,-58.3,0 --3,115.5,-57.9,0 --3,114.6,-57.4,0 --3,113.7,-57,0 --3,112.8,-56.5,0 --3,111.9,-56.1,0 --3,111,-55.6,0 --3,110.1,-55.2,0 --3,109.2,-54.7,0 --3,108.3,-54.3,0 --3,107.4,-53.8,0 --3,106.5,-53.4,0 --3,105.6,-52.9,0 --3,104.7,-52.5,0 --3,103.8,-52,0 --3,102.9,-51.6,0 --3,102,-51.1,0 --3,101.1,-50.7,0 --3,100.2,-50.2,0 --3,99.2,-49.7,0 --3,98.3,-49.3,0 --3,97.4,-48.8,0 --3,96.5,-48.4,0 --3,95.6,-47.9,0 --3,94.7,-47.5,0 --3,93.8,-47,0 --3,92.9,-46.6,0 --3,92,-46.1,0 --3,91.1,-45.7,0 --3,90.2,-45.2,0 --3,89.3,-44.8,90 --3,88.4,-44.3,89.1 --3,87.5,-43.9,88.2 --3,86.6,-43.4,87.3 --3,85.7,-43,86.4 --3,84.8,-42.5,85.5 --3,83.9,-42.1,84.5 --3,83,-41.6,83.6 --3,82.1,-41.2,82.7 --3,81.2,-40.7,81.8 --3,80.3,-40.3,80.9 --3,79.4,-39.8,80 --3,78.5,-39.3,79.1 --3,77.6,-38.9,78.2 --3,76.7,-38.4,77.3 --3,75.8,-38,76.4 --3,74.9,-37.5,75.5 --3,74,-37.1,74.5 --3,73.1,-36.6,73.6 --3,72.2,-36.2,72.7 --3,71.3,-35.7,71.8 --3,70.4,-35.3,70.9 --3,69.5,-34.8,70 --3,68.6,-34.4,69.1 --3,67.7,-33.9,68.2 --3,66.8,-33.5,67.3 --3,65.9,-33,66.4 --3,65,-32.6,65.5 --3,64.1,-32.1,64.5 --3,63.2,-31.7,63.6 --3,62.3,-31.2,62.7 --3,61.4,-30.8,61.8 --3,60.5,-30.3,60.9 --3,59.5,-29.8,60 --3,58.6,-29.4,59.1 --3,57.7,-28.9,58.2 --3,56.8,-28.5,57.3 --3,55.9,-28,56.4 --3,55,-27.6,55.5 --3,54.1,-27.1,54.5 --3,53.2,-26.7,53.6 --3,52.3,-26.2,52.7 --3,51.4,-25.8,51.8 --3,50.5,-25.3,50.9 --3,49.6,-24.9,50 --3,48.7,-24.4,49.1 --3,47.8,-24,48.2 --3,46.9,-23.5,47.3 --3,46,-23.1,46.4 --3,45.1,-22.6,45.5 --3,44.2,-22.2,44.5 --3,43.3,-21.7,43.6 --3,42.4,-21.3,42.7 --3,41.5,-20.8,41.8 --3,40.6,-20.4,40.9 --3,39.7,-19.9,40 --3,38.8,-19.4,39.1 --3,37.9,-19,38.2 --3,37,-18.5,37.3 --3,36.1,-18.1,36.4 --3,35.2,-17.6,35.5 --3,34.3,-17.2,34.5 --3,33.4,-16.7,33.6 --3,32.5,-16.3,32.7 --3,31.6,-15.8,31.8 --3,30.7,-15.4,30.9 --3,29.8,-14.9,30 --3,28.9,-14.5,29.1 --3,28,-14,28.2 --3,27.1,-13.6,27.3 --3,26.2,-13.1,26.4 --3,25.3,-12.7,25.5 --3,24.4,-12.2,24.5 --3,23.5,-11.8,23.6 --3,22.6,-11.3,22.7 --3,21.7,-10.9,21.8 --3,20.8,-10.4,20.9 --3,19.8,-9.9,20 --3,18.9,-9.5,19.1 --3,18,-9,18.2 --3,17.1,-8.6,17.3 --3,16.2,-8.1,16.4 --3,15.3,-7.7,15.5 --3,14.4,-7.2,14.5 --3,13.5,-6.8,13.6 --3,12.6,-6.3,12.7 --3,11.7,-5.9,11.8 --3,10.8,-5.4,10.9 --3,9.9,-5,10 --3,9,-4.5,9.1 --3,8.1,-4.1,8.2 --3,7.2,-3.6,7.3 --3,6.3,-3.2,6.4 --3,5.4,-2.7,5.5 --3,4.5,-2.3,4.5 --3,3.6,-1.8,3.6 --3,2.7,-1.4,2.7 --3,1.8,-0.9,1.8 --3,0.9,-0.5,0.9 --3,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -0.9,0,0,0.3 -0.9,0,0,0.3 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.8,0,0,0.5 -0.8,0,0,0.5 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.5,0,0,0.8 -0.5,0,0,0.8 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.3,0,0,0.9 -0.3,0,0,0.9 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,0.9 -0.3,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.8 -0.5,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.7,0,0,0.8 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.8,0,0,0.7 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.5 -0.8,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.3 -0.9,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 --3,0,0,0 --3,0.9,-0.5,0.9 --3,1.8,-0.9,1.8 --3,2.7,-1.4,2.7 --3,3.6,-1.8,3.6 --3,4.5,-2.3,4.5 --3,5.4,-2.7,5.5 --3,6.3,-3.2,6.4 --3,7.2,-3.6,7.3 --3,8.1,-4.1,8.2 --3,9,-4.5,9.1 --3,9.9,-5,10 --3,10.8,-5.4,10.9 --3,11.7,-5.9,11.8 --3,12.6,-6.3,12.7 --3,13.5,-6.8,13.6 --3,14.4,-7.2,14.5 --3,15.3,-7.7,15.5 --3,16.2,-8.1,16.4 --3,17.1,-8.6,17.3 --3,18,-9,18.2 --3,18.9,-9.5,19.1 --3,19.8,-9.9,20 --3,20.8,-10.4,20.9 --3,21.7,-10.9,21.8 --3,22.6,-11.3,22.7 --3,23.5,-11.8,23.6 --3,24.4,-12.2,24.5 --3,25.3,-12.7,25.5 --3,26.2,-13.1,26.4 --3,27.1,-13.6,27.3 --3,28,-14,28.2 --3,28.9,-14.5,29.1 --3,29.8,-14.9,30 --3,30.7,-15.4,30.9 --3,31.6,-15.8,31.8 --3,32.5,-16.3,32.7 --3,33.4,-16.7,33.6 --3,34.3,-17.2,34.5 --3,35.2,-17.6,35.5 --3,36.1,-18.1,36.4 --3,37,-18.5,37.3 --3,37.9,-19,38.2 --3,38.8,-19.4,39.1 --3,39.7,-19.9,40 --3,40.6,-20.4,40.9 --3,41.5,-20.8,41.8 --3,42.4,-21.3,42.7 --3,43.3,-21.7,43.6 --3,44.2,-22.2,44.5 --3,45.1,-22.6,45.5 --3,46,-23.1,46.4 --3,46.9,-23.5,47.3 --3,47.8,-24,48.2 --3,48.7,-24.4,49.1 --3,49.6,-24.9,50 --3,50.5,-25.3,50.9 --3,51.4,-25.8,51.8 --3,52.3,-26.2,52.7 --3,53.2,-26.7,53.6 --3,54.1,-27.1,54.5 --3,55,-27.6,55.5 --3,55.9,-28,56.4 --3,56.8,-28.5,57.3 --3,57.7,-28.9,58.2 --3,58.6,-29.4,59.1 --3,59.5,-29.8,60 --3,60.5,-30.3,60.9 --3,61.4,-30.8,61.8 --3,62.3,-31.2,62.7 --3,63.2,-31.7,63.6 --3,64.1,-32.1,64.5 --3,65,-32.6,65.5 --3,65.9,-33,66.4 --3,66.8,-33.5,67.3 --3,67.7,-33.9,68.2 --3,68.6,-34.4,69.1 --3,69.5,-34.8,70 --3,70.4,-35.3,70.9 --3,71.3,-35.7,71.8 --3,72.2,-36.2,72.7 --3,73.1,-36.6,73.6 --3,74,-37.1,74.5 --3,74.9,-37.5,75.5 --3,75.8,-38,76.4 --3,76.7,-38.4,77.3 --3,77.6,-38.9,78.2 --3,78.5,-39.3,79.1 --3,79.4,-39.8,80 --3,80.3,-40.3,80.9 --3,81.2,-40.7,81.8 --3,82.1,-41.2,82.7 --3,83,-41.6,83.6 --3,83.9,-42.1,84.5 --3,84.8,-42.5,85.5 --3,85.7,-43,86.4 --3,86.6,-43.4,87.3 --3,87.5,-43.9,88.2 --3,88.4,-44.3,89.1 --3,89.3,-44.8,90 --3,90.2,-45.2,0 --3,91.1,-45.7,0 --3,92,-46.1,0 --3,92.9,-46.6,0 --3,93.8,-47,0 --3,94.7,-47.5,0 --3,95.6,-47.9,0 --3,96.5,-48.4,0 --3,97.4,-48.8,0 --3,98.3,-49.3,0 --3,99.2,-49.7,0 --3,100.2,-50.2,0 --3,101.1,-50.7,0 --3,102,-51.1,0 --3,102.9,-51.6,0 --3,103.8,-52,0 --3,104.7,-52.5,0 --3,105.6,-52.9,0 --3,106.5,-53.4,0 --3,107.4,-53.8,0 --3,108.3,-54.3,0 --3,109.2,-54.7,0 --3,110.1,-55.2,0 --3,111,-55.6,0 --3,111.9,-56.1,0 --3,112.8,-56.5,0 --3,113.7,-57,0 --3,114.6,-57.4,0 --3,115.5,-57.9,0 --3,116.4,-58.3,0 --3,117.3,-58.8,0 --3,118.2,-59.2,0 --3,119.1,-59.7,0 --3,120,-60.2,0 --3,120.9,-60.6,0 --3,121.8,-61.1,0 --3,122.7,-61.5,0 --3,123.6,-62,0 --3,124.5,-62.4,0 --3,125.4,-62.9,0 --3,126.3,-63.3,0 --3,127.2,-63.8,0 --3,128.1,-64.2,0 --3,129,-64.7,0 --3,129.9,-65.1,0 --3,130.8,-65.6,0 --3,131.7,-66,0 --3,132.6,-66.5,0 --3,133.5,-66.9,0 --3,134.4,-67.4,0 --3,135.3,-67.8,0 --3,136.2,-68.3,0 --3,137.1,-68.7,0 --3,138,-69.2,0 --3,138.9,-69.6,0 --3,139.8,-70.1,0 --3,140.8,-70.6,0 --3,141.7,-71,0 --3,142.6,-71.5,0 --3,143.5,-71.9,0 --3,144.4,-72.4,0 --3,145.3,-72.8,0 --3,146.2,-73.3,0 --3,147.1,-73.7,0 --3,148,-74.2,0 --3,148.9,-74.6,0 --3,149.8,-75.1,0 --3,150.7,-75.5,0 --3,151.6,-76,0 --3,152.5,-76.4,0 --3,153.4,-76.9,0 --3,154.3,-77.3,0 --3,155.2,-77.8,0 --3,156.1,-78.2,0 --3,157,-78.7,0 --3,157.9,-79.1,0 --3,158.8,-79.6,0 --3,159.7,-80.1,0 --3,160.6,-80.5,0 --3,161.5,-81,0 --3,162.4,-81.4,0 --3,163.3,-81.9,0 --3,164.2,-82.3,0 --3,165.1,-82.8,0 --3,166,-83.2,0 --3,166.9,-83.7,0 --3,167.8,-84.1,0 --3,168.7,-84.6,0 --3,169.6,-85,0 --3,170.5,-85.5,0 --3,171.4,-85.9,0 --3,172.3,-86.4,0 --3,173.2,-86.8,0 --3,174.1,-87.3,0 --3,175,-87.7,0 --3,175.9,-88.2,0 --3,176.8,-88.6,0 --3,177.7,-89.1,0 --3,178.6,-89.5,0 --3,179.5,-90,0 --3,180.5,0,0 --3,181.4,0,0 --3,182.3,0,0 --3,183.2,0,0 --3,184.1,0,0 --3,185,0,0 --3,185.9,0,0 --3,186.8,0,0 --3,187.7,0,0 --3,188.6,0,0 --3,189.5,0,0 --3,190.4,0,0 --3,191.3,0,0 --3,192.2,0,0 --3,193.1,0,0 --3,194,0,0 --3,194.9,0,0 --3,195.8,0,0 --3,196.7,0,0 --3,197.6,0,0 --3,198.5,0,0 --3,199.4,0,0 --3,200.3,0,0 --3,201.2,0,0 --3,202.1,0,0 --3,203,0,0 --3,203.9,0,0 --3,204.8,0,0 --3,205.7,0,0 --3,206.6,0,0 --3,207.5,0,0 --3,208.4,0,0 --3,209.3,0,0 --3,210.2,0,0 --3,211.1,0,0 --3,212,0,0 --3,212.9,0,0 --3,213.8,0,0 --3,214.7,0,0 --3,215.6,0,0 --3,216.5,0,0 --3,217.4,0,0 --3,218.3,0,0 --3,219.2,0,0 --3,220.2,0,0 --3,221.1,0,0 --3,222,0,0 --3,222.9,0,0 --3,223.8,0,0 --3,224.7,0,0 --3,225.6,0,0 --3,226.5,0,0 --3,227.4,0,0 --3,228.3,0,0 --3,229.2,0,0 --3,230.1,0,0 --3,231,0,0 --3,231.9,0,0 --3,232.8,0,0 --3,233.7,0,0 --3,234.6,0,0 --3,235.5,0,0 --3,236.4,0,0 --3,237.3,0,0 --3,238.2,0,0 --3,239.1,0,0 --3,240,0,0 --3,240.9,0,0 --3,241.8,0,0 --3,242.7,0,0 --3,243.6,0,0 --3,244.5,0,0 --3,245.4,0,0 --3,246.3,0,0 --3,247.2,0,0 --3,248.1,0,0 --3,249,0,0 --3,249.9,0,0 --3,250.8,0,0 --3,251.7,0,0 --3,252.6,0,0 --3,253.5,0,0 --3,254.4,0,0 --3,255.3,0,0 --3,256.2,0,0 --3,257.1,0,0 --3,258,0,0 --3,258.9,0,0 --3,259.8,0,0 --3,260.8,0,0 --3,261.7,0,0 --3,262.6,0,0 --3,263.5,0,0 --3,264.4,0,0 --3,265.3,0,0 --3,266.2,0,0 --3,267.1,0,0 --3,268,0,0 --3,268.9,0,0 --3,269.8,0,0 --3,270.7,0,0 --3,271.6,0,0 --3,272.5,0,0 --3,273.4,0,0 --3,274.3,0,0 --3,275.2,0,0 --3,276.1,0,0 --3,277,0,0 --3,277.9,0,0 --3,278.8,0,0 --3,279.7,0,0 --3,280.6,0,0 --3,281.5,0,0 --3,282.4,0,0 --3,283.3,0,0 --3,284.2,0,0 --3,285.1,0,0 --3,286,0,0 --3,286.9,0,0 --3,287.8,0,0 --3,288.7,0,0 --3,289.6,0,0 --3,290.5,0,0 --3,291.4,0,0 --3,292.3,0,0 --3,293.2,0,0 --3,294.1,0,0 --3,295,0,0 --3,295.9,0,0 --3,296.8,0,0 --3,297.7,0,0 --3,298.6,0,0 --3,299.5,0,0 --3,300.5,0,0 --3,301.4,0,0 --3,302.3,0,0 --3,303.2,0,0 --3,304.1,0,0 --3,305,0,0 --3,305.9,0,0 --3,306.8,0,0 --3,307.7,0,0 --3,308.6,0,0 --3,309.5,0,0 --3,310.4,0,0 --3,311.3,0,0 --3,312.2,0,0 --3,313.1,0,0 --3,314,0,0 --3,314.9,0,0 --3,315.8,0,0 --3,316.7,0,0 --3,317.6,0,0 --3,318.5,0,0 --3,319.4,0,0 --3,320.3,0,0 --3,321.2,0,0 --3,322.1,0,0 --3,323,0,0 --3,323.9,0,0 --3,324.8,0,0 --3,325.7,0,0 --3,326.6,0,0 --3,327.5,0,0 --3,328.4,0,0 --3,329.3,0,0 --3,330.2,0,0 --3,331.1,0,0 --3,332,0,0 --3,332.9,0,0 --3,333.8,0,0 --3,334.7,0,0 --3,335.6,0,0 --3,336.5,0,0 --3,337.4,0,0 --3,338.3,0,0 --3,339.2,0,0 --3,340.2,0,0 --3,341.1,0,0 --3,342,0,0 --3,342.9,0,0 --3,343.8,0,0 --3,344.7,0,0 --3,345.6,0,0 --3,346.5,0,0 --3,347.4,0,0 --3,348.3,0,0 --3,349.2,0,0 --3,350.1,0,0 --3,351,0,0 --3,351.9,0,0 --3,352.8,0,0 --3,353.7,0,0 --3,354.6,0,0 --3,355.5,0,0 --3,356.4,0,0 --3,357.3,0,0 --3,358.2,0,0 --3,359.1,0,0 --3,360,0,0 diff --git a/scripts/trajectories/rotate_euler_quaternion_5s_delayed.csv b/scripts/trajectories/rotate_euler_quaternion_5s_delayed.csv deleted file mode 100644 index 99c54e3c52..0000000000 --- a/scripts/trajectories/rotate_euler_quaternion_5s_delayed.csv +++ /dev/null @@ -1,1020 +0,0 @@ --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,360,0,0 --3,359.1,0,0 --3,358.2,0,0 --3,357.3,0,0 --3,356.4,0,0 --3,355.5,0,0 --3,354.6,0,0 --3,353.7,0,0 --3,352.8,0,0 --3,351.9,0,0 --3,351,0,0 --3,350.1,0,0 --3,349.2,0,0 --3,348.3,0,0 --3,347.4,0,0 --3,346.5,0,0 --3,345.6,0,0 --3,344.7,0,0 --3,343.8,0,0 --3,342.9,0,0 --3,342,0,0 --3,341.1,0,0 --3,340.2,0,0 --3,339.2,0,0 --3,338.3,0,0 --3,337.4,0,0 --3,336.5,0,0 --3,335.6,0,0 --3,334.7,0,0 --3,333.8,0,0 --3,332.9,0,0 --3,332,0,0 --3,331.1,0,0 --3,330.2,0,0 --3,329.3,0,0 --3,328.4,0,0 --3,327.5,0,0 --3,326.6,0,0 --3,325.7,0,0 --3,324.8,0,0 --3,323.9,0,0 --3,323,0,0 --3,322.1,0,0 --3,321.2,0,0 --3,320.3,0,0 --3,319.4,0,0 --3,318.5,0,0 --3,317.6,0,0 --3,316.7,0,0 --3,315.8,0,0 --3,314.9,0,0 --3,314,0,0 --3,313.1,0,0 --3,312.2,0,0 --3,311.3,0,0 --3,310.4,0,0 --3,309.5,0,0 --3,308.6,0,0 --3,307.7,0,0 --3,306.8,0,0 --3,305.9,0,0 --3,305,0,0 --3,304.1,0,0 --3,303.2,0,0 --3,302.3,0,0 --3,301.4,0,0 --3,300.5,0,0 --3,299.5,0,0 --3,298.6,0,0 --3,297.7,0,0 --3,296.8,0,0 --3,295.9,0,0 --3,295,0,0 --3,294.1,0,0 --3,293.2,0,0 --3,292.3,0,0 --3,291.4,0,0 --3,290.5,0,0 --3,289.6,0,0 --3,288.7,0,0 --3,287.8,0,0 --3,286.9,0,0 --3,286,0,0 --3,285.1,0,0 --3,284.2,0,0 --3,283.3,0,0 --3,282.4,0,0 --3,281.5,0,0 --3,280.6,0,0 --3,279.7,0,0 --3,278.8,0,0 --3,277.9,0,0 --3,277,0,0 --3,276.1,0,0 --3,275.2,0,0 --3,274.3,0,0 --3,273.4,0,0 --3,272.5,0,0 --3,271.6,0,0 --3,270.7,0,0 --3,269.8,0,0 --3,268.9,0,0 --3,268,0,0 --3,267.1,0,0 --3,266.2,0,0 --3,265.3,0,0 --3,264.4,0,0 --3,263.5,0,0 --3,262.6,0,0 --3,261.7,0,0 --3,260.8,0,0 --3,259.8,0,0 --3,258.9,0,0 --3,258,0,0 --3,257.1,0,0 --3,256.2,0,0 --3,255.3,0,0 --3,254.4,0,0 --3,253.5,0,0 --3,252.6,0,0 --3,251.7,0,0 --3,250.8,0,0 --3,249.9,0,0 --3,249,0,0 --3,248.1,0,0 --3,247.2,0,0 --3,246.3,0,0 --3,245.4,0,0 --3,244.5,0,0 --3,243.6,0,0 --3,242.7,0,0 --3,241.8,0,0 --3,240.9,0,0 --3,240,0,0 --3,239.1,0,0 --3,238.2,0,0 --3,237.3,0,0 --3,236.4,0,0 --3,235.5,0,0 --3,234.6,0,0 --3,233.7,0,0 --3,232.8,0,0 --3,231.9,0,0 --3,231,0,0 --3,230.1,0,0 --3,229.2,0,0 --3,228.3,0,0 --3,227.4,0,0 --3,226.5,0,0 --3,225.6,0,0 --3,224.7,0,0 --3,223.8,0,0 --3,222.9,0,0 --3,222,0,0 --3,221.1,0,0 --3,220.2,0,0 --3,219.2,0,0 --3,218.3,0,0 --3,217.4,0,0 --3,216.5,0,0 --3,215.6,0,0 --3,214.7,0,0 --3,213.8,0,0 --3,212.9,0,0 --3,212,0,0 --3,211.1,0,0 --3,210.2,0,0 --3,209.3,0,0 --3,208.4,0,0 --3,207.5,0,0 --3,206.6,0,0 --3,205.7,0,0 --3,204.8,0,0 --3,203.9,0,0 --3,203,0,0 --3,202.1,0,0 --3,201.2,0,0 --3,200.3,0,0 --3,199.4,0,0 --3,198.5,0,0 --3,197.6,0,0 --3,196.7,0,0 --3,195.8,0,0 --3,194.9,0,0 --3,194,0,0 --3,193.1,0,0 --3,192.2,0,0 --3,191.3,0,0 --3,190.4,0,0 --3,189.5,0,0 --3,188.6,0,0 --3,187.7,0,0 --3,186.8,0,0 --3,185.9,0,0 --3,185,0,0 --3,184.1,0,0 --3,183.2,0,0 --3,182.3,0,0 --3,181.4,0,0 --3,180.5,0,0 --3,179.5,-90,0 --3,178.6,-89.5,0 --3,177.7,-89.1,0 --3,176.8,-88.6,0 --3,175.9,-88.2,0 --3,175,-87.7,0 --3,174.1,-87.3,0 --3,173.2,-86.8,0 --3,172.3,-86.4,0 --3,171.4,-85.9,0 --3,170.5,-85.5,0 --3,169.6,-85,0 --3,168.7,-84.6,0 --3,167.8,-84.1,0 --3,166.9,-83.7,0 --3,166,-83.2,0 --3,165.1,-82.8,0 --3,164.2,-82.3,0 --3,163.3,-81.9,0 --3,162.4,-81.4,0 --3,161.5,-81,0 --3,160.6,-80.5,0 --3,159.7,-80.1,0 --3,158.8,-79.6,0 --3,157.9,-79.1,0 --3,157,-78.7,0 --3,156.1,-78.2,0 --3,155.2,-77.8,0 --3,154.3,-77.3,0 --3,153.4,-76.9,0 --3,152.5,-76.4,0 --3,151.6,-76,0 --3,150.7,-75.5,0 --3,149.8,-75.1,0 --3,148.9,-74.6,0 --3,148,-74.2,0 --3,147.1,-73.7,0 --3,146.2,-73.3,0 --3,145.3,-72.8,0 --3,144.4,-72.4,0 --3,143.5,-71.9,0 --3,142.6,-71.5,0 --3,141.7,-71,0 --3,140.8,-70.6,0 --3,139.8,-70.1,0 --3,138.9,-69.6,0 --3,138,-69.2,0 --3,137.1,-68.7,0 --3,136.2,-68.3,0 --3,135.3,-67.8,0 --3,134.4,-67.4,0 --3,133.5,-66.9,0 --3,132.6,-66.5,0 --3,131.7,-66,0 --3,130.8,-65.6,0 --3,129.9,-65.1,0 --3,129,-64.7,0 --3,128.1,-64.2,0 --3,127.2,-63.8,0 --3,126.3,-63.3,0 --3,125.4,-62.9,0 --3,124.5,-62.4,0 --3,123.6,-62,0 --3,122.7,-61.5,0 --3,121.8,-61.1,0 --3,120.9,-60.6,0 --3,120,-60.2,0 --3,119.1,-59.7,0 --3,118.2,-59.2,0 --3,117.3,-58.8,0 --3,116.4,-58.3,0 --3,115.5,-57.9,0 --3,114.6,-57.4,0 --3,113.7,-57,0 --3,112.8,-56.5,0 --3,111.9,-56.1,0 --3,111,-55.6,0 --3,110.1,-55.2,0 --3,109.2,-54.7,0 --3,108.3,-54.3,0 --3,107.4,-53.8,0 --3,106.5,-53.4,0 --3,105.6,-52.9,0 --3,104.7,-52.5,0 --3,103.8,-52,0 --3,102.9,-51.6,0 --3,102,-51.1,0 --3,101.1,-50.7,0 --3,100.2,-50.2,0 --3,99.2,-49.7,0 --3,98.3,-49.3,0 --3,97.4,-48.8,0 --3,96.5,-48.4,0 --3,95.6,-47.9,0 --3,94.7,-47.5,0 --3,93.8,-47,0 --3,92.9,-46.6,0 --3,92,-46.1,0 --3,91.1,-45.7,0 --3,90.2,-45.2,0 --3,89.3,-44.8,90 --3,88.4,-44.3,89.1 --3,87.5,-43.9,88.2 --3,86.6,-43.4,87.3 --3,85.7,-43,86.4 --3,84.8,-42.5,85.5 --3,83.9,-42.1,84.5 --3,83,-41.6,83.6 --3,82.1,-41.2,82.7 --3,81.2,-40.7,81.8 --3,80.3,-40.3,80.9 --3,79.4,-39.8,80 --3,78.5,-39.3,79.1 --3,77.6,-38.9,78.2 --3,76.7,-38.4,77.3 --3,75.8,-38,76.4 --3,74.9,-37.5,75.5 --3,74,-37.1,74.5 --3,73.1,-36.6,73.6 --3,72.2,-36.2,72.7 --3,71.3,-35.7,71.8 --3,70.4,-35.3,70.9 --3,69.5,-34.8,70 --3,68.6,-34.4,69.1 --3,67.7,-33.9,68.2 --3,66.8,-33.5,67.3 --3,65.9,-33,66.4 --3,65,-32.6,65.5 --3,64.1,-32.1,64.5 --3,63.2,-31.7,63.6 --3,62.3,-31.2,62.7 --3,61.4,-30.8,61.8 --3,60.5,-30.3,60.9 --3,59.5,-29.8,60 --3,58.6,-29.4,59.1 --3,57.7,-28.9,58.2 --3,56.8,-28.5,57.3 --3,55.9,-28,56.4 --3,55,-27.6,55.5 --3,54.1,-27.1,54.5 --3,53.2,-26.7,53.6 --3,52.3,-26.2,52.7 --3,51.4,-25.8,51.8 --3,50.5,-25.3,50.9 --3,49.6,-24.9,50 --3,48.7,-24.4,49.1 --3,47.8,-24,48.2 --3,46.9,-23.5,47.3 --3,46,-23.1,46.4 --3,45.1,-22.6,45.5 --3,44.2,-22.2,44.5 --3,43.3,-21.7,43.6 --3,42.4,-21.3,42.7 --3,41.5,-20.8,41.8 --3,40.6,-20.4,40.9 --3,39.7,-19.9,40 --3,38.8,-19.4,39.1 --3,37.9,-19,38.2 --3,37,-18.5,37.3 --3,36.1,-18.1,36.4 --3,35.2,-17.6,35.5 --3,34.3,-17.2,34.5 --3,33.4,-16.7,33.6 --3,32.5,-16.3,32.7 --3,31.6,-15.8,31.8 --3,30.7,-15.4,30.9 --3,29.8,-14.9,30 --3,28.9,-14.5,29.1 --3,28,-14,28.2 --3,27.1,-13.6,27.3 --3,26.2,-13.1,26.4 --3,25.3,-12.7,25.5 --3,24.4,-12.2,24.5 --3,23.5,-11.8,23.6 --3,22.6,-11.3,22.7 --3,21.7,-10.9,21.8 --3,20.8,-10.4,20.9 --3,19.8,-9.9,20 --3,18.9,-9.5,19.1 --3,18,-9,18.2 --3,17.1,-8.6,17.3 --3,16.2,-8.1,16.4 --3,15.3,-7.7,15.5 --3,14.4,-7.2,14.5 --3,13.5,-6.8,13.6 --3,12.6,-6.3,12.7 --3,11.7,-5.9,11.8 --3,10.8,-5.4,10.9 --3,9.9,-5,10 --3,9,-4.5,9.1 --3,8.1,-4.1,8.2 --3,7.2,-3.6,7.3 --3,6.3,-3.2,6.4 --3,5.4,-2.7,5.5 --3,4.5,-2.3,4.5 --3,3.6,-1.8,3.6 --3,2.7,-1.4,2.7 --3,1.8,-0.9,1.8 --3,0.9,-0.5,0.9 --3,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -0.9,0,0,0.3 -0.9,0,0,0.3 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.8,0,0,0.5 -0.8,0,0,0.5 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.5,0,0,0.8 -0.5,0,0,0.8 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.3,0,0,0.9 -0.3,0,0,0.9 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.1,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.2,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,1 -0.3,0,0,0.9 -0.3,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.4,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.9 -0.5,0,0,0.8 -0.5,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.6,0,0,0.8 -0.7,0,0,0.8 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.7,0,0,0.7 -0.8,0,0,0.7 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.6 -0.8,0,0,0.5 -0.8,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.5 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.4 -0.9,0,0,0.3 -0.9,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.3 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.2 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0.1 -1,0,0,0 -1,0,0,0 -1,0,0,0 -1,0,0,0 --3,0,0,0 --3,0.9,-0.5,0.9 --3,1.8,-0.9,1.8 --3,2.7,-1.4,2.7 --3,3.6,-1.8,3.6 --3,4.5,-2.3,4.5 --3,5.4,-2.7,5.5 --3,6.3,-3.2,6.4 --3,7.2,-3.6,7.3 --3,8.1,-4.1,8.2 --3,9,-4.5,9.1 --3,9.9,-5,10 --3,10.8,-5.4,10.9 --3,11.7,-5.9,11.8 --3,12.6,-6.3,12.7 --3,13.5,-6.8,13.6 --3,14.4,-7.2,14.5 --3,15.3,-7.7,15.5 --3,16.2,-8.1,16.4 --3,17.1,-8.6,17.3 --3,18,-9,18.2 --3,18.9,-9.5,19.1 --3,19.8,-9.9,20 --3,20.8,-10.4,20.9 --3,21.7,-10.9,21.8 --3,22.6,-11.3,22.7 --3,23.5,-11.8,23.6 --3,24.4,-12.2,24.5 --3,25.3,-12.7,25.5 --3,26.2,-13.1,26.4 --3,27.1,-13.6,27.3 --3,28,-14,28.2 --3,28.9,-14.5,29.1 --3,29.8,-14.9,30 --3,30.7,-15.4,30.9 --3,31.6,-15.8,31.8 --3,32.5,-16.3,32.7 --3,33.4,-16.7,33.6 --3,34.3,-17.2,34.5 --3,35.2,-17.6,35.5 --3,36.1,-18.1,36.4 --3,37,-18.5,37.3 --3,37.9,-19,38.2 --3,38.8,-19.4,39.1 --3,39.7,-19.9,40 --3,40.6,-20.4,40.9 --3,41.5,-20.8,41.8 --3,42.4,-21.3,42.7 --3,43.3,-21.7,43.6 --3,44.2,-22.2,44.5 --3,45.1,-22.6,45.5 --3,46,-23.1,46.4 --3,46.9,-23.5,47.3 --3,47.8,-24,48.2 --3,48.7,-24.4,49.1 --3,49.6,-24.9,50 --3,50.5,-25.3,50.9 --3,51.4,-25.8,51.8 --3,52.3,-26.2,52.7 --3,53.2,-26.7,53.6 --3,54.1,-27.1,54.5 --3,55,-27.6,55.5 --3,55.9,-28,56.4 --3,56.8,-28.5,57.3 --3,57.7,-28.9,58.2 --3,58.6,-29.4,59.1 --3,59.5,-29.8,60 --3,60.5,-30.3,60.9 --3,61.4,-30.8,61.8 --3,62.3,-31.2,62.7 --3,63.2,-31.7,63.6 --3,64.1,-32.1,64.5 --3,65,-32.6,65.5 --3,65.9,-33,66.4 --3,66.8,-33.5,67.3 --3,67.7,-33.9,68.2 --3,68.6,-34.4,69.1 --3,69.5,-34.8,70 --3,70.4,-35.3,70.9 --3,71.3,-35.7,71.8 --3,72.2,-36.2,72.7 --3,73.1,-36.6,73.6 --3,74,-37.1,74.5 --3,74.9,-37.5,75.5 --3,75.8,-38,76.4 --3,76.7,-38.4,77.3 --3,77.6,-38.9,78.2 --3,78.5,-39.3,79.1 --3,79.4,-39.8,80 --3,80.3,-40.3,80.9 --3,81.2,-40.7,81.8 --3,82.1,-41.2,82.7 --3,83,-41.6,83.6 --3,83.9,-42.1,84.5 --3,84.8,-42.5,85.5 --3,85.7,-43,86.4 --3,86.6,-43.4,87.3 --3,87.5,-43.9,88.2 --3,88.4,-44.3,89.1 --3,89.3,-44.8,90 --3,90.2,-45.2,0 --3,91.1,-45.7,0 --3,92,-46.1,0 --3,92.9,-46.6,0 --3,93.8,-47,0 --3,94.7,-47.5,0 --3,95.6,-47.9,0 --3,96.5,-48.4,0 --3,97.4,-48.8,0 --3,98.3,-49.3,0 --3,99.2,-49.7,0 --3,100.2,-50.2,0 --3,101.1,-50.7,0 --3,102,-51.1,0 --3,102.9,-51.6,0 --3,103.8,-52,0 --3,104.7,-52.5,0 --3,105.6,-52.9,0 --3,106.5,-53.4,0 --3,107.4,-53.8,0 --3,108.3,-54.3,0 --3,109.2,-54.7,0 --3,110.1,-55.2,0 --3,111,-55.6,0 --3,111.9,-56.1,0 --3,112.8,-56.5,0 --3,113.7,-57,0 --3,114.6,-57.4,0 --3,115.5,-57.9,0 --3,116.4,-58.3,0 --3,117.3,-58.8,0 --3,118.2,-59.2,0 --3,119.1,-59.7,0 --3,120,-60.2,0 --3,120.9,-60.6,0 --3,121.8,-61.1,0 --3,122.7,-61.5,0 --3,123.6,-62,0 --3,124.5,-62.4,0 --3,125.4,-62.9,0 --3,126.3,-63.3,0 --3,127.2,-63.8,0 --3,128.1,-64.2,0 --3,129,-64.7,0 --3,129.9,-65.1,0 --3,130.8,-65.6,0 --3,131.7,-66,0 --3,132.6,-66.5,0 --3,133.5,-66.9,0 --3,134.4,-67.4,0 --3,135.3,-67.8,0 --3,136.2,-68.3,0 --3,137.1,-68.7,0 --3,138,-69.2,0 --3,138.9,-69.6,0 --3,139.8,-70.1,0 --3,140.8,-70.6,0 --3,141.7,-71,0 --3,142.6,-71.5,0 --3,143.5,-71.9,0 --3,144.4,-72.4,0 --3,145.3,-72.8,0 --3,146.2,-73.3,0 --3,147.1,-73.7,0 --3,148,-74.2,0 --3,148.9,-74.6,0 --3,149.8,-75.1,0 --3,150.7,-75.5,0 --3,151.6,-76,0 --3,152.5,-76.4,0 --3,153.4,-76.9,0 --3,154.3,-77.3,0 --3,155.2,-77.8,0 --3,156.1,-78.2,0 --3,157,-78.7,0 --3,157.9,-79.1,0 --3,158.8,-79.6,0 --3,159.7,-80.1,0 --3,160.6,-80.5,0 --3,161.5,-81,0 --3,162.4,-81.4,0 --3,163.3,-81.9,0 --3,164.2,-82.3,0 --3,165.1,-82.8,0 --3,166,-83.2,0 --3,166.9,-83.7,0 --3,167.8,-84.1,0 --3,168.7,-84.6,0 --3,169.6,-85,0 --3,170.5,-85.5,0 --3,171.4,-85.9,0 --3,172.3,-86.4,0 --3,173.2,-86.8,0 --3,174.1,-87.3,0 --3,175,-87.7,0 --3,175.9,-88.2,0 --3,176.8,-88.6,0 --3,177.7,-89.1,0 --3,178.6,-89.5,0 --3,179.5,-90,0 --3,180.5,0,0 --3,181.4,0,0 --3,182.3,0,0 --3,183.2,0,0 --3,184.1,0,0 --3,185,0,0 --3,185.9,0,0 --3,186.8,0,0 --3,187.7,0,0 --3,188.6,0,0 --3,189.5,0,0 --3,190.4,0,0 --3,191.3,0,0 --3,192.2,0,0 --3,193.1,0,0 --3,194,0,0 --3,194.9,0,0 --3,195.8,0,0 --3,196.7,0,0 --3,197.6,0,0 --3,198.5,0,0 --3,199.4,0,0 --3,200.3,0,0 --3,201.2,0,0 --3,202.1,0,0 --3,203,0,0 --3,203.9,0,0 --3,204.8,0,0 --3,205.7,0,0 --3,206.6,0,0 --3,207.5,0,0 --3,208.4,0,0 --3,209.3,0,0 --3,210.2,0,0 --3,211.1,0,0 --3,212,0,0 --3,212.9,0,0 --3,213.8,0,0 --3,214.7,0,0 --3,215.6,0,0 --3,216.5,0,0 --3,217.4,0,0 --3,218.3,0,0 --3,219.2,0,0 --3,220.2,0,0 --3,221.1,0,0 --3,222,0,0 --3,222.9,0,0 --3,223.8,0,0 --3,224.7,0,0 --3,225.6,0,0 --3,226.5,0,0 --3,227.4,0,0 --3,228.3,0,0 --3,229.2,0,0 --3,230.1,0,0 --3,231,0,0 --3,231.9,0,0 --3,232.8,0,0 --3,233.7,0,0 --3,234.6,0,0 --3,235.5,0,0 --3,236.4,0,0 --3,237.3,0,0 --3,238.2,0,0 --3,239.1,0,0 --3,240,0,0 --3,240.9,0,0 --3,241.8,0,0 --3,242.7,0,0 --3,243.6,0,0 --3,244.5,0,0 --3,245.4,0,0 --3,246.3,0,0 --3,247.2,0,0 --3,248.1,0,0 --3,249,0,0 --3,249.9,0,0 --3,250.8,0,0 --3,251.7,0,0 --3,252.6,0,0 --3,253.5,0,0 --3,254.4,0,0 --3,255.3,0,0 --3,256.2,0,0 --3,257.1,0,0 --3,258,0,0 --3,258.9,0,0 --3,259.8,0,0 --3,260.8,0,0 --3,261.7,0,0 --3,262.6,0,0 --3,263.5,0,0 --3,264.4,0,0 --3,265.3,0,0 --3,266.2,0,0 --3,267.1,0,0 --3,268,0,0 --3,268.9,0,0 --3,269.8,0,0 --3,270.7,0,0 --3,271.6,0,0 --3,272.5,0,0 --3,273.4,0,0 --3,274.3,0,0 --3,275.2,0,0 --3,276.1,0,0 --3,277,0,0 --3,277.9,0,0 --3,278.8,0,0 --3,279.7,0,0 --3,280.6,0,0 --3,281.5,0,0 --3,282.4,0,0 --3,283.3,0,0 --3,284.2,0,0 --3,285.1,0,0 --3,286,0,0 --3,286.9,0,0 --3,287.8,0,0 --3,288.7,0,0 --3,289.6,0,0 --3,290.5,0,0 --3,291.4,0,0 --3,292.3,0,0 --3,293.2,0,0 --3,294.1,0,0 --3,295,0,0 --3,295.9,0,0 --3,296.8,0,0 --3,297.7,0,0 --3,298.6,0,0 --3,299.5,0,0 --3,300.5,0,0 --3,301.4,0,0 --3,302.3,0,0 --3,303.2,0,0 --3,304.1,0,0 --3,305,0,0 --3,305.9,0,0 --3,306.8,0,0 --3,307.7,0,0 --3,308.6,0,0 --3,309.5,0,0 --3,310.4,0,0 --3,311.3,0,0 --3,312.2,0,0 --3,313.1,0,0 --3,314,0,0 --3,314.9,0,0 --3,315.8,0,0 --3,316.7,0,0 --3,317.6,0,0 --3,318.5,0,0 --3,319.4,0,0 --3,320.3,0,0 --3,321.2,0,0 --3,322.1,0,0 --3,323,0,0 --3,323.9,0,0 --3,324.8,0,0 --3,325.7,0,0 --3,326.6,0,0 --3,327.5,0,0 --3,328.4,0,0 --3,329.3,0,0 --3,330.2,0,0 --3,331.1,0,0 --3,332,0,0 --3,332.9,0,0 --3,333.8,0,0 --3,334.7,0,0 --3,335.6,0,0 --3,336.5,0,0 --3,337.4,0,0 --3,338.3,0,0 --3,339.2,0,0 --3,340.2,0,0 --3,341.1,0,0 --3,342,0,0 --3,342.9,0,0 --3,343.8,0,0 --3,344.7,0,0 --3,345.6,0,0 --3,346.5,0,0 --3,347.4,0,0 --3,348.3,0,0 --3,349.2,0,0 --3,350.1,0,0 --3,351,0,0 --3,351.9,0,0 --3,352.8,0,0 --3,353.7,0,0 --3,354.6,0,0 --3,355.5,0,0 --3,356.4,0,0 --3,357.3,0,0 --3,358.2,0,0 --3,359.1,0,0 --3,360,0,0 diff --git a/tests/conftest.py b/tests/conftest.py index 5d3632a762..721933f47d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -128,6 +128,12 @@ def pytest_addoption(parser): help="If specified, use given binary as DUT decoder.", ) + parser.addoption( + "--dut_postrend_path", + action="store", + help="If specified, use given binary as DUT ISAR post-renderer.", + ) + parser.addoption( "--ref_encoder_path", action="store", @@ -140,6 +146,12 @@ def pytest_addoption(parser): help="If specified, use given binary as REF decoder.", ) + parser.addoption( + "--ref_postrend_path", + action="store", + help="If specified, use given binary as REF ISAR post-renderer.", + ) + parser.addoption( "--test_vector_path", action="store", @@ -1316,3 +1328,216 @@ def get_format_from_enc_opts(enc_opts: str) -> str: format = IVAS_ENC_FORMATS[enc_format_str] return format + + +@pytest.fixture(scope="session") +def dut_postrend_path(request) -> Optional[str]: + """ + Return path of DUT postrend binary. + """ + if request.config.option.dut_postrend_path: + return request.config.option.dut_postrend_path + + if request.config.option.update_ref == "1": + return None + + here = Path(__file__).parent.resolve() + system = platform.system() + + if system == "Windows": + path = here.joinpath("../ISAR_post_rend.exe") + elif system in ["Darwin", "Linux"]: + path = here.joinpath("../ISAR_post_rend") + else: + raise ValueError(f'Wrong system "{system}"!') + + path = str(path.resolve()) + + if not os.path.isfile(path): + raise FileNotFoundError(f"DUT postrend binary {path} not found!\n!") + + return path + + +class PostRendFrontend: + def __init__(self, path, postrend_type, timeout=None, fr=20) -> None: + self._path = Path(path).absolute() + self._type = postrend_type + self.returncode = None + self.stdout = None + self.stderr = None + self.timeout = timeout + self.fr = fr + + def run( + self, + output_sampling_rate: int, + input_path: Path, + output_path: Path, + head_trajectory: Path, + metadata_input_path: Optional[Path] = None, + quiet_mode: Optional[bool] = True, + bfi_file: Optional[Path] = None, + add_option_list: Optional[list] = None, + run_dir: Optional[Path] = None, + ) -> None: + command = [str(self._path)] + + # add optional parameters + if quiet_mode: + command.extend(["-q"]) + + if bfi_file is not None: + command.extend(["-prbfi", str(bfi_file)]) + + if add_option_list is not None: + command.extend(add_option_list) + + if metadata_input_path is not None: + # If we have metadata input file, then input format must be PCM + command.extend(["-if", "BINAURAL_SPLIT_PCM"]) + command.extend(["-im", str(metadata_input_path)]) + else: + command.extend(["-if", "BINAURAL_SPLIT_CODED"]) + + command.extend( + [ + "-fr", + str(self.fr), + "-fs", + str(output_sampling_rate), + "-i", + str(input_path), + "-o", + str(output_path), + "-T", + str(head_trajectory), + ] + ) + + cmd_str = textwrap.indent(" ".join(command), prefix="\t") + log_dbg_msg(f"{self._type} post-rend command:\n{cmd_str}") + + try: + with tempfile.TemporaryDirectory() as tmp_dir: + if run_dir is None: + cwd = Path(tmp_dir).absolute() + else: + cwd = Path(run_dir).absolute() + result = run( + command, + capture_output=True, + check=False, + timeout=self.timeout, + cwd=cwd, + ) + except TimeoutExpired: + pytest.fail(f"{self._type} post-rend run timed out after {self.timeout}s.") + + self.returncode = result.returncode + self.stderr = result.stderr.decode("ascii") + self.stdout = result.stdout.decode("ascii") + if self.stdout: + stdout_str = textwrap.indent(self.stdout, prefix="\t") + log_dbg_msg(f"{self._type} post-rend stdout:\n{stdout_str}") + if self.stderr: + stderr_str = textwrap.indent(self.stderr, prefix="\t") + log_dbg_msg(f"{self._type} post-rend stderr:\n{stderr_str}") + if self.returncode: + pytest.fail( + f"{self._type} post-rend terminated with a non-0 return code: {self.returncode}" + ) + if self.stderr and "UndefinedBehaviorSanitizer" in self.stderr: + pytest.fail("Undefined Behaviour runtime error encountered") + + def _check_run(self): + if self.returncode is not None: + if self.returncode: + pytest.fail( + f"{self._type} post-rend terminated with a non-0 return code: {self.returncode}" + ) + else: + logger.warning("%s post-rend was set-up, but not run", self._type) + # next assert is not OK since stderr contains messages even when decoding was successful + # assert not self.stderr, self._type + " decoder stderr is not empty" + + +@pytest.fixture(scope="function") +def dut_postrend_frontend(dut_postrend_path, request) -> Optional[PostRendFrontend]: + """ + Return a :class:`conftest.PostRendFrontend` instance as DUT for the test session. + """ + postrend = None + + if dut_postrend_path and request.node.funcargs["out_format"] in [ + "BINAURAL_SPLIT_CODED", + "BINAURAL_SPLIT_PCM", + ]: + timeout = request.config.getoption("--testcase_timeout") + postrend = PostRendFrontend( + dut_postrend_path, + "DUT", + timeout=timeout, + fr=request.config.option.dut_fr, + ) + + yield postrend + + # Fixture teardown + if postrend is not None: + postrend._check_run() + + +@pytest.fixture(scope="session") +def ref_postrend_path(request) -> Optional[str]: + """ + Return path of DUT postrend binary. + """ + if request.config.option.ref_postrend_path: + return request.config.option.ref_postrend_path + + if request.config.option.update_ref == "0": + return None + + here = Path(__file__).parent.resolve() + system = platform.system() + + if system == "Windows": + path = here.joinpath("../ISAR_post_rend_ref.exe") + elif system in ["Darwin", "Linux"]: + path = here.joinpath("../ISAR_post_rend_ref") + else: + raise ValueError(f'Wrong system "{system}"!') + + path = str(path.resolve()) + + if not os.path.isfile(path): + raise FileNotFoundError(f"REF postrend binary {path} not found!\n!") + + return path + + +@pytest.fixture(scope="function") +def ref_postrend_frontend(ref_postrend_path, request) -> Optional[PostRendFrontend]: + """ + Return a :class:`conftest.PostRendFrontend` instance as REF for the test session. + """ + postrend = None + + if ref_postrend_path and request.node.funcargs["out_format"] in [ + "BINAURAL_SPLIT_CODED", + "BINAURAL_SPLIT_PCM", + ]: + timeout = request.config.getoption("--testcase_timeout") + postrend = PostRendFrontend( + ref_postrend_path, + "REF", + timeout=timeout, + fr=request.config.option.dut_fr, + ) + + yield postrend + + # Fixture teardown + if postrend is not None: + postrend._check_run() diff --git a/tests/split_rendering/constants.py b/tests/split_rendering/constants.py index 5869ec3b78..8b3b53f86b 100644 --- a/tests/split_rendering/constants.py +++ b/tests/split_rendering/constants.py @@ -104,7 +104,7 @@ RENDERER_CONFIGS_TO_TEST_PLC = RENDERER_CONFIGS_FASTCONV_RENDERER + RENDERER_CON """ Trajectories """ SPLIT_REND_HR_TRAJECTORIES_TO_TEST = [ - "rotate_euler_quaternion_5s", + "rotate_euler_quaternion_30s", ] """ IVAS specific constants """ diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index a881a7ad23..f08d08cd4a 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -32,8 +32,18 @@ import pytest -from tests.split_rendering.utils import * +from tempfile import TemporaryDirectory +from pathlib import Path +import filecmp +from tests.split_rendering.utils import * +from tests.split_rendering.constants import SCRIPTS_DIR, TESTV_DIR +from tests.test_be_for_jbm_neutral_dly_profile import ( + INPUT_FILES, + get_options_cod, +) +from split_rendering.isar_bstool import IsarBitstream +from pyaudio3dtools import audioarray, audiofile """ Ambisonics """ @@ -585,3 +595,109 @@ def test_framing_combinations_full_chain_split( get_odg=get_odg, get_odg_bin=get_odg_bin, ) + + +IN_FORMATS = [ + "MC_5_1", + "ISM4", + "FOA", + "MASA2TC", + "OSBA_ISM3_HOA3", + "OMASA_ISM4", +] + +DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] + + +# Compares PCM output and tracefile from a VoIP BINAURAL_SPLIT_PCM chain with equivalent BINAURAL +# chain to ensure time-scaling and other JBM operations are the BE between the two. +@pytest.mark.parametrize("in_format", IN_FORMATS) +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) +def test_be_splitrend_vs_binaural( + in_format, + delay_profile, + dut_encoder_frontend, + dut_decoder_frontend, + bitrate=128000, +): + with TemporaryDirectory() as tmp_dir: + tmp_dir = Path(tmp_dir) + + sampling_rate_khz = 48 + delay_profile_path = SCRIPTS_DIR / "dly_error_profiles" / delay_profile + delay_profile_id = int(delay_profile[-5]) + + # run encoder + bitstream_file = (tmp_dir / f"{in_format}-dly{delay_profile_id}.192").absolute() + dtx = False + wav_in = TESTV_DIR / INPUT_FILES[in_format] + dut_encoder_frontend.run( + bitrate, + sampling_rate_khz, + wav_in, + bitstream_file, + add_option_list=get_options_cod(in_format, dtx), + run_dir=tmp_dir, + ) + + def run_decoder(out_format): + options = [] + + # Head trajectory must be static due to the slight time shift between audio outputs of BINAURAL/SPLIT_PCM + head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") + options.extend(["-T", str(head_traj)]) + + wav_out = ( + tmp_dir + / f"{in_format}-{bitrate}-{out_format}-dly{delay_profile_id}.wav" + ).absolute() + + trace_out = wav_out.with_suffix(".trace") + options.extend(["-Tracefile", str(trace_out)]) + + if out_format == "BINAURAL_SPLIT_PCM": + isar_md_file = wav_out.with_suffix(".isarmd") + options.extend(["-om", str(isar_md_file)]) + else: + isar_md_file = None + + dut_decoder_frontend.run( + out_format, + sampling_rate_khz, + bitstream_file, + wav_out, + netsim_profile=delay_profile_path, + add_option_list=options, + ) + + return wav_out, trace_out, isar_md_file + + wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") + wav_out_sr, trace_out_sr, isar_md_out_sr = run_decoder("BINAURAL_SPLIT_PCM") + + # Delay-align audio + assert isar_md_out_sr is not None + sr_delay_samples = IsarBitstream(isar_md_out_sr).delay_samples + audio_sr, _ = audiofile.readfile(str(wav_out_sr)) + audio_bin, _ = audiofile.readfile(str(wav_out_bin)) + audio_sr = audio_sr[sr_delay_samples:] + audio_bin = audio_bin[:-sr_delay_samples] + + # Ensure audio and tracefiles are BE + audio_cmp_result = audioarray.compare( + audio_bin, audio_sr, fs=sampling_rate_khz * 1000, per_frame=False + ) + tracefiles_equal = filecmp.cmp(trace_out_bin, trace_out_sr) + failed = not audio_cmp_result["bitexact"] or not tracefiles_equal + if failed: + message = [] + if not audio_cmp_result["bitexact"]: + message.append( + "Difference found between delay-aligned BINAURAL audio and BINAURAL_SPLIT_PCM audio! " + f"Max abs diff: {audio_cmp_result['max_abs_diff']}" + ) + if not tracefiles_equal: + message.append( + "Difference found between BINAURAL tracefile and BINAURAL_SPLIT_PCM tracefile!" + ) + pytest.fail("; ".join(message)) diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index ec67ab22a3..0e2b2420f1 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -4,12 +4,15 @@ import sys import re import numpy as np from tempfile import TemporaryDirectory +from pathlib import Path from .constants import TESTV_DIR, SCRIPTS_DIR +from .split_rendering.constants import HR_TRAJECTORY_DIR sys.path.append(str(SCRIPTS_DIR)) from pyaudio3dtools import audiofile, audioarray +from split_rendering.isar_bstool import IsarBitstream DTX_ON = "DTX_ON" DTX_OFF = "DTX_OFF" @@ -76,12 +79,29 @@ TESTCASES_NO_DTX = [ ["OSBA_ISM3_HOA3", 128000, "EXT"], ["OSBA_ISM2_HOA3", 96000, "5_1"], ["OSBA_ISM1_HOA2", 32000, "mono"], + # BINAURAL_SPLIT_PCM as output + ["ISM4", 128000, "BINAURAL_SPLIT_PCM"], + ["MC_5_1", 128000, "BINAURAL_SPLIT_PCM"], + ["FOA", 128000, "BINAURAL_SPLIT_PCM"], + ["OMASA_ISM2", 128000, "BINAURAL_SPLIT_PCM"], + # BINAURAL_SPLIT_CODED with LC3plus + ["MC_5_1", 128000, "BINAURAL_SPLIT_CODED"], + ["ISM4", 128000, "BINAURAL_SPLIT_CODED"], + # BINAURAL_SPLIT_CODED with LCLD + ["HOA3", 128000, "BINAURAL_SPLIT_CODED"], + ["OSBA_ISM4_FOA", 128000, "BINAURAL_SPLIT_CODED"], + ] DLY_PROFILE = SCRIPTS_DIR.joinpath("dly_error_profiles/dly_error_profile_0.dat") JBM_NEUTRAL_DELAY_MS = 60 -def get_options(in_format, dtx): +def is_split_rend(format) -> bool: + return format in ["BINAURAL_SPLIT_CODED", "BINAURAL_SPLIT_PCM"] + + +def get_options_cod(in_format, dtx): + # NOTE: this function is shared with another test in tests/split_rendering/test_split_rendering.py options = list() if dtx: @@ -124,6 +144,32 @@ def get_options(in_format, dtx): return options +def get_options_dec( + output_format: str, + output_file: Path, + is_voip: bool, +): + options = [] + + if output_format == "BINAURAL_SPLIT_PCM": + options.extend(["-om", str(output_file.with_suffix(".isarmd"))]) + + if is_split_rend(output_format): + # In VoIP configs account for VoIP delay by using the appropriately delayed head rotation file + options.extend( + [ + "-T", + str( + HR_TRAJECTORY_DIR + / f"rotate_euler_quaternion_30s_delayed{'_voip' if is_voip else ''}.csv" + ), + ] + ) + + return options + + +# NOTE: this list is shared with another test in tests/split_rendering/test_split_rendering.py INPUT_FILES = { "stereo": "stvST48n.wav", "ISM1": "stv1ISM48s.wav", @@ -158,6 +204,33 @@ OUTPUT_FOLDER_IF_KEEP_FILES_NEUTRAL = OUTPUT_FOLDER_IF_KEEP_FILES.joinpath( "neutral-profile" ) +def compare_audio(non_voip_output, voip_output, sampling_rate_khz): + # compare no-jbm and jbm output + x, _ = audiofile.readfile(non_voip_output) + x_jbm, _ = audiofile.readfile(voip_output) + + # strip jbm delay + # TODO: this may need to be adapted to handle variable offsets based on outcome of #1122 + cmp_result = audioarray.compare( + x, + x_jbm, + fs=sampling_rate_khz * 1000, + per_frame=False, + test_start_offset_ms=JBM_NEUTRAL_DELAY_MS, + ) + if not cmp_result["bitexact"]: + pytest.fail( + f"Difference between no jbm and zero-delay jbm decoding found! Max abs diff: {cmp_result['max_abs_diff']}" + ) + + +def compare_isar_files(non_voip_isar, voip_isar): + isar_bs = IsarBitstream(non_voip_isar) + isar_bs_voip = IsarBitstream(voip_isar).trim(JBM_NEUTRAL_DELAY_MS / 1000) + if not isar_bs_voip.is_same_as(isar_bs): + pytest.fail( + "Difference between no jbm and zero-delay jbm decoding found! ISAR files differ" + ) @pytest.mark.parametrize( "in_format,bitrate,out_format", TESTCASES_NO_DTX + TESTCASES_WITH_DTX @@ -168,6 +241,7 @@ def test_be_for_jbm_neutral_dly_profile_no_dtx( out_format, dut_encoder_frontend, dut_decoder_frontend, + dut_postrend_frontend, keep_files, ): run_test( @@ -177,6 +251,7 @@ def test_be_for_jbm_neutral_dly_profile_no_dtx( DTX_OFF, dut_encoder_frontend, dut_decoder_frontend, + dut_postrend_frontend, keep_files, ) @@ -188,6 +263,7 @@ def test_be_for_jbm_neutral_dly_profile_with_dtx( out_format, dut_encoder_frontend, dut_decoder_frontend, + dut_postrend_frontend, keep_files, ): run_test( @@ -197,6 +273,7 @@ def test_be_for_jbm_neutral_dly_profile_with_dtx( DTX_ON, dut_encoder_frontend, dut_decoder_frontend, + dut_postrend_frontend, keep_files, ) @@ -208,6 +285,7 @@ def run_test( dtx, dut_encoder_frontend, dut_decoder_frontend, + dut_postrend_frontend, keep_files, ): if keep_files: @@ -216,6 +294,7 @@ def run_test( with TemporaryDirectory() as tmp_dir: tmp_dir = pathlib.Path(tmp_dir) + output_ext = "wav" if out_format != "BINAURAL_SPLIT_CODED" else "isarbs" output_dir_no_jbm = ( OUTPUT_FOLDER_IF_KEEP_FILES_NO_JBM if keep_files else tmp_dir ) @@ -243,7 +322,7 @@ def run_test( input_file = tmp_dir.joinpath(f"{input_file.stem}-plus-noise.wav") audiofile.writefile(input_file, input_signal, fs) - options = get_options(in_format, dtx == DTX_ON) + options = get_options_cod(in_format, dtx == DTX_ON) dut_encoder_frontend.run( bitrate, sampling_rate_khz, @@ -255,33 +334,67 @@ def run_test( # run decoder without network simulation output = output_dir_no_jbm.joinpath( - f"{in_format}-{bitrate}-{out_format}-{dtx}.wav" + f"{in_format}-{bitrate}-{out_format}-{dtx}.{output_ext}" ).absolute() - dut_decoder_frontend.run(out_format, sampling_rate_khz, bitstream_file, output) + non_voip_options = get_options_dec(out_format, output, is_voip=False) + dut_decoder_frontend.run( + out_format, + sampling_rate_khz, + bitstream_file, + output, + add_option_list=non_voip_options, + ) # run decoder with network simulation - output_jbm = output_dir_neutral.joinpath(output.with_suffix(".jbm-0.wav").name) + output_jbm = output_dir_neutral.joinpath(output.with_suffix(f".jbm-0.{output_ext}").name) + voip_options = get_options_dec(out_format, output_jbm, is_voip=True) dut_decoder_frontend.run( out_format, sampling_rate_khz, bitstream_file, output_jbm, netsim_profile=DLY_PROFILE, + add_option_list=voip_options, + ) + + if out_format == "BINAURAL_SPLIT_CODED": + # With `BINAURAL_SPLIT_CODED` the main output is an ISAR bitstream + compare_isar_files(output, output_jbm) + else: + # Otherwise audio output + compare_audio(output, output_jbm, sampling_rate_khz) + + # With `BINAURAL_SPLIT_PCM` there is an additional metadata output file + if out_format == "BINAURAL_SPLIT_PCM": + isar_md_file = Path(non_voip_options[non_voip_options.index("-om") + 1]) + isar_md_file_voip = Path(voip_options[voip_options.index("-om") + 1]) + compare_isar_files(isar_md_file, isar_md_file_voip) + else: + isar_md_file = None + isar_md_file_voip = None + + # We will test ISAR_post_rend below. Only applies to split rendering. + if not is_split_rend(out_format): + return + + # Render non-voip output + postrend_output = output_dir_no_jbm.joinpath(output.with_suffix(".postrend.wav").name) + dut_postrend_frontend.run( + sampling_rate_khz, + output, + postrend_output, + str(HR_TRAJECTORY_DIR / f"rotate_euler_quaternion_30s.csv"), + isar_md_file, ) - # compare no-jbm and jbm output - x, _ = audiofile.readfile(output) - x_jbm, _ = audiofile.readfile(output_jbm) - - # strip jbm delay - cmp_result = audioarray.compare( - x, - x_jbm, - fs=sampling_rate_khz * 1000, - per_frame=False, - test_start_offset_ms=JBM_NEUTRAL_DELAY_MS, + # Render voip output + postrend_output_voip = output_dir_neutral.joinpath(output_jbm.with_suffix(".postrend.wav").name) + dut_postrend_frontend.run( + sampling_rate_khz, + output_jbm, + postrend_output_voip, + str(HR_TRAJECTORY_DIR / f"rotate_euler_quaternion_30s_voip.csv"), + isar_md_file_voip, ) - if not cmp_result["bitexact"]: - pytest.fail( - f"Difference between no jbm and zero-delay jbm decoding found! Max abs diff: {cmp_result['max_abs_diff']}" - ) + + compare_audio(postrend_output, postrend_output_voip, sampling_rate_khz) -- GitLab From 543c4018835716eeaf8f539860bae6ce39f50649 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 22 Apr 2025 16:02:53 +0200 Subject: [PATCH 019/340] Port C changes --- apps/decoder.c | 121 ++++++- apps/isar_post_rend.c | 10 + lib_com/options.h | 2 +- lib_dec/lib_dec.c | 500 ++++++++++++++++++++++++++++- lib_dec/lib_dec.h | 21 ++ lib_isar/isar_prot.h | 15 + lib_isar/isar_splitRend_lcld_enc.c | 5 + lib_isar/isar_splitRendererPre.c | 64 +++- lib_isar/lib_isar_pre_rend.c | 7 +- lib_isar/lib_isar_pre_rend.h | 5 + lib_rend/lib_rend.c | 20 ++ 11 files changed, 751 insertions(+), 19 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 50c8b043e9..f1f205850d 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -171,7 +171,11 @@ typedef struct static bool parseCmdlIVAS_dec( int16_t argc, char **argv, DecArguments *arg ); static void usage_dec( void ); static ivas_error decodeG192( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); -static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, +#endif + IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary, IVAS_DEC_HANDLE hIvasDec, const IVAS_AUDIO_CONFIG OutputConfig, const int32_t output_Fs ); #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); @@ -756,7 +760,11 @@ int main( if ( arg.voipMode ) { - error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, hIvasDec, pcmBuf ); + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + &splitRendBits, +#endif + hIvasDec, pcmBuf ); } else { @@ -1872,7 +1880,11 @@ static ivas_error initOnFirstGoodFrame( for ( int16_t i = 0; i < numInitialBadFrames; ++i ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( isSplitRend ) +#else if ( *splitRendWriter != NULL ) +#endif { ISAR_SPLIT_REND_BITS_DATA splitRendBitsZero; splitRendBitsZero.bits_buf = NULL; @@ -1890,7 +1902,12 @@ static ivas_error initOnFirstGoodFrame( return error; } } + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( !isSplitCoded ) +#else else +#endif { if ( *pRemainingDelayNumSamples < *numOutSamples ) @@ -2098,6 +2115,14 @@ static ivas_error decodeG192( return error; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( !isSplitRend ) + { + /* Ensure split rendering output struct is not used when not outputting to a split rendering format */ + splitRendBits = NULL; + } +#endif + if ( ( error = IVAS_DEC_is_split_rendering_coded_out( hIvasDec, &isSplitCoded ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_is_split_rendering_coded_out, code: %d\n", error ); @@ -2433,7 +2458,11 @@ static ivas_error decodeG192( } /* decode transport channels, do TSM and feed to renderer */ - if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, isSplitRend, splitRendBits ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + isSplitRend, +#endif + splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -2984,6 +3013,9 @@ static ivas_error decodeVoIP( RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, +#endif IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ) { @@ -3035,6 +3067,29 @@ static ivas_error decodeVoIP( bool parametersAvailableForEditing = false; uint16_t nSamplesRendered = 0; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + SplitFileReadWrite *splitRendWriter = NULL; + int16_t isSplitRend, isSplitCoded; + + if ( ( error = IVAS_DEC_is_split_rendering_enabled( hIvasDec, &isSplitRend ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_is_split_rendering_enabled, code: %d\n", error ); + return error; + } + + if ( !isSplitRend ) + { + /* Ensure split rendering output struct is not used when not outputting to a split rendering format */ + splitRendBits = NULL; + } + + if ( ( error = IVAS_DEC_is_split_rendering_coded_out( hIvasDec, &isSplitCoded ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_is_split_rendering_coded_out, code: %d\n", error ); + return error; + } +#endif + vec_pos_update = 0; if ( ( error = IVAS_DEC_GetRenderFramesizeMs( hIvasDec, &systemTimeInc_ms ) ) != IVAS_ERR_OK ) { @@ -3325,6 +3380,27 @@ static ivas_error decodeVoIP( /* decode and get samples */ while ( nSamplesRendered < nOutSamples ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( isSplitRend ) + { + if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, +#ifdef SUPPORT_JBM_TRACEFILE + writeJbmTraceFileFrameWrapper, + jbmTraceWriter, +#endif + &bitstreamReadDone, + &nSamplesRendered, + ¶metersAvailableForEditing, + systemTime_ms + ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSplitBinauralBitstream: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + } + else + { +#endif #ifdef SUPPORT_JBM_TRACEFILE if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else @@ -3334,6 +3410,9 @@ static ivas_error decodeVoIP( fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + } +#endif if ( bitstreamReadDone == true ) { @@ -3413,9 +3492,17 @@ static ivas_error decodeVoIP( /* Once good frame decoded, catch up */ if ( decodedGoodFrame ) { +#ifndef FIX_1119_SPLIT_RENDERING_VOIP SplitFileReadWrite *splitRendWriter = NULL; +#endif - if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, NULL, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, + if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + &vec_pos_len, +#else + NULL, +#endif + delayNumSamples_orig, &delayNumSamples, &delayTimeScale, &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &splitRendWriter ) ) != IVAS_ERR_OK ) { fprintf( stderr, "Error in initOnFirstGoodFrame(): %s\n", IVAS_DEC_GetErrorMessage( error ) ); @@ -3431,6 +3518,18 @@ static ivas_error decodeVoIP( /* Write current frame */ if ( decodedGoodFrame ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( isSplitRend ) + { + if ( split_rend_write_bitstream_to_file( splitRendWriter, splitRendBits->bits_buf, &splitRendBits->bits_read, &splitRendBits->bits_written ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nUnable to write to bitstream file!\n" ); + goto cleanup; + } + } + if ( !isSplitCoded ) + { +#endif if ( delayNumSamples < nOutSamples ) { if ( ( error = AudioFileWriter_write( afWriter, &pcmBuf[delayNumSamples * nOutChannels], nOutSamples * nOutChannels - ( delayNumSamples * nOutChannels ) ) ) != IVAS_ERR_OK ) @@ -3444,6 +3543,9 @@ static ivas_error decodeVoIP( { delayNumSamples -= nOutSamples; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + } +#endif /* Write ISM metadata to external file(s) */ if ( decodedGoodFrame && arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL ) @@ -3602,11 +3704,19 @@ static ivas_error decodeVoIP( *------------------------------------------------------------------------------------------*/ memset( pcmBuf, 0, delayNumSamples_orig[0] * nOutChannels * sizeof( int16_t ) ); + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( afWriter != NULL) + { +#endif if ( ( error = AudioFileWriter_write( afWriter, pcmBuf, delayNumSamples_orig[0] * nOutChannels ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError writing output file: %s\n", ivas_error_to_string( error ) ); goto cleanup; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + } +#endif /*------------------------------------------------------------------------------------------* * Printouts after decoding has finished @@ -3651,6 +3761,9 @@ cleanup: EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); AudioFileWriter_close( &afWriter ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + split_rend_reader_writer_close( &splitRendWriter ); +#endif JbmOffsetFileWriter_close( &jbmOffsetWriter ); #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriter_close( &jbmTraceWriter ); diff --git a/apps/isar_post_rend.c b/apps/isar_post_rend.c index aa004c4cc3..1ec675f45b 100644 --- a/apps/isar_post_rend.c +++ b/apps/isar_post_rend.c @@ -1103,6 +1103,16 @@ int main( fprintf( stderr, "\nISAR_POST_REND_FeedSplitBinauralBitstream failed: %s\n", ivas_error_to_string( error ) ); goto cleanup; } + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + /* Set BFI if frame is empty */ + int16_t frameEmpty = (int16_t) ( bitsBuffer.config.bitsWritten == 0 ); + if ( ( error = ISAR_POST_REND_SetSplitRendBFI( hIsarPostRend, frameEmpty ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "Error in ISAR_POST_REND_SetSplitRendBFI(): %s\n", ivas_error_to_string( error ) ); + goto cleanup; + } +#endif } } diff --git a/lib_com/options.h b/lib_com/options.h index dc1b88d11d..c335aaa419 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -159,8 +159,8 @@ /* only BE switches wrt selection floating point code */ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ -#define TMP_FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add error check for unsupported config: split rendering with VoIP mode */ #define UNIFIED_DECODING_PATHS_LEFTOVERS /* VA: issue 880: remove leftovers after NONBE_UNIFIED_DECODING_PATHS */ +#define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index ae919c2184..781036b82f 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -115,7 +115,11 @@ static void store_JbmData( IVAS_DEC_VOIP *hVoIP, JB4_DATAUNIT_HANDLE dataUnit, c static ivas_error evs_dec_main( Decoder_Struct *st_ivas ); static ivas_error input_format_API_to_internal( IVAS_DEC_INPUT_FORMAT input_format, int16_t *bitstream_format_internal, int16_t *sdp_hf_only, const bool is_voip_enabled ); static void init_decoder_config( DECODER_CONFIG_HANDLE hDecoderConfig ); -static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, const int16_t isSplitRend, ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); +static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + const int16_t isSplitRend, +#endif + ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); static ivas_error apa_setup( IVAS_DEC_HANDLE hIvasDec, const bool isInitialized_voip, const uint16_t nTransportChannels ); static PCM_RESOLUTION pcm_type_API_to_internal( const IVAS_DEC_PCM_TYPE pcmType ); static void *pcm_buffer_offset( void *buffer, const IVAS_DEC_PCM_TYPE pcmType, const int32_t offset ); @@ -1115,7 +1119,9 @@ ivas_error IVAS_DEC_ReadFormat( ivas_error IVAS_DEC_GetSamplesDecoder( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP const int16_t isSplitRend, /* i : split rendering enabled flag */ +#endif ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { @@ -1145,7 +1151,11 @@ ivas_error IVAS_DEC_GetSamplesDecoder( * Setup all decoder parts (IVAS decoder, ISAR) *-----------------------------------------------------------------*/ - if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, isSplitRend, splitRendBits ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + isSplitRend, +#endif + splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -1836,6 +1846,203 @@ ivas_error IVAS_DEC_GetSamplesRenderer( } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static int16_t isar_get_frame_size( + Decoder_Struct* st_ivas +) +{ + int32_t output_Fs; + int16_t nSamplesPerChannel; + output_Fs = st_ivas->hDecoderConfig->output_Fs; + + if ( st_ivas->hDecoderConfig->render_framesize != IVAS_RENDER_FRAMESIZE_20MS && + ( st_ivas->hRenderConfig->split_rend_config.poseCorrectionMode == ISAR_SPLIT_REND_POSE_CORRECTION_MODE_NONE || + st_ivas->hRenderConfig->split_rend_config.dof == 0 ) ) + { + nSamplesPerChannel = (int16_t) ( output_Fs / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ); + nSamplesPerChannel *= (int16_t) st_ivas->hDecoderConfig->render_framesize; + } + else + { + nSamplesPerChannel = (int16_t) ( output_Fs / FRAMES_PER_SEC ); + } + + return nSamplesPerChannel; +} + +static ivas_error isar_render_poses( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ + float** p_head_pose_buf, + float *p_Cldfb_RealBuffer_Binaural[][CLDFB_NO_COL_MAX], + float *p_Cldfb_ImagBuffer_Binaural[][CLDFB_NO_COL_MAX], + int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ + bool *needNewFrame /* o : indication that the decoder needs a new frame */ +) +{ + Decoder_Struct *st_ivas; + float pcmBuf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES * L_FRAME48k]; + int16_t i, j; + ivas_error error; + ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; + int16_t numPoses; + int16_t slots_rendered, slots_rendered_new; + + error = IVAS_ERR_OK; + st_ivas = hIvasDec->st_ivas; + *needNewFrame = false; + hSplitBinRend = st_ivas->hSplitBinRend; + + numPoses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; + + /* init flush buffer for rate switch if not already initizalized */ + if ( hIvasDec->flushbuffer == NULL ) + { + hIvasDec->flushbuffer = (void *) malloc( numPoses * BINAURAL_CHANNELS * hIvasDec->nSamplesFrame / IVAS_MAX_PARAM_SPATIAL_SUBFRAMES * sizeof( float ) ); + if ( hIvasDec->flushbuffer == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Could not allocate flush buffer" ); + } + hIvasDec->pcmType = IVAS_DEC_PCM_FLOAT; + set_zero( (float *) hIvasDec->flushbuffer, numPoses * BINAURAL_CHANNELS * hIvasDec->nSamplesFrame / IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ); + } + + if ( st_ivas->hTcBuffer == NULL || hIvasDec->hasBeenFedFrame ) + { + slots_rendered = 0; + } + else + { + /* this is needed for OMASA-DISC, because the td-rend granularity is 240 samples at 48kHz, leading to wrong slot count. */ + if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC ) + { + slots_rendered = st_ivas->hTcBuffer->n_samples_rendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); + } + else + { + slots_rendered = st_ivas->hTcBuffer->n_samples_rendered / st_ivas->hTcBuffer->n_samples_granularity; + } + } + + /* render */ + if ( ( error = IVAS_DEC_GetSamplesRenderer( hIvasDec, nSamplesAsked, IVAS_DEC_PCM_FLOAT, pcmBuf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) + { + return error; + } + + // TODO: 1119 - Check if this early return can be simplified. ATM we early return through two stack frames. + if ( !hIvasDec->hasBeenFedFirstGoodFrame ) + { + return IVAS_ERR_OK; + } + + /* change buffer layout */ + for ( i = 0; i < *nOutSamples; ++i ) + { + for ( j = 0; j < BINAURAL_CHANNELS * numPoses; ++j ) + { + p_head_pose_buf[j][i] = pcmBuf[i * BINAURAL_CHANNELS * numPoses + j]; + } + } + + if ( st_ivas->hTcBuffer == NULL ) + { + slots_rendered_new = 0; + } + else + { + /* this is needed for OMASA-DISC, because the td-rend granularity is 240 samples at 48kHz, leading to wrong slot count. */ + if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC ) + { + slots_rendered_new = st_ivas->hTcBuffer->n_samples_rendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); + } + else + { + slots_rendered_new = st_ivas->hTcBuffer->n_samples_rendered / st_ivas->hTcBuffer->n_samples_granularity; + } + } + + for ( i = 0; i < BINAURAL_CHANNELS * numPoses; ++i ) + { + for ( j = slots_rendered; j < slots_rendered_new; ++j ) + { + mvr2r( hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[i][j], p_Cldfb_RealBuffer_Binaural[i][j - slots_rendered], CLDFB_NO_CHANNELS_MAX ); + mvr2r( hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[i][j], p_Cldfb_ImagBuffer_Binaural[i][j - slots_rendered], CLDFB_NO_CHANNELS_MAX ); + } + } + + return error; +} + +static ivas_error isar_generate_metadata_and_bitstream( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + float** p_head_pose_buf, + float *p_Cldfb_RealBuffer_Binaural[][CLDFB_NO_COL_MAX], + float *p_Cldfb_ImagBuffer_Binaural[][CLDFB_NO_COL_MAX], + ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ +) +{ + Decoder_Struct *st_ivas; + AUDIO_CONFIG output_config; + int32_t output_Fs; + ivas_error error; + ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; + int16_t max_band; + int16_t pcm_out_flag; + int16_t td_input; + int16_t ro_md_flag; + IVAS_QUATERNION Quaternion; + + error = IVAS_ERR_OK; + st_ivas = hIvasDec->st_ivas; + output_config = st_ivas->hDecoderConfig->output_config; + output_Fs = st_ivas->hDecoderConfig->output_Fs; + hSplitBinRend = st_ivas->hSplitBinRend; + + max_band = (int16_t) ( ( BINAURAL_MAXBANDS * output_Fs ) / 48000 ); + pcm_out_flag = ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; + td_input = st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC_ROOM && st_ivas->renderer_type != RENDERER_STEREO_PARAMETRIC; + + if ( st_ivas->hBinRendererTd != NULL ) + { + ro_md_flag = 1; + } + else + { + ro_md_flag = 0; + } + + if ( st_ivas->hHeadTrackData != NULL ) + { + Quaternion = st_ivas->hHeadTrackData->Quaternions[0]; + } + else + { + Quaternion.w = -3.0f; + Quaternion.x = 0.0f; + Quaternion.y = 0.0f; + Quaternion.z = 0.0f; + } + + if ( ( error = ISAR_PRE_REND_MultiBinToSplitBinaural( &hSplitBinRend->splitrend, + Quaternion, + st_ivas->hRenderConfig->split_rend_config.splitRendBitRate, + st_ivas->hRenderConfig->split_rend_config.codec, + st_ivas->hRenderConfig->split_rend_config.isar_frame_size_ms, + st_ivas->hRenderConfig->split_rend_config.codec_frame_size_ms, + splitRendBits, + p_Cldfb_RealBuffer_Binaural, + p_Cldfb_ImagBuffer_Binaural, + max_band, p_head_pose_buf, 1, !td_input, pcm_out_flag, ro_md_flag ) ) != IVAS_ERR_OK ) + { + return error; + } + + return IVAS_ERR_OK; +} +#endif /* FIX_1119_SPLIT_RENDERING_VOIP */ + + /*---------------------------------------------------------------------* * IVAS_DEC_GetSplitBinauralBitstream( ) * @@ -1850,6 +2057,82 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + Decoder_Struct *st_ivas; + ivas_error error; + float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; + float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; + float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + int32_t i, j; + int16_t pcm_out_flag; + int16_t numSamplesPerChannelToOutput; + + if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL ) + { + return IVAS_ERR_UNEXPECTED_NULL_POINTER; + } + + error = IVAS_ERR_UNKNOWN; + st_ivas = hIvasDec->st_ivas; + + if ( is_split_rendering_enabled( st_ivas->hDecoderConfig, st_ivas->hRenderConfig ) == 0 ) + { + return IVAS_ERR_WRONG_PARAMS; + } + + pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; + numSamplesPerChannelToOutput = isar_get_frame_size(st_ivas); + + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] = head_pose_buf[i]; + + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; + p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; + } + } + + if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, nOutSamples, needNewFrame)) != IVAS_ERR_OK ) + { + return error; + } + // TODO: 1119 - Check if this early return can be simplified. ATM we early return through two stack frames. + if ( !hIvasDec->hasBeenFedFirstGoodFrame ) + { + return IVAS_ERR_OK; + } + + if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, splitRendBits ) ) ) + { + return error; + } + + /* convert to int16 with limiting for BINAURAL_SPLIT_PCM */ + if ( pcm_out_flag ) + { + // TODO: 1119 - remove duplicated if/else branches + if ( st_ivas->hDecoderConfig->render_framesize == IVAS_RENDER_FRAMESIZE_5MS ) + { +#ifndef DISABLE_LIMITER + ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, numSamplesPerChannelToOutput, st_ivas->BER_detect ); +#endif + } + else + { + ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, numSamplesPerChannelToOutput, st_ivas->BER_detect ); + } + +#ifdef DEBUGGING + st_ivas->noClipping += +#endif + ivas_syn_output( p_head_pose_buf, numSamplesPerChannelToOutput, st_ivas->hDecoderConfig->nchan_out, (int16_t *) pcmBuf_out ); + } +#else Decoder_Struct *st_ivas; AUDIO_CONFIG output_config; int32_t output_Fs; @@ -2036,6 +2319,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( #endif ivas_syn_output( pOutput, numSamplesPerChannelToDecode, st_ivas->hDecoderConfig->nchan_out, (int16_t *) pcmBuf_out ); } +#endif return error; } @@ -2050,7 +2334,9 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ uint8_t *nTransportChannels, /* o : number of decoded transport PCM channels */ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP const int16_t isSplitRend, /* i : split rendering enabled flag */ +#endif ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { @@ -2074,7 +2360,11 @@ static ivas_error ivas_dec_setup_all( st_ivas = hIvasDec->st_ivas; /* Setup IVAS split rendering */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( splitRendBits != NULL ) +#else if ( isSplitRend ) +#endif { if ( ( error = isar_set_split_rend_setup( st_ivas->hSplitBinRend, &st_ivas->hRenderConfig->split_rend_config, st_ivas->hCombinedOrientationData, splitRendBits ) ) != IVAS_ERR_OK ) { @@ -2106,7 +2396,11 @@ static ivas_error ivas_dec_setup_all( * - reconfigure the ISAR handle in case of bitrate switching (renderer might change) *-----------------------------------------------------------------*/ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( st_ivas->ini_frame == 0 && splitRendBits != NULL ) +#else if ( st_ivas->ini_frame == 0 && isSplitRend ) +#endif { if ( ( error = ivas_dec_init_split_rend( st_ivas ) ) != IVAS_ERR_OK ) { @@ -3422,17 +3716,31 @@ ivas_error IVAS_DEC_TSM_SetQuality( #endif +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * ivas_dec_voip_get_samples_common( ) + * + * Main function to output one frame in VoIP. Holds common code for + * regular output configs and split rendering configs. + *---------------------------------------------------------------------*/ +static ivas_error ivas_dec_voip_get_samples_common +#else /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_GetSamples( ) * * Main function to decode one frame in VoIP *---------------------------------------------------------------------*/ -ivas_error IVAS_DEC_VoIP_GetSamples( +ivas_error IVAS_DEC_VoIP_GetSamples +#endif +( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ void *pcmBuf, /* o : output synthesis signal */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ +#endif #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, void *jbmWriter, @@ -3453,6 +3761,17 @@ ivas_error IVAS_DEC_VoIP_GetSamples( ivas_error error; uint8_t nOutChannels; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int32_t i, j; + int16_t nSlotsRendered; + float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; + float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; + float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; +#endif + if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->hVoIP == NULL ) { return IVAS_ERR_UNEXPECTED_NULL_POINTER; @@ -3469,14 +3788,6 @@ ivas_error IVAS_DEC_VoIP_GetSamples( return IVAS_ERR_WRONG_PARAMS; } -#ifdef TMP_FIX_1119_SPLIT_RENDERING_VOIP - if ( hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || - hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) - { - return IVAS_ERROR( IVAS_ERR_NOT_IMPLEMENTED, "Split rendering is not integrated with VoIP mode" ); - } -#endif - /* make sure that the FIFO after decoder/scaler contains at least one sound card frame (i.e. 20ms) */ while ( *nSamplesRendered < nSamplesPerChannel ) { @@ -3622,7 +3933,13 @@ ivas_error IVAS_DEC_VoIP_GetSamples( { if ( hIvasDec->nSamplesAvailableNext == 0 || hIvasDec->nSamplesAvailableNext == hIvasDec->nSamplesFrame ) { - if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, 0, NULL ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + splitRendBits +#else + 0, NULL +#endif + ) ) != IVAS_ERR_OK ) { return error; } @@ -3644,20 +3961,179 @@ ivas_error IVAS_DEC_VoIP_GetSamples( } } +// TODO: 1119 - extract to a function? +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + { + nSlotsRendered = *nSamplesRendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); + + /* Move output pointers forward */ + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] = &head_pose_buf[i][*nSamplesRendered]; + + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + p_Cldfb_RealBuffer_Binaural[i][j] = &Cldfb_RealBuffer_Binaural[i][j][nSlotsRendered]; + p_Cldfb_ImagBuffer_Binaural[i][j] = &Cldfb_ImagBuffer_Binaural[i][j][nSlotsRendered]; + } + } + + /* Render head poses from time-scaled transport channels */ + if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, &nSamplesRendered_loop, &tmp )) != IVAS_ERR_OK ) + { + return error; + } + } + else + { +#endif /* render IVAS frames directly to the output buffer */ if ( ( error = IVAS_DEC_GetSamplesRenderer( hIvasDec, nSamplesToRender, pcmType, pcm_buffer_offset( pcmBuf, pcmType, *nSamplesRendered * nOutChannels ), &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) { return error; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + } +#endif *nSamplesRendered += nSamplesRendered_loop; update_voip_rendered20ms( hIvasDec, nSamplesRendered_loop ); } } +// TODO: 1119 - extract to a function? +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( hIvasDec->hasDecodedFirstGoodFrame && + ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || + hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) + { + /* Set pointers to beginning of head pose buffers */ + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] = head_pose_buf[i]; + + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; + p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; + } + } + + /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ + if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, splitRendBits ) ) ) + { + return error; + } + + /* Synthesise PCM output if split PCM */ + if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + { + if ( st_ivas->hDecoderConfig->render_framesize == IVAS_RENDER_FRAMESIZE_5MS ) + { + #ifndef DISABLE_LIMITER + ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, *nSamplesRendered, st_ivas->BER_detect ); + #endif + } + else + { + ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, *nSamplesRendered, st_ivas->BER_detect ); + } + + #ifdef DEBUGGING + st_ivas->noClipping += + #endif + ivas_syn_output( p_head_pose_buf, *nSamplesRendered, st_ivas->hDecoderConfig->nchan_out, (int16_t *) pcmBuf ); + } + } +#endif + return IVAS_ERR_OK; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * IVAS_DEC_VoIP_GetSamples( ) + * + * Main function to decode one frame in VoIP + *---------------------------------------------------------------------*/ + +ivas_error IVAS_DEC_VoIP_GetSamples ( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ +#ifdef SUPPORT_JBM_TRACEFILE + JbmTraceFileWriterFn jbmWriterFn, + void *jbmWriter, +#endif + bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ + uint16_t *nSamplesRendered, /* o : number of samples rendered */ + bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ + const uint32_t systemTimestamp_ms /* i : current system timestamp */ +) +{ + return ivas_dec_voip_get_samples_common( + hIvasDec, + nSamplesPerChannel, + pcmType, + pcmBuf, + NULL, +#ifdef SUPPORT_JBM_TRACEFILE + jbmWriterFn, + jbmWriter, +#endif + bitstreamReadDone, + nSamplesRendered, + parametersAvailableForEditing, + systemTimestamp_ms + ); +} + +/*! r: error code */ +ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ +#ifdef SUPPORT_JBM_TRACEFILE + JbmTraceFileWriterFn jbmWriterFn, + void* jbmWriter +#endif + , + bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ + uint16_t *nSamplesRendered, /* o : number of samples rendered */ + bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ + const uint32_t systemTimestamp_ms /* i : current system timestamp */ +) +{ + ivas_error error = IVAS_ERR_UNKNOWN; + int16_t nSamplesPerChannel = 0; + + if ( ( error = IVAS_DEC_GetRenderFramesizeSamples( hIvasDec, &nSamplesPerChannel ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError getting render frame size in samples\n" ); + return error; + } + + return ivas_dec_voip_get_samples_common( + hIvasDec, + nSamplesPerChannel, + IVAS_DEC_PCM_INT16, + pcmBuf, + splitRendBits, +#ifdef SUPPORT_JBM_TRACEFILE + jbmWriterFn, + jbmWriter, +#endif + bitstreamReadDone, + nSamplesRendered, + parametersAvailableForEditing, + systemTimestamp_ms + ); +} +#endif + /*---------------------------------------------------------------------* * update_voip_rendered20ms( ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 588399ef2a..b8a60066a3 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -162,7 +162,9 @@ ivas_error IVAS_DEC_ReadFormat( /*! r: decoder error code */ ivas_error IVAS_DEC_GetSamplesDecoder( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP const int16_t isSplitRend, /* i : split rendering enabled flag */ +#endif ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ); @@ -321,6 +323,25 @@ ivas_error IVAS_DEC_VoIP_GetSamples( const uint32_t systemTimestamp_ms /* i : current system timestamp */ ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*! r: error code */ +ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ +#ifdef SUPPORT_JBM_TRACEFILE + JbmTraceFileWriterFn jbmWriterFn, + void* jbmWriter +#endif + , + bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ + uint16_t *nSamplesRendered, + bool *parametersAvailableForEditing, + const uint32_t systemTimestamp_ms /* i : current system timestamp */ +); +#endif + ivas_error IVAS_DEC_Flush( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ const int16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ diff --git a/lib_isar/isar_prot.h b/lib_isar/isar_prot.h index 7f66b317b5..f842b0a53b 100644 --- a/lib_isar/isar_prot.h +++ b/lib_isar/isar_prot.h @@ -67,8 +67,13 @@ void isar_splitBinPreRendClose( void lc3plusTimeAlignCldfbPoseCorr( SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renderer handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX] /* i/o: Binaural signals, imag. part */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: Binaural signals, real part */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX] /* i/o: Binaural signals, imag. part */ +#endif ); ivas_error splitRendLc3plusEncodeAndWrite( @@ -183,8 +188,13 @@ void isar_splitBinLCLDEncClose( void isar_splitBinLCLDEncProcess( ISAR_BIN_HR_SPLIT_LCLD_ENC_HANDLE hSplitBinLCLDEnc, /* i/o: ISAR LCLD encoder handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], + float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], +#else float Cldfb_In_Real[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_In_Imag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif const int32_t available_bits, ISAR_SPLIT_REND_BITS_HANDLE pBits /* i/o: ISAR bits handle */ ); @@ -271,8 +281,13 @@ void isar_rend_CldfbSplitPreRendProcess( const ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i : binaural pre-renderer handle */ const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i : Binaural signals, real part */ + float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i : Binaural signals, imag. part */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, real part */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, imag. part */ +#endif ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits handle */ const int32_t target_md_bits, /* i : ISAR MD bitrate */ const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ diff --git a/lib_isar/isar_splitRend_lcld_enc.c b/lib_isar/isar_splitRend_lcld_enc.c index ac43233e1a..171b5162b3 100644 --- a/lib_isar/isar_splitRend_lcld_enc.c +++ b/lib_isar/isar_splitRend_lcld_enc.c @@ -158,8 +158,13 @@ void isar_splitBinLCLDEncClose( void isar_splitBinLCLDEncProcess( ISAR_BIN_HR_SPLIT_LCLD_ENC_HANDLE hSplitBinLCLDEnc, /* i/o: ISAR LCLD encoder handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], + float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], +#else float Cldfb_In_Real[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_In_Imag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif const int32_t available_bits, ISAR_SPLIT_REND_BITS_HANDLE pBits /* i/o: ISAR bits handle */ ) diff --git a/lib_isar/isar_splitRendererPre.c b/lib_isar/isar_splitRendererPre.c index c7098f997f..e7470c000e 100644 --- a/lib_isar/isar_splitRendererPre.c +++ b/lib_isar/isar_splitRendererPre.c @@ -53,7 +53,15 @@ * Local function declarations *---------------------------------------------------------------------*/ -static void isar_SplitRenderer_GetRotMd( ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], const int16_t low_res, const int16_t ro_md_flag ); +static void isar_SplitRenderer_GetRotMd( ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float* Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], + float* Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], +#else + float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], + float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif + const int16_t low_res, const int16_t ro_md_flag ); /*------------------------------------------------------------------------- @@ -271,11 +279,21 @@ static void ComputePostPredCov( static void ComputeBandedCrossCov( +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_RealBuffer1[][CLDFB_NO_COL_MAX], + float *Cldfb_ImagBuffer1[][CLDFB_NO_COL_MAX], +#else float Cldfb_RealBuffer1[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer1[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif const int16_t ch_start_idx1, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float* Cldfb_RealBuffer2[][CLDFB_NO_COL_MAX], + float* Cldfb_ImagBuffer2[][CLDFB_NO_COL_MAX], +#else float Cldfb_RealBuffer2[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer2[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif const int16_t ch_start_idx2, float out_cov_re[BINAURAL_CHANNELS][BINAURAL_CHANNELS], float out_cov_im[BINAURAL_CHANNELS][BINAURAL_CHANNELS], @@ -340,8 +358,13 @@ static void ComputeBandedCrossCov( static void ComputeBandedCov( +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float * Cldfb_RealBuffer[][CLDFB_NO_COL_MAX], + float * Cldfb_ImagBuffer[][CLDFB_NO_COL_MAX], +#else float Cldfb_RealBuffer[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], +#endif const int16_t ch_start_idx, float out_cov_re[][BINAURAL_CHANNELS], float out_cov_im[][BINAURAL_CHANNELS], @@ -1347,8 +1370,13 @@ static void isar_SplitRenderer_quant_code( static void isar_SplitRenderer_GetRotMd( ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i/o: binaural renderer handle */ MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ + float *Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ +#else float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* o : Reference Binaural signals */ float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* o : Reference Binaural signals */ +#endif const int16_t low_res, const int16_t ro_md_flag /* i : Flag to indicate real only metadata for yaw */ ) @@ -1433,8 +1461,13 @@ void isar_rend_CldfbSplitPreRendProcess( const ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i : binaural pre-renderer handle */ const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i : Binaural signals, real part */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i : Binaural signals, imag. part */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, real part */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, imag. part */ +#endif ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits handle */ const int32_t target_md_bits, /* i : ISAR MD bitrate */ const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ @@ -1893,6 +1926,11 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( uint8_t useLc3plus; float *in_delayed[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; int16_t i; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int16_t j; + float *p_Cldfb_In_BinReal[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + float *p_Cldfb_In_BinImag[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; +#endif int32_t num_slots; push_wmops( "isar_renderMultiTDBinToSplitBinaural" ); @@ -1902,6 +1940,17 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( useLc3plus = hSplitBin->hLc3plusEnc != NULL; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) + { + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + p_Cldfb_In_BinReal[i][j] = Cldfb_In_BinReal[i][j]; + p_Cldfb_In_BinImag[i][j] = Cldfb_In_BinImag[i][j]; + } + } +#endif + if ( useLc3plus ) { /*this should always have the time resolution of pose correction MD. Note that this does not change frame size of LC3plus*/ @@ -1963,7 +2012,11 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( { target_md_bits = isar_get_split_rend_md_target_brate( SplitRendBitRate, pcm_out_flag ) * L_FRAME48k / 48000; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + isar_rend_CldfbSplitPreRendProcess( hSplitBin->hBinHrSplitPreRend, headPosition, &hSplitBin->multiBinPoseData, p_Cldfb_In_BinReal, p_Cldfb_In_BinImag, pBits, target_md_bits, low_res_pre_rend_rot, ro_md_flag ); +#else isar_rend_CldfbSplitPreRendProcess( hSplitBin->hBinHrSplitPreRend, headPosition, &hSplitBin->multiBinPoseData, Cldfb_In_BinReal, Cldfb_In_BinImag, pBits, target_md_bits, low_res_pre_rend_rot, ro_md_flag ); +#endif } if ( pcm_out_flag == 0 ) @@ -1978,7 +2031,11 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( pBits->codec_frame_size_ms = codec_frame_size_ms; pBits->isar_frame_size_ms = isar_frame_size_ms; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + isar_splitBinLCLDEncProcess( hSplitBin->hSplitBinLCLDEnc, p_Cldfb_In_BinReal, p_Cldfb_In_BinImag, available_bits, pBits ); +#else isar_splitBinLCLDEncProcess( hSplitBin->hSplitBinLCLDEnc, Cldfb_In_BinReal, Cldfb_In_BinImag, available_bits, pBits ); +#endif } else { @@ -2040,8 +2097,13 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( void lc3plusTimeAlignCldfbPoseCorr( SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renderer handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX] /* i/o: Binaural signals, imag. part */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: Binaural signals, real part */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX] /* ii/: Binaural signals, imag. part */ +#endif ) { float Cldfb_In_BinReal_tmp[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][2][CLDFB_NO_CHANNELS_MAX]; diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index 09323f6eed..0e8c7ad50c 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -273,7 +273,7 @@ void ISAR_PRE_REND_GetMultiBinPoseData( * *------------------------------------------------------------------------*/ -ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( +ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural ( SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renerer handle */ const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ const int32_t SplitRendBitRate, /* i : Split renderer bitrate */ @@ -281,8 +281,13 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( const int16_t isar_frame_size_ms, /* i : ISAR framesize */ int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ + float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB real buffer */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB imag buffer */ +#endif const int16_t max_bands, /* i : CLDFB bands */ float *output[], /* i/o: PCM in/out buffer */ const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ diff --git a/lib_isar/lib_isar_pre_rend.h b/lib_isar/lib_isar_pre_rend.h index 9f9f098f39..e1124a714c 100644 --- a/lib_isar/lib_isar_pre_rend.h +++ b/lib_isar/lib_isar_pre_rend.h @@ -71,8 +71,13 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( const int16_t isar_frame_size_ms, /* i : ISAR framesize */ int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ + float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ +#else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB real buffer */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB imag buffer */ +#endif const int16_t max_bands, /* i : CLDFB bands */ float *output[], /* i/o: PCM in/out buffer */ const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 2d77b66ed6..6d97493190 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7463,6 +7463,21 @@ static ivas_error getSamplesInternal( int16_t i, ro_md_flag; float *tmpBinaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS], tmpBinaural_buff[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][L_FRAME48k]; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + int32_t j; + + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; + p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; + } + } +#endif + for ( ch = 0; ch < MAX_OUTPUT_CHANNELS; ch++ ) { tmpBinaural[ch] = tmpBinaural_buff[ch]; @@ -7499,8 +7514,13 @@ static ivas_error getSamplesInternal( hIvasRend->hRendererConfig->split_rend_config.isar_frame_size_ms, hIvasRend->hRendererConfig->split_rend_config.codec_frame_size_ms, &bits, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + p_Cldfb_RealBuffer_Binaural, + p_Cldfb_ImagBuffer_Binaural, +#else Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, +#endif ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, -- GitLab From d38aa90143c326f2a1965a5c878068340f325891 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 28 Apr 2025 09:30:41 +0200 Subject: [PATCH 020/340] Add fix for missing assignment of secondary renderer --- lib_dec/ivas_output_config.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index 430b57afa2..31bc424739 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -499,11 +499,22 @@ RENDERER_TYPE ivas_renderer_secondary_select( renderer_type = RENDERER_DISABLE; output_config = st_ivas->hDecoderConfig->output_config; - if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC && output_config == IVAS_AUDIO_CONFIG_BINAURAL ) + if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC && +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) +#else + output_config == IVAS_AUDIO_CONFIG_BINAURAL +#endif + ) { renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } - else if ( st_ivas->ivas_format == SBA_ISM_FORMAT && st_ivas->ism_mode == ISM_SBA_MODE_DISC && ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB ) ) + else if ( st_ivas->ivas_format == SBA_ISM_FORMAT && st_ivas->ism_mode == ISM_SBA_MODE_DISC && ( + output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM +#endif + ) ) { renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } -- GitLab From e7c1525d65284cd36112222eba37432926bff327 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 23 May 2025 11:31:05 +0200 Subject: [PATCH 021/340] [tmp] crude fix for out of bounds array access --- lib_dec/ivas_stat_dec.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 89d53452bb..e0702a345a 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -823,15 +823,15 @@ typedef struct renderer_struct typedef struct { - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; } ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA, *ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE; typedef struct { - float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; IVAS_AUDIO_CONFIG config; } ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA, *ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA_HANDLE; -- GitLab From 0746d6644b52411ee038b5526015d004755c395b Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 10 Jun 2025 12:33:24 +0200 Subject: [PATCH 022/340] Add missing preprocessor switch --- lib_dec/ivas_stat_dec.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index e0702a345a..efc5f736d6 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -830,8 +830,13 @@ typedef struct typedef struct { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP // TODO 1119: This double space should not be needed - we always output 20ms of content float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; +#else + float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; +#endif IVAS_AUDIO_CONFIG config; } ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA, *ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA_HANDLE; -- GitLab From 08776d370533956b6d0d171b9d21cc366f552eb2 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 11 Jun 2025 10:15:42 +0200 Subject: [PATCH 023/340] Store CLDFB content used for SR in a ring buffer --- lib_dec/cldfb_ring_buffer.c | 227 +++++++++++++++++++ lib_dec/cldfb_ring_buffer.h | 66 ++++++ lib_dec/ivas_dirac_dec.c | 9 + lib_dec/ivas_mc_param_dec.c | 9 + lib_dec/ivas_mc_paramupmix_dec.c | 9 + lib_dec/ivas_omasa_dec.c | 11 + lib_dec/ivas_osba_dec.c | 19 +- lib_dec/ivas_stat_dec.h | 9 + lib_dec/lib_dec.c | 150 ++++++------ lib_isar/lib_isar_pre_rend.c | 4 +- lib_rend/ivas_dirac_dec_binaural_functions.c | 18 ++ 11 files changed, 455 insertions(+), 76 deletions(-) create mode 100644 lib_dec/cldfb_ring_buffer.c create mode 100644 lib_dec/cldfb_ring_buffer.h diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c new file mode 100644 index 0000000000..8e91d37486 --- /dev/null +++ b/lib_dec/cldfb_ring_buffer.c @@ -0,0 +1,227 @@ +/****************************************************************************************************** + + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include "cldfb_ring_buffer.h" +#include "cnst.h" +#include "prot.h" +#include "ivas_error_utils.h" +#include +#include +#include + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + +struct CldfbRingBuffer { + float* real; + float* imag; + uint32_t capacity; + uint32_t write_pos; + uint32_t read_pos; + int16_t is_full; +}; + +ivas_error CLDFB_RB_Open( CLDFB_RING_BUFFER_HANDLE *ph, int16_t capacity_columns ) +{ + CLDFB_RING_BUFFER_HANDLE h; + int32_t capacity; + capacity = capacity_columns * CLDFB_NO_CHANNELS_MAX; + + if ( ( h = malloc( sizeof( struct CldfbRingBuffer ) ) ) == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); + } + h->real = NULL; + h->imag = NULL; + h->capacity = 0; + h->write_pos = 0; + h->read_pos = 0; + h->is_full = 0; + *ph = h; + + if ( ( h->real = malloc( capacity * sizeof( float ) ) ) == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); + } + if ( ( h->imag = malloc( capacity * sizeof( float ) ) ) == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); + } + h->capacity = capacity; + + return IVAS_ERR_OK; +} + +void CLDFB_RB_Close( CLDFB_RING_BUFFER_HANDLE *ph ) +{ + CLDFB_RING_BUFFER_HANDLE h; + + if ( ph == NULL ) + { + return; + } + h = *ph; + + if ( h == NULL ) + { + return; + } + + if ( h->real != NULL ) + { + free( h->real ); + } + if ( h->imag != NULL ) + { + free( h->imag ); + } + + free( h ); + *ph = NULL; + + return; +} + +void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* imag, uint16_t num_bands ) +{ + assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); + assert( !CLDFB_RB_IsFull( h ) ); + + mvr2r( real, &h->real[h->write_pos], (int16_t) num_bands ); + mvr2r( imag, &h->imag[h->write_pos], (int16_t) num_bands ); + + h->write_pos += CLDFB_NO_CHANNELS_MAX; + if ( h->write_pos == h->capacity ) + { + h->write_pos = 0; + } + + if ( h->read_pos == h->write_pos ) + { + h->is_full = 1; + } + + return; +} + +void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ) +{ + CLDFB_RB_Front( h, real, imag, num_bands ); + + h->read_pos += CLDFB_NO_CHANNELS_MAX; + if ( h->read_pos == h->capacity ) + { + h->read_pos = 0; + } + + if ( h->is_full ) + { + h->is_full = 0; + } +} + +void CLDFB_RB_Front( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ) +{ + assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); + assert( !CLDFB_RB_IsEmpty( h ) ); + + mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); + mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); + + return; +} + +static uint32_t rb_num_floats( CLDFB_RING_BUFFER_HANDLE h ) +{ + uint16_t ret; + + if ( CLDFB_RB_IsFull( h ) ) + { + return h->capacity; + } + + if ( h->read_pos <= h->write_pos ) + { + ret = h->write_pos - h->read_pos; + } + else + { + /* wrap around */ + ret = h->write_pos + h->capacity - h->read_pos; + } + + return ret; +} + +int16_t CLDFB_RB_GetNthLastPushed( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, uint16_t n ) +{ + uint32_t offset; + offset = ( n + 1 ) * CLDFB_NO_CHANNELS_MAX; + + /* Trying to access column that was already popped, error */ + if ( rb_num_floats( h ) < offset ) + { + *p_real = NULL; + *p_imag = NULL; + return -1; + } + + if ( offset <= h->write_pos ) + { + *p_real = &h->real[h->write_pos - offset]; + *p_imag = &h->imag[h->write_pos - offset]; + } + else + { + /* wrap around */ + *p_real = &h->real[h->write_pos + h->capacity - offset]; + *p_imag = &h->imag[h->write_pos + h->capacity - offset]; + } + + return 0; +} + +int16_t CLDFB_RB_IsEmpty( CLDFB_RING_BUFFER_HANDLE h ) +{ + return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); +} + +int16_t CLDFB_RB_IsFull( CLDFB_RING_BUFFER_HANDLE h ) +{ + return h->is_full; +} + +uint16_t CLDFB_RB_Size( CLDFB_RING_BUFFER_HANDLE h ) +{ + return rb_num_floats( h ) / CLDFB_NO_CHANNELS_MAX; +} + +#endif diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h new file mode 100644 index 0000000000..cb276d7549 --- /dev/null +++ b/lib_dec/cldfb_ring_buffer.h @@ -0,0 +1,66 @@ +/****************************************************************************************************** + + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef CLDFB_RING_BUFFER_H +#define CLDFB_RING_BUFFER_H + +#include "ivas_error.h" +#include + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + +typedef struct CldfbRingBuffer* CLDFB_RING_BUFFER_HANDLE; + +ivas_error CLDFB_RB_Open( CLDFB_RING_BUFFER_HANDLE *ph, int16_t capacity_columns ); + +void CLDFB_RB_Close( CLDFB_RING_BUFFER_HANDLE *ph ); + +void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* imag, uint16_t num_bands ); + +void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ); + +void CLDFB_RB_Front( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ); + +/** + * Get pointers into the last nth pushed CLDFB column. + * TODO: describe n more precisely + */ +int16_t CLDFB_RB_GetNthLastPushed( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, uint16_t n ); + +int16_t CLDFB_RB_IsEmpty( CLDFB_RING_BUFFER_HANDLE h ); + +int16_t CLDFB_RB_IsFull( CLDFB_RING_BUFFER_HANDLE h ); + +uint16_t CLDFB_RB_Size( CLDFB_RING_BUFFER_HANDLE h ); + +#endif /* FIX_1119_SPLIT_RENDERING_VOIP */ +#endif diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index cfc4b0bc30..0b0ceecdd6 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2328,8 +2328,17 @@ void ivas_dirac_dec_render_sf( { for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RB_Push( + st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], + Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], + Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], + hSpatParamRendCom->num_freq_bands + ); +#else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hSpatParamRendCom->num_freq_bands ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hSpatParamRendCom->num_freq_bands ); +#endif } } } diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index 643781bfcc..77af926a1d 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -1820,8 +1820,17 @@ void ivas_param_mc_dec_render( { for ( ch = 0; ch < nchan_out_cldfb; ch++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RB_Push( + st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], + Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], + Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], + hParamMC->num_freq_bands + ); +#else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hParamMC->num_freq_bands ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hParamMC->num_freq_bands ); +#endif } } } diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index c06e8b3fa1..45fcd5f440 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -790,8 +790,17 @@ static void ivas_mc_paramupmix_dec_sf( { for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RB_Push( + st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], + Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], + Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], + maxBand + ); +#else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); +#endif } } } diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index beaf645fa0..c4d33ce425 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -751,9 +751,13 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( float data_separated_objects[BINAURAL_CHANNELS][L_FRAME48k]; ivas_error error; float *p_sepobj[BINAURAL_CHANNELS]; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *re, *im; +#else int16_t slot_idx_start; slot_idx_start = st_ivas->hSpatParamRendCom->slots_rendered; +#endif for ( n = 0; n < BINAURAL_CHANNELS; n++ ) { @@ -795,8 +799,15 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( cldfbAnalysis_ts( &( p_rend_obj[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + // TODO 1119: Double-check indexing here + CLDFB_RB_GetNthLastPushed( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, cldfb_slots - slot_idx - 1 ); + v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); + v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); +#else v_add( st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx], Cldfb_RealBuffer, st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx], num_cldfb_bands ); v_add( st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx], Cldfb_ImagBuffer, st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx], num_cldfb_bands ); +#endif } } } diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index afa273fdbb..c2b6548738 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -137,9 +137,13 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( float output_separated_objects[BINAURAL_CHANNELS][L_FRAME48k]; float *p_sepobj[BINAURAL_CHANNELS]; int16_t channel_offset; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *re, *im; +#else int16_t slot_idx_start; slot_idx_start = st_ivas->hSpatParamRendCom->slots_rendered; +#endif for ( n = 0; n < BINAURAL_CHANNELS; n++ ) { @@ -180,16 +184,27 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( { cldfbAnalysis_ts( &( output_f[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + // TODO 1119: Double-check indexing here + CLDFB_RB_GetNthLastPushed( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, cldfb_slots - slot_idx - 1 ); + + for ( b = 0; b < num_cldfb_bands; b++ ) + { + re[b] = 0.5f * re[b] + 0.5f * Cldfb_RealBuffer[b]; + im[b] = 0.5f * im[b] + 0.5f * Cldfb_ImagBuffer[b]; + } +#else for ( b = 0; b < num_cldfb_bands; b++ ) { - st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] = + st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] = ( 0.5f * st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] ) + ( 0.5f * Cldfb_RealBuffer[b] ); - st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] = + st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] = ( 0.5f * st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] ) + ( 0.5f * Cldfb_ImagBuffer[b] ); } +#endif } } } diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index efc5f736d6..eca95d41f9 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -40,6 +40,9 @@ #include "stat_dec.h" #include "ivas_stat_com.h" #include "ivas_stat_rend.h" +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +#include "cldfb_ring_buffer.h" +#endif /*----------------------------------------------------------------------------------* @@ -821,12 +824,14 @@ typedef struct renderer_struct * IVAS decoder specific ISAR wrapper structures *----------------------------------------------------------------------------------*/ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP typedef struct { float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; } ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA, *ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE; +#endif typedef struct { @@ -843,7 +848,11 @@ typedef struct typedef struct { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RING_BUFFER_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; +#else ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE hMultiBinCldfbData; /*scratch buffer for frame by frame processing*/ +#endif ISAR_SPLIT_REND_BITS_HANDLE hSplitRendBits; /*scratch buffer for frame by frame processing*/ SPLIT_REND_WRAPPER splitrend; ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA_HANDLE hCldfbDataOut; /*buffer to store cldfb data before binauralization*/ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 781036b82f..5854b58195 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1874,8 +1874,6 @@ static ivas_error isar_render_poses( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ float** p_head_pose_buf, - float *p_Cldfb_RealBuffer_Binaural[][CLDFB_NO_COL_MAX], - float *p_Cldfb_ImagBuffer_Binaural[][CLDFB_NO_COL_MAX], int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) @@ -1886,7 +1884,6 @@ static ivas_error isar_render_poses( ivas_error error; ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; int16_t numPoses; - int16_t slots_rendered, slots_rendered_new; error = IVAS_ERR_OK; st_ivas = hIvasDec->st_ivas; @@ -1907,23 +1904,6 @@ static ivas_error isar_render_poses( set_zero( (float *) hIvasDec->flushbuffer, numPoses * BINAURAL_CHANNELS * hIvasDec->nSamplesFrame / IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ); } - if ( st_ivas->hTcBuffer == NULL || hIvasDec->hasBeenFedFrame ) - { - slots_rendered = 0; - } - else - { - /* this is needed for OMASA-DISC, because the td-rend granularity is 240 samples at 48kHz, leading to wrong slot count. */ - if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC ) - { - slots_rendered = st_ivas->hTcBuffer->n_samples_rendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); - } - else - { - slots_rendered = st_ivas->hTcBuffer->n_samples_rendered / st_ivas->hTcBuffer->n_samples_granularity; - } - } - /* render */ if ( ( error = IVAS_DEC_GetSamplesRenderer( hIvasDec, nSamplesAsked, IVAS_DEC_PCM_FLOAT, pcmBuf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) { @@ -1945,40 +1925,12 @@ static ivas_error isar_render_poses( } } - if ( st_ivas->hTcBuffer == NULL ) - { - slots_rendered_new = 0; - } - else - { - /* this is needed for OMASA-DISC, because the td-rend granularity is 240 samples at 48kHz, leading to wrong slot count. */ - if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC ) - { - slots_rendered_new = st_ivas->hTcBuffer->n_samples_rendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); - } - else - { - slots_rendered_new = st_ivas->hTcBuffer->n_samples_rendered / st_ivas->hTcBuffer->n_samples_granularity; - } - } - - for ( i = 0; i < BINAURAL_CHANNELS * numPoses; ++i ) - { - for ( j = slots_rendered; j < slots_rendered_new; ++j ) - { - mvr2r( hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[i][j], p_Cldfb_RealBuffer_Binaural[i][j - slots_rendered], CLDFB_NO_CHANNELS_MAX ); - mvr2r( hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[i][j], p_Cldfb_ImagBuffer_Binaural[i][j - slots_rendered], CLDFB_NO_CHANNELS_MAX ); - } - } - return error; } static ivas_error isar_generate_metadata_and_bitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ float** p_head_pose_buf, - float *p_Cldfb_RealBuffer_Binaural[][CLDFB_NO_COL_MAX], - float *p_Cldfb_ImagBuffer_Binaural[][CLDFB_NO_COL_MAX], ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { @@ -1992,6 +1944,12 @@ static ivas_error isar_generate_metadata_and_bitstream( int16_t td_input; int16_t ro_md_flag; IVAS_QUATERNION Quaternion; + int16_t i, j, num_poses; + // TODO 1119: If data could be read directly from the ring buffer, these large buffers could be removed + float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; + float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; error = IVAS_ERR_OK; st_ivas = hIvasDec->st_ivas; @@ -2003,6 +1961,27 @@ static ivas_error isar_generate_metadata_and_bitstream( pcm_out_flag = ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; td_input = st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC_ROOM && st_ivas->renderer_type != RENDERER_STEREO_PARAMETRIC; + if ( !td_input ) + { + num_poses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; + + for ( i = 0; i < (int16_t) ( BINAURAL_CHANNELS * num_poses ); ++i ) + { + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + { + CLDFB_RB_Pop( + hSplitBinRend->hMultiBinCldfbData[i], + Cldfb_RealBuffer_Binaural[i][j], + Cldfb_ImagBuffer_Binaural[i][j], + CLDFB_NO_CHANNELS_MAX + ); + + p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; + p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; + } + } + } + if ( st_ivas->hBinRendererTd != NULL ) { ro_md_flag = 1; @@ -2097,7 +2076,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( } } - if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, nOutSamples, needNewFrame)) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame)) != IVAS_ERR_OK ) { return error; } @@ -2107,7 +2086,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( return IVAS_ERR_OK; } - if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, splitRendBits ) ) ) + if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ) ) ) { return error; } @@ -3762,14 +3741,9 @@ ivas_error IVAS_DEC_VoIP_GetSamples uint8_t nOutChannels; #ifdef FIX_1119_SPLIT_RENDERING_VOIP - int32_t i, j; - int16_t nSlotsRendered; + int32_t i; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; - float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; #endif if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->hVoIP == NULL ) @@ -3965,22 +3939,14 @@ ivas_error IVAS_DEC_VoIP_GetSamples #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { - nSlotsRendered = *nSamplesRendered / NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); - /* Move output pointers forward */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { p_head_pose_buf[i] = &head_pose_buf[i][*nSamplesRendered]; - - for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) - { - p_Cldfb_RealBuffer_Binaural[i][j] = &Cldfb_RealBuffer_Binaural[i][j][nSlotsRendered]; - p_Cldfb_ImagBuffer_Binaural[i][j] = &Cldfb_ImagBuffer_Binaural[i][j][nSlotsRendered]; - } } /* Render head poses from time-scaled transport channels */ - if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, &nSamplesRendered_loop, &tmp )) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, &nSamplesRendered_loop, &tmp )) != IVAS_ERR_OK ) { return error; } @@ -4012,16 +3978,10 @@ ivas_error IVAS_DEC_VoIP_GetSamples for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { p_head_pose_buf[i] = head_pose_buf[i]; - - for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) - { - p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; - p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; - } } /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ - if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, splitRendBits ) ) ) + if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ) ) ) { return error; } @@ -5242,6 +5202,9 @@ static ivas_error ivas_create_handle_isar( ) { ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int16_t i; +#endif if ( ( hSplitBinRend = (ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE) malloc( sizeof( ISAR_DEC_SPLIT_REND_WRAPPER ) ) ) == NULL ) { @@ -5250,7 +5213,14 @@ static ivas_error ivas_create_handle_isar( isar_init_split_rend_handles( &hSplitBinRend->splitrend ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) + { + hSplitBinRend->hMultiBinCldfbData[i] = NULL; + } +#else hSplitBinRend->hMultiBinCldfbData = NULL; +#endif hSplitBinRend->hCldfbDataOut = NULL; hSplitBinRend->numTdSamplesPerChannelCached = 0; @@ -5270,10 +5240,24 @@ static void ivas_destroy_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *hSplitBinRend /* i/o: ISAR split binaural rendering handle */ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int16_t i; +#endif + if ( *hSplitBinRend != NULL ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) + { + if ( ( *hSplitBinRend )->hMultiBinCldfbData[i] != NULL ) + { + CLDFB_RB_Close( &( *hSplitBinRend )->hMultiBinCldfbData[i] ); + } + } +#else free( ( *hSplitBinRend )->hMultiBinCldfbData ); ( *hSplitBinRend )->hMultiBinCldfbData = NULL; +#endif ISAR_PRE_REND_close( &( *hSplitBinRend )->splitrend, NULL ); @@ -5458,6 +5442,9 @@ static ivas_error ivas_dec_init_split_rend( ivas_error error; int16_t cldfb_in_flag, pcm_out_flag; int16_t mixed_td_cldfb_flag; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int16_t i, num_poses; +#endif pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; cldfb_in_flag = 0; @@ -5470,6 +5457,24 @@ static ivas_error ivas_dec_init_split_rend( cldfb_in_flag = 1; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ISAR_PRE_REND_GetMultiBinPoseData( &st_ivas->hRenderConfig->split_rend_config, &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, ( st_ivas->hHeadTrackData != NULL ) ? st_ivas->hHeadTrackData->sr_pose_pred_axis : DEFAULT_AXIS ); + num_poses = st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; + assert( num_poses <= MAX_HEAD_ROT_POSES ); + + if ( cldfb_in_flag ) + { + for ( i = 0; i < (int16_t) ( num_poses * BINAURAL_CHANNELS ); ++i ) + { + /* note: this is intra-frame heap memory */ + error = CLDFB_RB_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], 2 * CLDFB_NO_COL_MAX ); /* TODO: 2x space to fit scaled frames is only needed in VoIP mode */ + if ( error != IVAS_ERR_OK ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); + } + } + } +#else /* note: this is intra-frame heap memory */ if ( ( st_ivas->hSplitBinRend->hMultiBinCldfbData = (ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE) malloc( sizeof( ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA ) ) ) == NULL ) { @@ -5477,6 +5482,7 @@ static ivas_error ivas_dec_init_split_rend( } ISAR_PRE_REND_GetMultiBinPoseData( &st_ivas->hRenderConfig->split_rend_config, &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, ( st_ivas->hHeadTrackData != NULL ) ? st_ivas->hHeadTrackData->sr_pose_pred_axis : DEFAULT_AXIS ); +#endif if ( cldfb_in_flag == 1 && ( st_ivas->hSplitBinRend->splitrend.multiBinPoseData.poseCorrectionMode == ISAR_SPLIT_REND_POSE_CORRECTION_MODE_NONE ) ) { diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index 0e8c7ad50c..22807d1300 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -282,8 +282,8 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural ( int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ - float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ + float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ + float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ #else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB real buffer */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB imag buffer */ diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index cc1bccb131..d23133d30e 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -797,8 +797,17 @@ static void ivas_dirac_dec_binaural_internal( { for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RB_Push( + st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], + tmp_Cldfb_out_re[ch][i], + tmp_Cldfb_out_im[ch][i], + CLDFB_NO_CHANNELS_MAX + ); +#else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); +#endif } } } @@ -866,8 +875,17 @@ static void ivas_dirac_dec_binaural_internal( { for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + CLDFB_RB_Push( + st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], + tmp_Cldfb_out_re[ch][i], + tmp_Cldfb_out_im[ch][i], + CLDFB_NO_CHANNELS_MAX + ); +#else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); +#endif } } -- GitLab From a667e45c2ff5bb47f6da54f071a901b8d551c08e Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 11 Jun 2025 10:50:59 +0200 Subject: [PATCH 024/340] Remove unused variables --- lib_dec/lib_dec.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 5854b58195..541727b364 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -2041,11 +2041,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( ivas_error error; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; - float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; - int32_t i, j; + int32_t i; int16_t pcm_out_flag; int16_t numSamplesPerChannelToOutput; @@ -2068,12 +2064,6 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { p_head_pose_buf[i] = head_pose_buf[i]; - - for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) - { - p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; - p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; - } } if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame)) != IVAS_ERR_OK ) -- GitLab From c7da622a031463cd16759d5a8e99285f3c4659a9 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 12 Jun 2025 15:02:22 +0300 Subject: [PATCH 025/340] Remove disable headtracking reader files from MSVC project --- Workspace_msvc/lib_util.vcxproj | 2 -- Workspace_msvc/lib_util.vcxproj.filters | 6 ------ 2 files changed, 8 deletions(-) diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 28832eb4f0..94561b175a 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -108,7 +108,6 @@ - @@ -139,7 +138,6 @@ - diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index f3824f1fe4..afaedfb5da 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -82,9 +82,6 @@ util_c - - util_c - util_c @@ -177,9 +174,6 @@ util_h - - util_h - util_h -- GitLab From d8f66c1b4ecc0068970c4395df23da166f0ca08a Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 12 Jun 2025 15:33:38 +0300 Subject: [PATCH 026/340] Remove enable/disable headtracking PI data --- apps/encoder.c | 58 ------- lib_com/common_api_types.h | 2 - lib_dec/lib_dec.c | 31 ---- lib_dec/lib_dec.h | 5 - lib_rend/ivas_rotation.c | 25 +-- lib_rend/ivas_stat_rend.h | 3 - lib_util/disable_headtracking_file_reader.c | 169 -------------------- lib_util/disable_headtracking_file_reader.h | 86 ---------- lib_util/ivas_rtp_payload.c | 74 +-------- lib_util/ivas_rtp_payload.h | 24 --- readme.txt | 1 - 11 files changed, 5 insertions(+), 473 deletions(-) delete mode 100644 lib_util/disable_headtracking_file_reader.c delete mode 100644 lib_util/disable_headtracking_file_reader.h diff --git a/apps/encoder.c b/apps/encoder.c index 13dd477363..fe444c4d11 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -39,7 +39,6 @@ #include "jbm_file_reader.h" #include "masa_file_reader.h" #ifdef IVAS_RTPDUMP -#include "disable_headtracking_file_reader.h" #include "evs_rtp_payload.h" #include "ivas_rtp_payload.h" #include "rotation_file_reader.h" @@ -156,7 +155,6 @@ typedef struct bool rtpdumpOutput; char *sceneOrientationTrajFileName; char *deviceOrientationTrajFileName; - char *disableHeadtrackingFileName; #endif } EncArguments; @@ -204,7 +202,6 @@ int main( #ifdef IVAS_RTPDUMP RotFileReader *sceneOrientationFileReader = NULL; RotFileReader *deviceOrientationFileReader = NULL; - DisableHeadtrackingFileReader *disableHeadtrackingFileReader = NULL; #endif #ifdef DEBUGGING FILE *f_forcedModeProfile = NULL; @@ -668,19 +665,6 @@ int main( goto cleanup; } } - - /*------------------------------------------------------------------------------------------* - * Open disable headtracking file - *------------------------------------------------------------------------------------------*/ - - if ( arg.disableHeadtrackingFileName != NULL ) - { - if ( ( error = DisableHeadtrackingFileReader_open( arg.disableHeadtrackingFileName, &disableHeadtrackingFileReader ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError: Can't open disable headtracking file %s \n\n", arg.disableHeadtrackingFileName ); - goto cleanup; - } - } #endif int16_t numSamplesRead = 0; @@ -878,17 +862,6 @@ int main( rtpdumpPacker->piDataPacker.deviceOrientationPresent = true; } - /* disable headtracking */ - if ( disableHeadtrackingFileReader ) - { - if ( ( error = DisableHeadtrackingFileReading( disableHeadtrackingFileReader, &rtpdumpPacker->piDataPacker.disableHeadtracking ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while reading disable headtracking data from %s\n", IVAS_ENC_GetErrorMessage( error ), DisableHeadtrackingFileReader_getFilePath( disableHeadtrackingFileReader ) ); - goto cleanup; - } - IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( rtpdumpPacker ); - } - /* PI presence */ IVAS_RTPDUMP_PACKER_determinePIpresence( rtpdumpPacker ); @@ -1007,11 +980,6 @@ cleanup: { RotationFileReader_close( &deviceOrientationFileReader ); } - - if ( disableHeadtrackingFileReader ) - { - DisableHeadtrackingFileReader_close( &disableHeadtrackingFileReader ); - } #endif IVAS_ENC_Close( &hIvasEnc ); @@ -1088,7 +1056,6 @@ static bool parseCmdlIVAS_enc( arg->rtpdumpOutput = false; arg->sceneOrientationTrajFileName = NULL; arg->deviceOrientationTrajFileName = NULL; - arg->disableHeadtrackingFileName = NULL; #endif #ifdef DEBUGGING @@ -1932,24 +1899,6 @@ static bool parseCmdlIVAS_enc( i++; } - /*-----------------------------------------------------------------* - * Disable headtracking file reading - *-----------------------------------------------------------------*/ - - else if ( strcmp( argv_to_upper, "-DISABLE_HEADTRACKING_FILE" ) == 0 ) - { - i++; - if ( argc - i <= 4 || argv[i][0] == '-' ) - { - fprintf( stderr, "Error: Disable headtracking input file name not specified!\n\n" ); - usage_enc(); - return false; - } - - arg->disableHeadtrackingFileName = argv[i]; - i++; - } - #endif /*-----------------------------------------------------------------* * Option not recognized @@ -1975,12 +1924,6 @@ static bool parseCmdlIVAS_enc( usage_enc(); return false; } - if ( arg->disableHeadtrackingFileName != NULL && arg->rtpdumpOutput == false ) - { - fprintf( stderr, "Error: Disable headtracking file reading is only enabled with rtpdump output!\n\n" ); - usage_enc(); - return false; - } #endif /*-----------------------------------------------------------------* @@ -2196,7 +2139,6 @@ static void usage_enc( void ) fprintf( stdout, " EVS RTP Payload Format is used. \n" ); fprintf( stdout, "-scene_orientation : Scene orientation trajectory file. Only used with rtpdump output.\n" ); fprintf( stdout, "-device_orientation : Device orientation trajectory file. Only used with rtpdump output.\n" ); - fprintf( stdout, "-disable_headtracking_file : Disable headtracking input file. (1) to disable and (0) to enable headtracking. Only used with rtpdump output.\n" ); #endif fprintf( stdout, "\n" ); diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index 866229f11c..d711365703 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -226,8 +226,6 @@ typedef struct char *acousticEnvironmentId; char *acousticEnvironmentOnlyLateReverb; char *acousticEnvironmentLateReverbAndEarlyReflections; - bool enableHeadTracking; - bool disableHeadTracking; } PI_DATA_CURRENT_FRAME; typedef struct diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index aeaf61caa3..0ef490fcc7 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3784,34 +3784,6 @@ ivas_error IVAS_DEC_parseSinglePIorientation( } -/*---------------------------------------------------------------------* - * IVAS_DEC_parsePIenableDisableHeadtracking( ) - * - * Parse enable/disable headtracking from PI data - *---------------------------------------------------------------------*/ - -void IVAS_DEC_parsePIenableDisableHeadtracking( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ -) -{ - if ( PIdataCurrentFrame->enableHeadTracking ) - { - if ( hIvasDec->st_ivas->hCombinedOrientationData ) - { - hIvasDec->st_ivas->hCombinedOrientationData->enableHeadTrackingPI = 1; - } - } - if ( PIdataCurrentFrame->disableHeadTracking ) - { - if ( hIvasDec->st_ivas->hCombinedOrientationData ) - { - hIvasDec->st_ivas->hCombinedOrientationData->enableHeadTrackingPI = 0; - } - } -} - - /*---------------------------------------------------------------------* * IVAS_DEC_parsePIdata( ) * @@ -3838,9 +3810,6 @@ ivas_error IVAS_DEC_parsePIdata( return error; } - /* enable/disable headtracking */ - IVAS_DEC_parsePIenableDisableHeadtracking( hIvasDec, PIdataCurrentFrame ); - return error; } diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 2170cd94a7..f71ff96e7c 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -337,11 +337,6 @@ ivas_error IVAS_DEC_parseSinglePIorientation( IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ ); -void IVAS_DEC_parsePIenableDisableHeadtracking( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* i : PI data handle for the current frame */ -); - ivas_error IVAS_DEC_parsePIdata( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* i : PI data handle for the current frame */ diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 93c0cd0996..378736e650 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -862,9 +862,6 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->subframe_idx = 0; ( *hCombinedOrientationData )->subframe_size = (int16_t) ( fs / ( FRAMES_PER_SEC * MAX_PARAM_SPATIAL_SUBFRAMES ) ); ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; -#ifdef IVAS_RTPDUMP - ( *hCombinedOrientationData )->enableHeadTrackingPI = 1; -#endif return IVAS_ERR_OK; } @@ -1026,24 +1023,10 @@ ivas_error combine_external_and_head_orientations( else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { /* Head rotation only */ -#ifdef IVAS_RTPDUMP - if ( hCombinedOrientationData->enableHeadTrackingPI == 1 ) - { -#endif - for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) - { - hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; - } -#ifdef IVAS_RTPDUMP - } - else + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { - for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) - { - hCombinedOrientationData->Quaternions[i] = identity; - } + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; } -#endif } if ( hExtOrientationData != NULL ) @@ -1121,11 +1104,7 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->isHeadRotationFrozen = 0; } /* Use the most recent head rotation */ -#ifdef IVAS_RTPDUMP - if ( hExtOrientationData->enableHeadRotation[i] == 1 && hCombinedOrientationData->enableHeadTrackingPI == 1 ) -#else if ( hExtOrientationData->enableHeadRotation[i] == 1 ) -#endif { if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) { diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index 906f5e157d..e990c336f6 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -710,9 +710,6 @@ typedef struct ivas_combined_orientation_struct int16_t cur_subframe_samples_rendered; int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; -#ifdef IVAS_RTPDUMP - int8_t enableHeadTrackingPI; -#endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; /*----------------------------------------------------------------------------------* diff --git a/lib_util/disable_headtracking_file_reader.c b/lib_util/disable_headtracking_file_reader.c deleted file mode 100644 index 992464a6f9..0000000000 --- a/lib_util/disable_headtracking_file_reader.c +++ /dev/null @@ -1,169 +0,0 @@ -/****************************************************************************************************** - - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. - -*******************************************************************************************************/ - - -#include "disable_headtracking_file_reader.h" -#include -#include -#include -#include "prot.h" - -#ifdef IVAS_RTPDUMP - -struct DisableHeadtrackingFileReader -{ - FILE *inputFile; - int32_t frameCounter; - char *file_path; - bool fileRewind; -}; - - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_open() - * - * Allocate and initialize file reader - *-----------------------------------------------------------------------*/ - -ivas_error DisableHeadtrackingFileReader_open( - char *filePath, /* i : disable-headtracking input file name */ - DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ -) -{ - DisableHeadtrackingFileReader *self; - FILE *inputFile; - - /* Open trajectory file */ - if ( strlen( filePath ) < 1 ) - { - return IVAS_ERR_FAILED_FILE_OPEN; - } - - inputFile = fopen( filePath, "r" ); - - if ( !inputFile ) - { - return IVAS_ERR_FAILED_FILE_OPEN; - } - - self = calloc( sizeof( DisableHeadtrackingFileReader ), 1 ); - self->inputFile = inputFile; - self->frameCounter = 0; - self->file_path = calloc( sizeof( char ), strlen( filePath ) + 1 ); - strcpy( self->file_path, filePath ); - self->fileRewind = false; - - *fileReader = self; - - return IVAS_ERR_OK; -} - - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReading() - * - * Read values from the input file. (1) to disable and (0) to enable headtracking. - *-----------------------------------------------------------------------*/ - -ivas_error DisableHeadtrackingFileReading( - DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ - bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ -) -{ - float disableHeadTrackingFlag; - int32_t read_values; - - /* Initial values, if they are not read from the file (enable headtracking by default) */ - disableHeadTrackingFlag = 0; - - read_values = fscanf( fileReader->inputFile, "%f", &disableHeadTrackingFlag ); - if ( ( read_values != 1 ) ) - { - if ( feof( fileReader->inputFile ) ) - { - rewind( fileReader->inputFile ); - fileReader->fileRewind = true; - return DisableHeadtrackingFileReading( fileReader, pHeadTrackingDisabled ); - } - return IVAS_ERR_FAILED_FILE_PARSE; - } - - ( fileReader->frameCounter )++; - - *pHeadTrackingDisabled = (int8_t) disableHeadTrackingFlag == 1 ? true : false; - - return IVAS_ERR_OK; -} - - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_close() - * - * Deallocates memory for the file reader - *-----------------------------------------------------------------------*/ - -void DisableHeadtrackingFileReader_close( - DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ -) -{ - if ( fileReader == NULL || *fileReader == NULL ) - { - return; - } - - fclose( ( *fileReader )->inputFile ); - free( ( *fileReader )->file_path ); - free( *fileReader ); - *fileReader = NULL; - - return; -} - - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_getFilePath() - * - * - *-----------------------------------------------------------------------*/ - -const char *DisableHeadtrackingFileReader_getFilePath( - DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ -) -{ - if ( fileReader == NULL ) - { - return NULL; - } - - return fileReader->file_path; -} -#endif diff --git a/lib_util/disable_headtracking_file_reader.h b/lib_util/disable_headtracking_file_reader.h deleted file mode 100644 index 1dda351565..0000000000 --- a/lib_util/disable_headtracking_file_reader.h +++ /dev/null @@ -1,86 +0,0 @@ -/****************************************************************************************************** - - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. - -*******************************************************************************************************/ - -#ifndef IVAS_DISABLE_HEADTRACKING_FILE_READER_H -#define IVAS_DISABLE_HEADTRACKING_FILE_READER_H - -#include -#include "common_api_types.h" - -#ifdef IVAS_RTPDUMP - -typedef struct DisableHeadtrackingFileReader DisableHeadtrackingFileReader; - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_open() - * - * Allocate and initialize reader handle - *-----------------------------------------------------------------------*/ - -ivas_error DisableHeadtrackingFileReader_open( - char *filePath, /* i : disable-headtracking input file name */ - DisableHeadtrackingFileReader **fileReader /* o : DisableHeadtrackingFileReader handle */ -); - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReading() - * - * Read values from the input file. (1) to disable and (0) to enable headtracking. - *-----------------------------------------------------------------------*/ - -ivas_error DisableHeadtrackingFileReading( - DisableHeadtrackingFileReader *fileReader, /* i/o: DisableHeadtrackingFileReader handle */ - bool *pHeadTrackingDisabled /* o : enable/disable headtracking data */ -); - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_close() - * - * Deallocates memory for the reader handle - *-----------------------------------------------------------------------*/ - -void DisableHeadtrackingFileReader_close( - DisableHeadtrackingFileReader **fileReader /* i/o: DisableHeadtrackingFileReader handle */ -); - -/*-----------------------------------------------------------------------* - * DisableHeadtrackingFileReader_getFilePath() - * - * - *-----------------------------------------------------------------------*/ - -const char *DisableHeadtrackingFileReader_getFilePath( - DisableHeadtrackingFileReader *fileReader /* i/o: DisableHeadtrackingFileReader handle */ -); -#endif - -#endif /* IVAS_DISABLE_HEADTRACKING_FILE_READER_H */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 22d160cebd..467de66eb9 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -147,14 +147,6 @@ static void ivasPayload_parsePItype( PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = PIframeDataBeginning + parsedPIframeSizeBytes; } } - else if ( PIframeType == PI_ENABLE_HEADTRACKING ) - { - PIdataCurrentFrame->enableHeadTracking = true; - } - else if ( PIframeType == PI_DISABLE_HEADTRACKING ) - { - PIdataCurrentFrame->disableHeadTracking = true; - } else if ( PIframeType == PI_NO_DATA ) { /* no data */ @@ -372,8 +364,6 @@ void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) PIdataCurrentFrame->acousticEnvironmentId = NULL; PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = NULL; PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = NULL; - PIdataCurrentFrame->enableHeadTracking = false; - PIdataCurrentFrame->disableHeadTracking = false; } @@ -696,9 +686,6 @@ IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( self->piDataPacker.deviceOrientationQuat = identity; self->piDataPacker.sceneOrientationPresent = false; self->piDataPacker.deviceOrientationPresent = false; - self->piDataPacker.enableHeadtracking = false; - self->piDataPacker.disableHeadtracking = false; - self->piDataPackerState.previousDisableHeadtrackingState = false; self->piDataPresent = false; *rtpdumpPacker = self; return IVAS_RTPDUMP_PACKER_NO_ERROR; @@ -962,7 +949,7 @@ void IVAS_RTPDUMP_PACKER_writePIdata( /* scene orientation */ if ( self->piDataPacker.sceneOrientationPresent ) { - if ( self->piDataPacker.deviceOrientationPresent || self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) + if ( self->piDataPacker.deviceOrientationPresent ) { somePIfollowing = true; } @@ -981,40 +968,11 @@ void IVAS_RTPDUMP_PACKER_writePIdata( /* device orientation */ if ( self->piDataPacker.deviceOrientationPresent ) { - if ( self->piDataPacker.enableHeadtracking || self->piDataPacker.disableHeadtracking ) - { - somePIfollowing = true; - } - else - { - somePIfollowing = false; - } - if ( somePIfollowing ) - { - PIheaderByte = ( PF0 << 7 ) | ( PM_NOT_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; - } - else - { - PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; - } + PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; writeByteToRtp( self, (char) PIheaderByte ); writeByteToRtp( self, PI_DEVICE_ORIENTATION_UNCOMPENSATED_SIZE_BYTES ); } - /* enable/disable headtracking */ - if ( self->piDataPacker.enableHeadtracking && !self->piDataPacker.disableHeadtracking ) - { - PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_ENABLE_HEADTRACKING; - writeByteToRtp( self, (char) PIheaderByte ); - writeByteToRtp( self, PI_ENABLE_HEADTRACKING_SIZE_BYTES ); - } - if ( self->piDataPacker.disableHeadtracking ) - { - PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DISABLE_HEADTRACKING; - writeByteToRtp( self, (char) PIheaderByte ); - writeByteToRtp( self, PI_DISABLE_HEADTRACKING_SIZE_BYTES ); - } - /* PI frame data section */ if ( self->piDataPacker.sceneOrientationPresent ) @@ -1044,37 +1002,13 @@ void IVAS_RTPDUMP_PACKER_writePIdata( } } -void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ -) -{ - if ( self->piDataPacker.disableHeadtracking == self->piDataPackerState.previousDisableHeadtrackingState ) - { - /* same state as before, do not send PI data */ - self->piDataPacker.disableHeadtracking = false; - } - else if ( self->piDataPacker.disableHeadtracking && self->piDataPackerState.previousDisableHeadtrackingState == false ) - { - /* headtracking was enabled, send disable headtracking PI data */ - self->piDataPackerState.previousDisableHeadtrackingState = true; - } - else if ( !self->piDataPacker.disableHeadtracking && self->piDataPackerState.previousDisableHeadtrackingState == true ) - { - /* headtracking was disabled, send enable headtracking PI data */ - self->piDataPackerState.previousDisableHeadtrackingState = false; - self->piDataPacker.enableHeadtracking = true; - } -} - void IVAS_RTPDUMP_PACKER_determinePIpresence( IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ ) { self->piDataPresent = false; if ( self->piDataPacker.sceneOrientationPresent || - self->piDataPacker.deviceOrientationPresent || - self->piDataPacker.enableHeadtracking || - self->piDataPacker.disableHeadtracking ) + self->piDataPacker.deviceOrientationPresent ) { self->piDataPresent = true; } @@ -1095,8 +1029,6 @@ void IVAS_RTPDUMP_PACKER_resetPIdata( self->piDataPacker.deviceOrientationQuat = identity; self->piDataPacker.sceneOrientationPresent = false; self->piDataPacker.deviceOrientationPresent = false; - self->piDataPacker.enableHeadtracking = false; - self->piDataPacker.disableHeadtracking = false; } void IVAS_RTPDUMP_PACKER_close( diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index bbe939e036..9d8bad958f 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -89,8 +89,6 @@ typedef enum PI_DEVICE_ORIENTATION_COMPENSATED = 1, PI_DEVICE_ORIENTATION_UNCOMPENSATED = 2, PI_ACOUSTIC_ENVIRONMENT = 3, - PI_ENABLE_HEADTRACKING = 4, - PI_DISABLE_HEADTRACKING = 5, PI_NO_DATA = 31 } PI_TYPE; @@ -102,8 +100,6 @@ typedef enum PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES = 1, PI_ACOUSTIC_ENVIRONMENT_ONLY_LATE_REVERB_SIZE_BYTES = 5, PI_ACOUSTIC_ENVIRONMENT_LATE_REVERB_AND_EARLY_REFLECTIONS_SIZE_BYTES = 8, - PI_ENABLE_HEADTRACKING_SIZE_BYTES = 0, - PI_DISABLE_HEADTRACKING_SIZE_BYTES = 0, PI_NO_DATA_SIZE_BYTES = 0 } PI_SIZE_BYTES; @@ -113,15 +109,8 @@ typedef struct IVAS_QUATERNION deviceOrientationQuat; bool sceneOrientationPresent; bool deviceOrientationPresent; - bool enableHeadtracking; - bool disableHeadtracking; } PI_DATA_PACKER; -typedef struct -{ - bool previousDisableHeadtrackingState; -} PI_DATA_PACKER_STATE; - typedef struct { uint16_t w, x, y, z; @@ -211,7 +200,6 @@ typedef struct RTPDUMP_RTPPACKET rtpPacket; uint32_t timeoffset_ms; PI_DATA_PACKER piDataPacker; - PI_DATA_PACKER_STATE piDataPackerState; bool piDataPresent; int32_t writingIndex; } IVAS_RTPDUMP_PACKER; @@ -370,18 +358,6 @@ void IVAS_RTPDUMP_PACKER_writePIdata( ); -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState() - * - * Check for disable headtracking input and previously set disable/enable headtracking state. - * Set the current PI headtracking state accordingly. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_checkPIdisableHeadtrackingState( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ -); - - /*-----------------------------------------------------------------------* * IVAS_RTPDUMP_PACKER_determinePIpresence() * diff --git a/readme.txt b/readme.txt index a4bdba47c7..d9a8e5b414 100644 --- a/readme.txt +++ b/readme.txt @@ -255,7 +255,6 @@ EVS mono is default, for IVAS choose one of the following: -stereo, -ism, -sba, EVS RTP Payload Format is used. -scene_orientation : Scene orientation trajectory file. Only used with rtpdump output. -device_orientation : Device orientation trajectory file. Only used with rtpdump output. --disable_headtracking_file : Disable headtracking input file. (1) to disable and (0) to enable headtracking. Only used with rtpdump output. The usage of the "IVAS_dec" program is as follows: -- GitLab From 290e40eac6ba866e0df9458371b1f01dbbbe6eb3 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Fri, 13 Jun 2025 14:04:59 +0300 Subject: [PATCH 027/340] Restructure code, move PI data parsing to utils from lib_dec --- apps/decoder.c | 7 +- lib_com/ivas_prot.h | 9 -- lib_dec/lib_dec.c | 58 +++------ lib_dec/lib_dec.h | 14 +- lib_util/ivas_rtp_payload.c | 252 +++++++++++++++++++++--------------- lib_util/ivas_rtp_payload.h | 3 +- 6 files changed, 171 insertions(+), 172 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 61f72dd493..cb2e08233f 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3384,14 +3384,13 @@ static ivas_error decodeVoIP( /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; - /* parse PI data */ - error = IVAS_DEC_parsePIdata( hIvasDec, &rtpdumpDepacker.PIdataCurrentFrame, &rtpdumpDepacker.PIdataDepackerState ); + /* feed PI data to decoder handle */ + error = IVAS_DEC_feedPIdata( hIvasDec, &rtpdumpDepacker.PIdataDepackerState ); if ( error != IVAS_ERR_OK ) { - fprintf( stderr, "\nError in IVAS_DEC_parsePIdata: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + fprintf( stderr, "\nError in IVAS_DEC_feedPIdata: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } - IVAS_DEC_resetPIdataCurrentFrame( &rtpdumpDepacker.PIdataCurrentFrame ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index 983f75e8f0..80232e5800 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -821,15 +821,6 @@ void QuaternionInverse( IVAS_QUATERNION *const r ); -void resetPIdataCurrentFrame( - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ -); - -bool ivasPayload_parseOrientationPIdata( - char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ - IVAS_QUATERNION *q /* o : Parsed quaternion value */ -); - #endif /*----------------------------------------------------------------------------------* * JBM prototypes diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0ef490fcc7..d3ada6dd9c 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3723,24 +3723,23 @@ ivas_error IVAS_DEC_Flush( #ifdef IVAS_RTPDUMP /*---------------------------------------------------------------------* - * IVAS_DEC_parseSinglePIorientation( ) + * IVAS_DEC_feedSinglePIorientation( ) * - * Parse a single orientation from PI data and feed to external orientation handle. - * Save the parsed (inverted) orientation value and use in processing in future frames, if no new PI orientation is received. + * Feed a single orientation PI data to external orientation handle. *---------------------------------------------------------------------*/ -ivas_error IVAS_DEC_parseSinglePIorientation( +ivas_error IVAS_DEC_feedSinglePIorientation( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - char *orientation, /* i : input PI orientation */ - bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ + const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ ) { int16_t i; ivas_error error = IVAS_ERR_OK; - if ( orientation ) + IVAS_QUATERNION savedInvOrientation; + + if ( *isOrientationSaved ) { - IVAS_QUATERNION orientationQuat, orientationQuatInv; if ( !hIvasDec->st_ivas->hExtOrientationData ) { if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) @@ -3757,24 +3756,12 @@ ivas_error IVAS_DEC_parseSinglePIorientation( } } - /* read the orientation PI value */ - if ( !ivasPayload_parseOrientationPIdata( orientation, &orientationQuat ) ) - { - return IVAS_ERROR( IVAS_ERR_INTERNAL, "Parsing PI orientation failed" ); - } - QuaternionInverse( orientationQuat, &orientationQuatInv ); + QuaternionInverse( *savedOrientation, &savedInvOrientation ); - /* update the saved orientation value */ - *isOrientationSaved = true; - *savedInvOrientation = orientationQuatInv; - } - - if ( *isOrientationSaved ) - { /* use the new PI orientation or the previously saved orientation in processing */ for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) { - QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], *savedInvOrientation, + QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], savedInvOrientation, &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; } @@ -3785,27 +3772,26 @@ ivas_error IVAS_DEC_parseSinglePIorientation( /*---------------------------------------------------------------------* - * IVAS_DEC_parsePIdata( ) + * IVAS_DEC_feedPIdata( ) * - * Parse PI data for a single frame + * Feed PI data to the decoder handle *---------------------------------------------------------------------*/ -ivas_error IVAS_DEC_parsePIdata( +ivas_error IVAS_DEC_feedPIdata( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* i : PI data handle for the current frame */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { ivas_error error = IVAS_ERR_OK; /* scene orientation */ - if ( ( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } /* device orientation */ - if ( ( error = IVAS_DEC_parseSinglePIorientation( hIvasDec, PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } @@ -3828,20 +3814,6 @@ void IVAS_DEC_resetExternalOrientations( } -/*---------------------------------------------------------------------* - * IVAS_DEC_resetPIdataCurrentFrame( ) - * - * Reset current PI frame struct - *---------------------------------------------------------------------*/ - -void IVAS_DEC_resetPIdataCurrentFrame( - PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ -) -{ - resetPIdataCurrentFrame( piDataCurrentFrame ); -} - - #endif /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_IsEmpty( ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index f71ff96e7c..f20d6a9eb2 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -330,16 +330,14 @@ ivas_error IVAS_DEC_Flush( ); #ifdef IVAS_RTPDUMP -ivas_error IVAS_DEC_parseSinglePIorientation( +ivas_error IVAS_DEC_feedSinglePIorientation( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - char *orientation, /* i : input PI orientation */ - bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedInvOrientation /* i : previously saved (inverted) orientation for this PI type */ + const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ ); -ivas_error IVAS_DEC_parsePIdata( +ivas_error IVAS_DEC_feedPIdata( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* i : PI data handle for the current frame */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ); @@ -347,10 +345,6 @@ void IVAS_DEC_resetExternalOrientations( IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ ); -void IVAS_DEC_resetPIdataCurrentFrame( - PI_DATA_CURRENT_FRAME *piDataCurrentFrame /* i/o: PI data handle for the current frame */ -); - #endif /* Setter functions - apply changes to decoder configuration */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 467de66eb9..e9d65ea0b1 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -196,6 +196,133 @@ static void ivasPayload_PIaddHeaderBytes( } +/*-----------------------------------------------------------------------* + * ivasPayload_convertFromQ15() + * + * Convert a Q15 encoded value to a float value. + *-----------------------------------------------------------------------*/ + +static float ivasPayload_convertFromQ15( uint16_t Q15Value ) +{ + float temp; + temp = (float) ( Q15Value & 0x7FFF ) / 0x7FFF; + return ( Q15Value & 0x8000 ) ? -temp : temp; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ15() + * + * Convert a float value into a Q15 encoded value. + *-----------------------------------------------------------------------*/ + +static uint16_t ivasPayload_convertToQ15( float value ) +{ + uint16_t temp; + temp = (uint16_t) ( fabsf( value ) * 0x7FFF ); + if ( value >= 0.0f ) + { + temp = temp & 0x7FFF; + } + else + { + temp = temp | 0x8000; + } + return temp; +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_convertOrientationToQ15() + * + * Convert a float quaternion into a Q15 encoded quaternion. + *-----------------------------------------------------------------------*/ + +static void ivasPayload_convertOrientationToQ15( + IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ + IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ +) +{ + orientationQ15->w = ivasPayload_convertToQ15( orientation.w ); + orientationQ15->x = ivasPayload_convertToQ15( orientation.x ); + orientationQ15->y = ivasPayload_convertToQ15( orientation.y ); + orientationQ15->z = ivasPayload_convertToQ15( orientation.z ); +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parseQuaternionValue() + * + * Parse a single Q15 encoded quaternion component from the RTP packet and convert it to float. + *-----------------------------------------------------------------------*/ + +static float ivasPayload_parseQuaternionComponent( + char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ +) +{ + uint16_t tempQ15value; + tempQ15value = ( uint16_t ) * *PIorientationData; + tempQ15value = (uint16_t) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); + *PIorientationData += 2; + return ivasPayload_convertFromQ15( tempQ15value ); +} + + +/*-----------------------------------------------------------------------* + * ivasPayload_parseOrientationPIdata() + * + * Parse a single orientation in quaternions from the PI data. + *-----------------------------------------------------------------------*/ + +static bool ivasPayload_parseOrientationPIdata( + char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ + IVAS_QUATERNION *q /* o : Parsed quaternion value */ +) +{ + q->w = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->x = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->y = ivasPayload_parseQuaternionComponent( &PIorientationData ); + q->z = ivasPayload_parseQuaternionComponent( &PIorientationData ); + + if ( q->w > 1.0f || q->w < -1.0f || + q->x > 1.0f || q->x < -1.0f || + q->y > 1.0f || q->y < -1.0f || + q->z > 1.0f || q->z < -1.0f ) + { + fprintf( stderr, "Error: invalid orientation PI data\n" ); + return false; + } + return true; +} + + +/*---------------------------------------------------------------------* + * ivasPayload_parseSinglePIorientation( ) + * + * Parse a single orientation from PI data. + * Save the parsed orientation value and use in processing in future frames, if no new PI orientation is received. + *---------------------------------------------------------------------*/ + +static bool ivasPayload_parseSinglePIorientation( + char *orientation, /* i : input PI orientation */ + bool *isOrientationSaved, /* o : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* o : saved orientation value for this PI type */ +) +{ + if ( orientation ) + { + /* read the orientation PI value */ + if ( !ivasPayload_parseOrientationPIdata( orientation, savedOrientation ) ) + { + fprintf( stderr, "Error: parsing PI orientation failed\n" ); + return false; + } + *isOrientationSaved = true; + } + return true; +} + + /*-----------------------------------------------------------------------* * ivasPayload_parsePIdata() * @@ -206,7 +333,8 @@ static bool ivasPayload_parsePIdata( char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame,/* o : PI data handle for the current audio frame */ + PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { // TODO(pajunen): can this accidentally include zero padding bits? @@ -273,79 +401,21 @@ static bool ivasPayload_parsePIdata( } ivasPayload_PIaddHeaderBytes( totalPIheaderSizeBytes, PIdataCurrentFrame ); - return true; -} - - -/*-----------------------------------------------------------------------* - * ivasPayload_convertFromQ15() - * - * Convert a Q15 encoded value to a float value. - *-----------------------------------------------------------------------*/ - -static float ivasPayload_convertFromQ15( uint16_t Q15Value ) -{ - float temp; - temp = (float) ( Q15Value & 0x7FFF ) / 0x7FFF; - return ( Q15Value & 0x8000 ) ? -temp : temp; -} - - -/*-----------------------------------------------------------------------* - * ivasPayload_convertToQ15() - * - * Convert a float value into a Q15 encoded value. - *-----------------------------------------------------------------------*/ - -static uint16_t ivasPayload_convertToQ15( float value ) -{ - uint16_t temp; - temp = (uint16_t) ( fabsf( value ) * 0x7FFF ); - if ( value >= 0.0f ) + /* scene orientation */ + if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) { - temp = temp & 0x7FFF; + fprintf( stderr, "Error: parsing scene orientation PI data failed.\n" ); + return false; } - else + + /* device orientation */ + if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) { - temp = temp | 0x8000; + fprintf( stderr, "Error: parsing device orientation PI data failed.\n" ); + return false; } - return temp; -} - - -/*-----------------------------------------------------------------------* - * ivasPayload_convertOrientationToQ15() - * - * Convert a float quaternion into a Q15 encoded quaternion. - *-----------------------------------------------------------------------*/ - -static void ivasPayload_convertOrientationToQ15( - IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ - IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ -) -{ - orientationQ15->w = ivasPayload_convertToQ15( orientation.w ); - orientationQ15->x = ivasPayload_convertToQ15( orientation.x ); - orientationQ15->y = ivasPayload_convertToQ15( orientation.y ); - orientationQ15->z = ivasPayload_convertToQ15( orientation.z ); -} - -/*-----------------------------------------------------------------------* - * ivasPayload_parseQuaternionValue() - * - * Parse a single Q15 encoded quaternion component from the RTP packet and convert it to float. - *-----------------------------------------------------------------------*/ - -static float ivasPayload_parseQuaternionComponent( - char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ -) -{ - uint16_t tempQ15value; - tempQ15value = ( uint16_t ) * *PIorientationData; - tempQ15value = (uint16_t) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); - *PIorientationData += 2; - return ivasPayload_convertFromQ15( tempQ15value ); + return true; } @@ -355,7 +425,7 @@ static float ivasPayload_parseQuaternionComponent( * Reset the PI data handle for the current audio frame. *-----------------------------------------------------------------------*/ -void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) +static void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) { PIdataCurrentFrame->PIdataPresent = false; PIdataCurrentFrame->sceneOrientation = NULL; @@ -367,33 +437,6 @@ void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) } -/*-----------------------------------------------------------------------* - * ivasPayload_parseOrientationPIdata() - * - * Parse a single orientation in quaternions from the PI data. - *-----------------------------------------------------------------------*/ - -bool ivasPayload_parseOrientationPIdata( - char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ - IVAS_QUATERNION *q /* o : Parsed quaternion value */ -) -{ - q->w = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->x = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->y = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->z = ivasPayload_parseQuaternionComponent( &PIorientationData ); - - if ( q->w > 1.0f || q->w < -1.0f || - q->x > 1.0f || q->x < -1.0f || - q->y > 1.0f || q->y < -1.0f || - q->z > 1.0f || q->z < -1.0f ) - { - fprintf( stderr, "Error: invalid orientation PI data\n" ); - return false; - } - return true; -} - bool ivasPayload_unpackFrame( char *payload, /* i : RTP payload data */ uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ @@ -406,7 +449,7 @@ bool ivasPayload_unpackFrame( * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ + PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit; @@ -417,6 +460,8 @@ bool ivasPayload_unpackFrame( fullPayloadSizeBytes = payloadSizeBytes; PIdataPayload = payload; someFrameFollowing = true; + PI_DATA_CURRENT_FRAME PIdataCurrentFrame; + resetPIdataCurrentFrame( &PIdataCurrentFrame ); if ( payloadSizeBytes < 1 ) { fprintf( stderr, "Error: payload too small to parse ToC\n" ); @@ -425,7 +470,7 @@ bool ivasPayload_unpackFrame( /* parse CMR/E-bytes */ if ( *payload & E_BYTE ) // 1xxx xxxx { - PIdataCurrentFrame->PIdataPresent = false; + PIdataCurrentFrame.PIdataPresent = false; while ( 1 ) { if ( ( *payload & 0xF0 ) == IVAS_E_CMR ) // 1111 xxxx @@ -487,7 +532,7 @@ bool ivasPayload_unpackFrame( else if ( ( *payload & 0xE0 ) == IVAS_E_PI_INDICATION ) // 110x xxxx { /* IVAS PI presence */ - PIdataCurrentFrame->PIdataPresent = true; + PIdataCurrentFrame.PIdataPresent = true; } else if ( ( *payload & 0xE0 ) == IVAS_E_RESERVED ) // 111x xxxx { @@ -574,11 +619,11 @@ bool ivasPayload_unpackFrame( } /* parse PI data */ - if ( PIdataCurrentFrame->PIdataPresent ) + if ( PIdataCurrentFrame.PIdataPresent ) { PIdataPayloadSizeBytes = payloadSizeBytes; PIdataPayload += ( fullPayloadSizeBytes - PIdataPayloadSizeBytes ); - if ( !ivasPayload_parsePIdata( PIdataPayload, PIdataPayloadSizeBytes, frameIndex, PIdataCurrentFrame ) ) + if ( !ivasPayload_parsePIdata( PIdataPayload, PIdataPayloadSizeBytes, frameIndex, &PIdataCurrentFrame, PIdataDepackerState ) ) { fprintf( stderr, "Error while parsing PI data\n" ); return false; @@ -600,7 +645,6 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( { return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; } - resetPIdataCurrentFrame( &self->PIdataCurrentFrame ); self->PIdataDepackerState.sceneOrientationSaved = false; self->PIdataDepackerState.deviceOrientationSaved = false; return IVAS_RTPDUMP_DEPACKER_NO_ERROR; @@ -638,7 +682,7 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( if ( !ivasPayload_unpackFrame( self->rtpPacket.data + self->rtpPacket.headerSize, self->rtpPacket.payloadSize, self->frameIndex, evsIvasModeBit, &self->frameFollowing, bitrateIndex, ivasIndicatorBit, - frame, frameSizeBits, &self->PIdataCurrentFrame ) ) + frame, frameSizeBits, &self->PIdataDepackerState ) ) { return IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR; } diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 9d8bad958f..d1ec02d200 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -190,7 +190,6 @@ typedef struct uint32_t timeoffset_ms; uint16_t frameIndex; bool frameFollowing; - PI_DATA_CURRENT_FRAME PIdataCurrentFrame; PI_DATA_DEPACKER_STATE PIdataDepackerState; } IVAS_RTPDUMP_DEPACKER; @@ -238,7 +237,7 @@ bool ivasPayload_unpackFrame( * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data for frame indicated by frameIndex */ + PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ); -- GitLab From a4c646dfaaa66fe1912d9e03dc38a0a8af2a30dc Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 13 Jun 2025 14:13:16 +0200 Subject: [PATCH 028/340] Fix typo in comment --- tests/split_rendering/test_split_rendering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index f08d08cd4a..a4bdca36dd 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -610,7 +610,7 @@ DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] # Compares PCM output and tracefile from a VoIP BINAURAL_SPLIT_PCM chain with equivalent BINAURAL -# chain to ensure time-scaling and other JBM operations are the BE between the two. +# chain to ensure time-scaling and other JBM operations are BE between the two. @pytest.mark.parametrize("in_format", IN_FORMATS) @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) def test_be_splitrend_vs_binaural( -- GitLab From aba18db5c0e4f21df9c64e0369a5b94640f4df89 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 13 Jun 2025 14:11:25 +0200 Subject: [PATCH 029/340] Add a temporary workaround to fix IVAS_DEC_Flush with BINAURAL_SPLIT_PCM output --- lib_com/options.h | 1 + lib_dec/ivas_jbm_dec.c | 6 ++++++ lib_dec/ivas_stat_dec.h | 4 ++++ lib_dec/lib_dec.c | 8 ++++++++ 4 files changed, 19 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index c335aaa419..4c5013cb5d 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -161,6 +161,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define UNIFIED_DECODING_PATHS_LEFTOVERS /* VA: issue 880: remove leftovers after NONBE_UNIFIED_DECODING_PATHS */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ +#define TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Workaround for incorrect implementation of decoder flush with split rendering */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 68e9099896..b11fe466fd 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -1467,6 +1467,12 @@ ivas_error ivas_jbm_dec_render( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { nchan_out_syn_output = BINAURAL_CHANNELS * st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; +#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR + if ( st_ivas->flushing ) + { + nchan_out_syn_output = BINAURAL_CHANNELS; + } +#endif } else { diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index eca95d41f9..1389e53f77 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -1146,6 +1146,10 @@ typedef struct Decoder_Struct int16_t ism_extmeta_active; /* Extended metadata active in decoder */ int16_t ism_extmeta_cnt; /* Change frame counter for extended metadata */ +#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR + int16_t flushing; +#endif + } Decoder_Struct; /* clang-format on */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 541727b364..ade85b1a30 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -4134,6 +4134,10 @@ ivas_error IVAS_DEC_Flush( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } +#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR + hIvasDec->st_ivas->flushing = 1; +#endif + *nSamplesFlushed = min( nSamplesPerChannel, hIvasDec->nSamplesAvailableNext ); nSamplesToRender = (uint16_t) *nSamplesFlushed; @@ -4149,6 +4153,10 @@ ivas_error IVAS_DEC_Flush( *nSamplesFlushed = 0; } +#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR + hIvasDec->st_ivas->flushing = 0; +#endif + return error; } -- GitLab From a2e78779843651bbd9df3c30732863da265330f2 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Fri, 13 Jun 2025 15:58:40 +0300 Subject: [PATCH 030/340] Clang format --- lib_dec/lib_dec.c | 6 +++--- lib_util/ivas_rtp_payload.c | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index d3ada6dd9c..7bf5401729 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3729,9 +3729,9 @@ ivas_error IVAS_DEC_Flush( *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_feedSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ ) { int16_t i; diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index e9d65ea0b1..951c9a9a0c 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -304,9 +304,9 @@ static bool ivasPayload_parseOrientationPIdata( *---------------------------------------------------------------------*/ static bool ivasPayload_parseSinglePIorientation( - char *orientation, /* i : input PI orientation */ - bool *isOrientationSaved, /* o : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* o : saved orientation value for this PI type */ + char *orientation, /* i : input PI orientation */ + bool *isOrientationSaved, /* o : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* o : saved orientation value for this PI type */ ) { if ( orientation ) @@ -330,10 +330,10 @@ static bool ivasPayload_parseSinglePIorientation( *-----------------------------------------------------------------------*/ static bool ivasPayload_parsePIdata( - char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ - uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ - uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame,/* o : PI data handle for the current audio frame */ + char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ + uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ + uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ + PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* o : PI data handle for the current audio frame */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { @@ -438,17 +438,17 @@ static void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) bool ivasPayload_unpackFrame( - char *payload, /* i : RTP payload data */ - uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ - uint16_t frameIndex, /* i : Index for the data frame in the payload */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeInBits, /* o : Data frame size in bits */ + char *payload, /* i : RTP payload data */ + uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ + uint16_t frameIndex, /* i : Index for the data frame in the payload */ + bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ + bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + unsigned char **frame, /* o : Data frame indicated by frameIndex */ + uint16_t *frameSizeInBits, /* o : Data frame size in bits */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { -- GitLab From c0bf68fbe73762197f98ce62a5045c98f700b256 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 18 Jun 2025 16:20:30 +0300 Subject: [PATCH 031/340] Fix Q-bit parsing, add good frame indication, remove unused evs payload parsing functions --- apps/decoder.c | 24 +++++++++++++----------- lib_dec/jbm_jb4sb.h | 5 +++++ lib_dec/lib_dec.c | 12 ++++++++++++ lib_dec/lib_dec.h | 6 +++++- lib_util/evs_rtp_payload.c | 27 ++++++++++++++++++++++++--- lib_util/evs_rtp_payload.h | 7 +++++-- lib_util/ivas_rtp_payload.c | 11 ++++++++--- lib_util/ivas_rtp_payload.h | 8 +++++--- 8 files changed, 77 insertions(+), 23 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index cb2e08233f..213b77985a 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -2895,7 +2895,8 @@ static ivas_error printBitstreamInfoVoip( #ifdef IVAS_RTPDUMP bool evsIvasModeBit; uint16_t bitrateIndex; - bool ivasIndicatorOrQBit; + bool ivasIndicatorBit; + bool isGoodFrame; #else bool isAMRWB_IOmode; uint16_t frameTypeIndex; @@ -2944,7 +2945,7 @@ static ivas_error printBitstreamInfoVoip( { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSizeBits, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); #ifdef IVAS_RTPDUMP - ivasIndicatorOrQBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ + isGoodFrame = 1; /* good frame for INPUT_FORMAT_G192 */ #else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ #endif @@ -2953,7 +2954,7 @@ static ivas_error printBitstreamInfoVoip( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSizeBits ); + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, &isGoodFrame, &auPtr, (uint16_t *) &auSizeBits ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); #endif @@ -2967,7 +2968,7 @@ static ivas_error printBitstreamInfoVoip( goto cleanup; } #ifdef IVAS_RTPDUMP - } while ( !ivasIndicatorOrQBit || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); + } while ( !isGoodFrame || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); #else } while ( !qBit || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); #endif @@ -3066,7 +3067,8 @@ static ivas_error decodeVoIP( #ifdef IVAS_RTPDUMP bool evsIvasModeBit; uint16_t bitrateIndex; - bool ivasIndicatorOrQBit; + bool ivasIndicatorBit; + bool isGoodFrame; #else bool isAMRWB_IOmode; uint16_t frameTypeIndex; @@ -3177,7 +3179,7 @@ static ivas_error decodeVoIP( { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); #ifdef IVAS_RTPDUMP - ivasIndicatorOrQBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ + isGoodFrame = 1; /* good frame for INPUT_FORMAT_G192 */ #else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ #endif @@ -3186,7 +3188,7 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, &auPtr, (uint16_t *) &auSize ); + rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, &isGoodFrame, &auPtr, (uint16_t *) &auSize ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif @@ -3351,7 +3353,7 @@ static ivas_error decodeVoIP( { /* feed the previous read packet into the receiver now */ #ifdef IVAS_RTPDUMP - error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, ivasIndicatorOrQBit ); + error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, isGoodFrame ); #else error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, qBit ); #endif @@ -3368,7 +3370,7 @@ static ivas_error decodeVoIP( error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); #ifdef IVAS_RTPDUMP - ivasIndicatorOrQBit = 1; /* good q_bit for VOIP_G192_RTP */ + isGoodFrame = 1; /* good frame for VOIP_G192_RTP */ #else qBit = 1; /* good q_bit for VOIP_G192_RTP */ #endif @@ -3379,8 +3381,8 @@ static ivas_error decodeVoIP( #ifdef IVAS_RTPDUMP rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, - &evsIvasModeBit, &bitrateIndex, &ivasIndicatorOrQBit, - &auPtr, (uint16_t *) &auSize ); + &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, + &isGoodFrame, &auPtr, (uint16_t *) &auSize ); /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; diff --git a/lib_dec/jbm_jb4sb.h b/lib_dec/jbm_jb4sb.h index 8155d66ae9..2ef48dbfb3 100644 --- a/lib_dec/jbm_jb4sb.h +++ b/lib_dec/jbm_jb4sb.h @@ -63,8 +63,13 @@ struct JB4_DATAUNIT uint32_t rcvTime; /** true, if the data unit contains only silence */ bool silenceIndicator; +#ifdef IVAS_RTPDUMP + /** good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ + Word16 isGoodFrame; +#else /** Q bit for AMR-WB IO */ Word16 qBit; +#endif /** the binary encoded access unit */ uint8_t *data; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 7bf5401729..91959047ce 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3232,7 +3232,11 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( const uint16_t rtpSequenceNumber, /* i : RTP sequence number (16 bits) */ const uint32_t rtpTimeStamp, /* i : RTP timestamp (32 bits) */ const uint32_t rcvTime_ms, /* i : receive time of the RTP packet in milliseconds */ +#ifdef IVAS_RTPDUMP + const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ +#else const bool qBit /* i : Q bit for AMR-WB IO */ +#endif ) { JB4_DATAUNIT_HANDLE dataUnit; @@ -3274,7 +3278,11 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( dataUnit->timeStamp = rtpTimeStamp; dataUnit->partial_frame = 0; dataUnit->partialCopyOffset = partialCopyOffset; +#ifdef IVAS_RTPDUMP + dataUnit->isGoodFrame = isGoodFrame; +#else dataUnit->qBit = qBit; +#endif /* add the frame to the JBM */ result = JB4_PushDataUnit( hIvasDec->hVoIP->hJBM, dataUnit, rcvTime_ms ); @@ -3297,7 +3305,11 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( dataUnit->timeStamp = rtpTimeStamp - partialCopyOffset * dataUnit->duration; dataUnit->partial_frame = 1; dataUnit->partialCopyOffset = partialCopyOffset; +#ifdef IVAS_RTPDUMP + dataUnit->isGoodFrame = isGoodFrame; +#else dataUnit->qBit = qBit; +#endif /* add the frame to the JBM */ result = JB4_PushDataUnit( hIvasDec->hVoIP->hJBM, dataUnit, rcvTime_ms ); diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index f20d6a9eb2..40a5403373 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -285,7 +285,11 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( const uint16_t rtpSequenceNumber, /* i : RTP sequence number (16 bits) */ const uint32_t rtpTimeStamp, /* i : RTP timestamp (32 bits) */ const uint32_t rcvTime_ms, /* i : receive time of the RTP packet in milliseconds */ - const bool qBit /* i : Q bit for AMR-WB IO */ +#ifdef IVAS_RTPDUMP + const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ +#else +const bool qBit /* i : Q bit for AMR-WB IO */ +#endif ); ivas_error IVAS_DEC_VoIP_SetScale( diff --git a/lib_util/evs_rtp_payload.c b/lib_util/evs_rtp_payload.c index 89a3bdf51d..9c1f71be48 100644 --- a/lib_util/evs_rtp_payload.c +++ b/lib_util/evs_rtp_payload.c @@ -39,6 +39,7 @@ #include "evs_rtp_payload.h" #include "options.h" +#ifndef IVAS_RTPDUMP static void evsPayload_unpackFrame_compact_amrWbIo( const char *payload, uint16_t payloadSizeBits, uint16_t iProtectedSize, unsigned char **framePtr, uint16_t *frameSizeInBits ) { uint16_t i, iBit0; @@ -123,12 +124,30 @@ bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBy return evsHeaderFullPayload_unpackFrame( payload, payloadSizeBytes, frameIndex, isAMRWB_IOmode, frameFollowing, frameTypeIndex, qBit, framePtr, frameSizeInBits ); } +#endif #ifdef IVAS_RTPDUMP -void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) +void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, bool *isGoodFrame, int32_t *bitrate ) +{ + bool evsModeBit = ( toc & 0x20 ) != 0; + *isAMRWB_IOmode = evsModeBit; + *frameFollowing = ( toc & 0x40 ) != 0; + *frameTypeIndex = toc & 0x0f; + if ( *isAMRWB_IOmode ) + { + *qBit = ( toc & 0x10 ) != 0; + *isGoodFrame = *qBit; + *bitrate = AMRWB_IOmode2rate[*frameTypeIndex]; + } + else + { + *qBit = false; /* Q-bit is unused in EVS Primary mode */ + *isGoodFrame = true; /* assume good frame for EVS Primary */ + *bitrate = PRIMARYmode2rate[*frameTypeIndex]; + } +} #else static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) -#endif { bool evsModeBit = ( toc & 0x20 ) != 0; *isAMRWB_IOmode = evsModeBit; @@ -145,8 +164,9 @@ static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bo *bitrate = AMRWB_IOmode2rate[*frameTypeIndex]; } } +#endif - +#ifndef IVAS_RTPDUMP bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeInBits ) { bool someIsAMRWB_IOmode, someFrameFollowing = true, someQBit; @@ -278,3 +298,4 @@ void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ) } RTPDUMP_Close( &self->rtpdump, 0 ); } +#endif diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 21bb10f3f3..aea968ddff 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -83,6 +83,7 @@ extern "C" 0 /* NO_DATA */ }; +#ifndef IVAS_RTPDUMP static const uint16_t evsPayloadProtectedSizes[22] = { 48, 56, @@ -159,13 +160,14 @@ extern "C" }; bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); - +#endif #ifdef IVAS_RTPDUMP - void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); + void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, bool *isGoodFrame, int32_t *bitrate ); #else static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #endif +#ifndef IVAS_RTPDUMP bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); typedef struct @@ -200,6 +202,7 @@ static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bo uint16_t *frameSizeBits ); void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); +#endif #ifdef __cplusplus } diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 951c9a9a0c..e99ff585d6 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -87,18 +87,20 @@ static void ivasPayload_parseToc( uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ int32_t *bitrate /* o : Bitrate of the data frame indicated in the ToC byte */ ) { bool isIvasToc = ( toc & 0x30 ) == 0x10; // xx01 xxxx if ( !isIvasToc ) { - evsHeaderFullPayload_parseToc( toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, bitrate ); + evsHeaderFullPayload_parseToc( toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, isGoodFrame, bitrate ); } else { *evsIvasModeBit = false; *ivasIndicatorBit = true; + *isGoodFrame = true; /* assume good frame for IVAS */ *frameFollowing = ( toc & 0x40 ) != 0; *bitrateIndex = toc & 0x0f; *bitrate = IVASmode2rate[*bitrateIndex]; @@ -447,12 +449,13 @@ bool ivasPayload_unpackFrame( uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeInBits, /* o : Data frame size in bits */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ ) { - bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit; + bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit, someGoodFrameIndication; uint16_t someBitrateIndex, someFrameSizeInBits, fullPayloadSizeBytes, PIdataPayloadSizeBytes; int32_t bitrate; uint16_t iFrame; @@ -582,7 +585,7 @@ bool ivasPayload_unpackFrame( fprintf( stderr, "Error: expected ToC, found CMR/E-byte\n" ); return false; } - ivasPayload_parseToc( *payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &bitrate ); + ivasPayload_parseToc( *payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &someGoodFrameIndication, &bitrate ); if ( bitrate < 0 ) { fprintf( stderr, "Error: unexpected frameTypeIndex in ToC\n" ); @@ -609,6 +612,7 @@ bool ivasPayload_unpackFrame( *frameFollowing = someFrameFollowing; *bitrateIndex = someBitrateIndex; *ivasIndicatorBit = someIvasIndicatorBit; + *isGoodFrame = someGoodFrameIndication; *frameSizeInBits = someFrameSizeInBits; } if ( (int16_t) payloadSizeBytes < 0 ) @@ -660,6 +664,7 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeBits /* o : Data frame size in bits */ ) diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index d1ec02d200..8529de51b7 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -230,11 +230,12 @@ bool ivasPayload_unpackFrame( uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ uint16_t frameIndex, /* i : Index for the data frame in the payload */ bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ + * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeInBits, /* o : Data frame size in bits */ PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ @@ -268,7 +269,8 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ + bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ unsigned char **frame, /* o : Data frame indicated by frameIndex */ uint16_t *frameSizeBits /* o : Data frame size in bits */ ); -- GitLab From d98442d375f228d7fd8dc9d910c4333ce758c59c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 18 Jun 2025 16:30:03 +0300 Subject: [PATCH 032/340] Clang format --- lib_dec/lib_dec.c | 4 ++-- lib_util/evs_rtp_payload.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 91959047ce..a476430478 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3233,9 +3233,9 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( const uint32_t rtpTimeStamp, /* i : RTP timestamp (32 bits) */ const uint32_t rcvTime_ms, /* i : receive time of the RTP packet in milliseconds */ #ifdef IVAS_RTPDUMP - const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ + const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ #else - const bool qBit /* i : Q bit for AMR-WB IO */ + const bool qBit /* i : Q bit for AMR-WB IO */ #endif ) { diff --git a/lib_util/evs_rtp_payload.c b/lib_util/evs_rtp_payload.c index 9c1f71be48..7fc0914892 100644 --- a/lib_util/evs_rtp_payload.c +++ b/lib_util/evs_rtp_payload.c @@ -141,7 +141,7 @@ void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *fra } else { - *qBit = false; /* Q-bit is unused in EVS Primary mode */ + *qBit = false; /* Q-bit is unused in EVS Primary mode */ *isGoodFrame = true; /* assume good frame for EVS Primary */ *bitrate = PRIMARYmode2rate[*frameTypeIndex]; } -- GitLab From 52aeec044074d527064c4e4153ee1c246a6f58f5 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 25 Jun 2025 15:38:08 +0200 Subject: [PATCH 033/340] CLDFB ring buffer: replace CLDFB_RB_GetNthLastPushed with CLDFB_RB_GetByIdx --- lib_dec/cldfb_ring_buffer.c | 35 +++++++++++++++-------------------- lib_dec/cldfb_ring_buffer.h | 8 +------- lib_dec/ivas_omasa_dec.c | 3 +-- lib_dec/ivas_osba_dec.c | 3 +-- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index 8e91d37486..0282d928f5 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -134,7 +134,11 @@ void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ) { - CLDFB_RB_Front( h, real, imag, num_bands ); + assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); + assert( !CLDFB_RB_IsEmpty( h ) ); + + mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); + mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); h->read_pos += CLDFB_NO_CHANNELS_MAX; if ( h->read_pos == h->capacity ) @@ -146,15 +150,6 @@ void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_ { h->is_full = 0; } -} - -void CLDFB_RB_Front( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ) -{ - assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !CLDFB_RB_IsEmpty( h ) ); - - mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); - mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); return; } @@ -181,31 +176,31 @@ static uint32_t rb_num_floats( CLDFB_RING_BUFFER_HANDLE h ) return ret; } -int16_t CLDFB_RB_GetNthLastPushed( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, uint16_t n ) +int16_t CLDFB_RB_GetByIdx( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, int16_t col_idx ) { + int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; + int32_t num_floats = (int32_t) rb_num_floats(h); uint32_t offset; - offset = ( n + 1 ) * CLDFB_NO_CHANNELS_MAX; - /* Trying to access column that was already popped, error */ - if ( rb_num_floats( h ) < offset ) + if ( idx < -num_floats || num_floats <= idx ) { *p_real = NULL; *p_imag = NULL; return -1; } - if ( offset <= h->write_pos ) + if ( idx >= 0 ) { - *p_real = &h->real[h->write_pos - offset]; - *p_imag = &h->imag[h->write_pos - offset]; + offset = ( h->read_pos + idx ) % h->capacity; } else { - /* wrap around */ - *p_real = &h->real[h->write_pos + h->capacity - offset]; - *p_imag = &h->imag[h->write_pos + h->capacity - offset]; + offset = ( h->write_pos + h->capacity + idx ) % h->capacity; } + *p_real = &h->real[offset]; + *p_imag = &h->imag[offset]; + return 0; } diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index cb276d7549..eee1bf1faa 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -48,13 +48,7 @@ void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ); -void CLDFB_RB_Front( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ); - -/** - * Get pointers into the last nth pushed CLDFB column. - * TODO: describe n more precisely - */ -int16_t CLDFB_RB_GetNthLastPushed( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, uint16_t n ); +int16_t CLDFB_RB_GetByIdx( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, int16_t idx ); int16_t CLDFB_RB_IsEmpty( CLDFB_RING_BUFFER_HANDLE h ); diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index c4d33ce425..cd1ae88521 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -800,8 +800,7 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - // TODO 1119: Double-check indexing here - CLDFB_RB_GetNthLastPushed( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, cldfb_slots - slot_idx - 1 ); + CLDFB_RB_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); #else diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index c2b6548738..3f474cc172 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -185,8 +185,7 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( cldfbAnalysis_ts( &( output_f[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP - // TODO 1119: Double-check indexing here - CLDFB_RB_GetNthLastPushed( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, cldfb_slots - slot_idx - 1 ); + CLDFB_RB_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); for ( b = 0; b < num_cldfb_bands; b++ ) { -- GitLab From 49bfaa8394846e119dea0f5272f1ef8b0ccc30c3 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 25 Jun 2025 15:58:32 +0200 Subject: [PATCH 034/340] Save memory by using values directly from the CLDFB ring buffer --- lib_dec/cldfb_ring_buffer.c | 10 ++++++++-- lib_dec/lib_dec.c | 26 ++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index 0282d928f5..cfb5362d36 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -137,8 +137,14 @@ void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_ assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); assert( !CLDFB_RB_IsEmpty( h ) ); - mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); - mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); + if ( real != NULL ) + { + mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); + } + if ( imag != NULL ) + { + mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); + } h->read_pos += CLDFB_NO_CHANNELS_MAX; if ( h->read_pos == h->capacity ) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index ade85b1a30..1bda91d724 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1945,9 +1945,6 @@ static ivas_error isar_generate_metadata_and_bitstream( int16_t ro_md_flag; IVAS_QUATERNION Quaternion; int16_t i, j, num_poses; - // TODO 1119: If data could be read directly from the ring buffer, these large buffers could be removed - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; @@ -1969,15 +1966,24 @@ static ivas_error isar_generate_metadata_and_bitstream( { for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) { + /* Save pointers to first CLDFB column in the ring buffer. Allows us to save + * significant amounts of memory by not copying CLDFB values into a separate buffer. */ + CLDFB_RB_GetByIdx( + hSplitBinRend->hMultiBinCldfbData[i], + &p_Cldfb_RealBuffer_Binaural[i][j], + &p_Cldfb_ImagBuffer_Binaural[i][j], + 0 + ); + /* Pop the CLDFB column we just saved pointers to. This is fine as long as we use + * the saved columns only before any new columns are pushed to the buffer - the new + * columns could potentially overwrite the old columns we wanted to use. + * This requirement is fulfilled in this case. */ CLDFB_RB_Pop( - hSplitBinRend->hMultiBinCldfbData[i], - Cldfb_RealBuffer_Binaural[i][j], - Cldfb_ImagBuffer_Binaural[i][j], - CLDFB_NO_CHANNELS_MAX + hSplitBinRend->hMultiBinCldfbData[i], + NULL, + NULL, + CLDFB_NO_CHANNELS_MAX ); - - p_Cldfb_RealBuffer_Binaural[i][j] = Cldfb_RealBuffer_Binaural[i][j]; - p_Cldfb_ImagBuffer_Binaural[i][j] = Cldfb_ImagBuffer_Binaural[i][j]; } } } -- GitLab From adaf54fd997116f8e0713f6d47394dad1a061134 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 25 Jun 2025 18:10:36 +0200 Subject: [PATCH 035/340] Handle leftover TODOs --- lib_dec/ivas_stat_dec.h | 6 +++--- lib_dec/lib_dec.c | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 1389e53f77..4bb1a5cc90 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -827,15 +827,15 @@ typedef struct renderer_struct #ifndef FIX_1119_SPLIT_RENDERING_VOIP typedef struct { - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; } ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA, *ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE; #endif typedef struct { -#ifdef FIX_1119_SPLIT_RENDERING_VOIP // TODO 1119: This double space should not be needed - we always output 20ms of content +#ifdef FIX_1119_SPLIT_RENDERING_VOIP float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; #else diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 1bda91d724..d9d0dd6d47 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1909,8 +1909,6 @@ static ivas_error isar_render_poses( { return error; } - - // TODO: 1119 - Check if this early return can be simplified. ATM we early return through two stack frames. if ( !hIvasDec->hasBeenFedFirstGoodFrame ) { return IVAS_ERR_OK; @@ -2076,7 +2074,6 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( { return error; } - // TODO: 1119 - Check if this early return can be simplified. ATM we early return through two stack frames. if ( !hIvasDec->hasBeenFedFirstGoodFrame ) { return IVAS_ERR_OK; @@ -2090,7 +2087,6 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( /* convert to int16 with limiting for BINAURAL_SPLIT_PCM */ if ( pcm_out_flag ) { - // TODO: 1119 - remove duplicated if/else branches if ( st_ivas->hDecoderConfig->render_framesize == IVAS_RENDER_FRAMESIZE_5MS ) { #ifndef DISABLE_LIMITER @@ -3931,7 +3927,6 @@ ivas_error IVAS_DEC_VoIP_GetSamples } } -// TODO: 1119 - extract to a function? #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { @@ -3964,7 +3959,6 @@ ivas_error IVAS_DEC_VoIP_GetSamples } } -// TODO: 1119 - extract to a function? #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( hIvasDec->hasDecodedFirstGoodFrame && ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || @@ -5447,7 +5441,7 @@ static ivas_error ivas_dec_init_split_rend( int16_t cldfb_in_flag, pcm_out_flag; int16_t mixed_td_cldfb_flag; #ifdef FIX_1119_SPLIT_RENDERING_VOIP - int16_t i, num_poses; + int16_t i, num_poses, cldfb_buffer_capacity; #endif pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; @@ -5468,10 +5462,20 @@ static ivas_error ivas_dec_init_split_rend( if ( cldfb_in_flag ) { + if ( st_ivas->hDecoderConfig->Opt_tsm ) + { + /* With TSM we need more space for stretched frames */ + cldfb_buffer_capacity = CLDFB_NO_COL_MAX * 2; + } + else + { + cldfb_buffer_capacity = CLDFB_NO_COL_MAX; + } + for ( i = 0; i < (int16_t) ( num_poses * BINAURAL_CHANNELS ); ++i ) { /* note: this is intra-frame heap memory */ - error = CLDFB_RB_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], 2 * CLDFB_NO_COL_MAX ); /* TODO: 2x space to fit scaled frames is only needed in VoIP mode */ + error = CLDFB_RB_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], cldfb_buffer_capacity ); if ( error != IVAS_ERR_OK ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); -- GitLab From b4bf26f916ff550444b4539de567184221ff1621 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 11:29:53 +0200 Subject: [PATCH 036/340] Minor cleanup --- lib_dec/ivas_dirac_dec.c | 5 ++--- lib_dec/ivas_osba_dec.c | 4 ++-- lib_dec/ivas_stat_dec.h | 2 +- lib_dec/lib_dec.c | 8 ++++++++ lib_isar/isar_splitRendererPre.c | 26 ++++++++++++++++++++++---- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 351a05416d..80262f605a 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1667,16 +1667,15 @@ static void binRenderer_split( { for ( ch = 0; ch < nchan_out; ch++ ) { - #ifdef FIX_1119_SPLIT_RENDERING_VOIP CLDFB_RB_Push( - hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], + hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], num_freq_bands ); #else - mvr2r( Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); + mvr2r( Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); mvr2r( Cldfb_ImagBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); #endif } diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index 3f474cc172..39a927c74a 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -195,11 +195,11 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( #else for ( b = 0; b < num_cldfb_bands; b++ ) { - st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] = + st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] = ( 0.5f * st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[n][slot_idx_start + slot_idx][b] ) + ( 0.5f * Cldfb_RealBuffer[b] ); - st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] = + st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] = ( 0.5f * st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[n][slot_idx_start + slot_idx][b] ) + ( 0.5f * Cldfb_ImagBuffer[b] ); } diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 66f6e15c95..f1fa590de5 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -836,7 +836,7 @@ typedef struct typedef struct { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; + float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; /* Double space to account for TSM */ float Cldfb_ImagBuffer[MAX_OUTPUT_CHANNELS][2 * CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; #else float Cldfb_RealBuffer[MAX_OUTPUT_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 2101acb75d..e42138534e 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3748,6 +3748,14 @@ ivas_error IVAS_DEC_VoIP_GetSamples return IVAS_ERR_WRONG_PARAMS; } +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + if ( hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || + hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) + { + return IVAS_ERROR( IVAS_ERR_NOT_IMPLEMENTED, "Split rendering is not integrated with VoIP mode" ); + } +#endif + /* make sure that the FIFO after decoder/scaler contains at least one sound card frame (i.e. 20ms) */ while ( *nSamplesRendered < nSamplesPerChannel ) { diff --git a/lib_isar/isar_splitRendererPre.c b/lib_isar/isar_splitRendererPre.c index e7470c000e..94aabfe3ce 100644 --- a/lib_isar/isar_splitRendererPre.c +++ b/lib_isar/isar_splitRendererPre.c @@ -2012,11 +2012,22 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( { target_md_bits = isar_get_split_rend_md_target_brate( SplitRendBitRate, pcm_out_flag ) * L_FRAME48k / 48000; + isar_rend_CldfbSplitPreRendProcess( + hSplitBin->hBinHrSplitPreRend, + headPosition, + &hSplitBin->multiBinPoseData, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - isar_rend_CldfbSplitPreRendProcess( hSplitBin->hBinHrSplitPreRend, headPosition, &hSplitBin->multiBinPoseData, p_Cldfb_In_BinReal, p_Cldfb_In_BinImag, pBits, target_md_bits, low_res_pre_rend_rot, ro_md_flag ); + p_Cldfb_In_BinReal, + p_Cldfb_In_BinImag, #else - isar_rend_CldfbSplitPreRendProcess( hSplitBin->hBinHrSplitPreRend, headPosition, &hSplitBin->multiBinPoseData, Cldfb_In_BinReal, Cldfb_In_BinImag, pBits, target_md_bits, low_res_pre_rend_rot, ro_md_flag ); + Cldfb_In_BinReal, + Cldfb_In_BinImag, #endif + pBits, + target_md_bits, + low_res_pre_rend_rot, + ro_md_flag + ); } if ( pcm_out_flag == 0 ) @@ -2031,11 +2042,18 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( pBits->codec_frame_size_ms = codec_frame_size_ms; pBits->isar_frame_size_ms = isar_frame_size_ms; + isar_splitBinLCLDEncProcess( + hSplitBin->hSplitBinLCLDEnc, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - isar_splitBinLCLDEncProcess( hSplitBin->hSplitBinLCLDEnc, p_Cldfb_In_BinReal, p_Cldfb_In_BinImag, available_bits, pBits ); + p_Cldfb_In_BinReal, + p_Cldfb_In_BinImag, #else - isar_splitBinLCLDEncProcess( hSplitBin->hSplitBinLCLDEnc, Cldfb_In_BinReal, Cldfb_In_BinImag, available_bits, pBits ); + Cldfb_In_BinReal, + Cldfb_In_BinImag, #endif + available_bits, + pBits + ); } else { -- GitLab From 2c5914eaaaa03368aef30da5eac0651e3f896ce9 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 11:34:58 +0200 Subject: [PATCH 037/340] Rename CLDFB ring buffer --- lib_dec/cldfb_ring_buffer.c | 32 ++++++++++---------- lib_dec/cldfb_ring_buffer.h | 22 +++++++------- lib_dec/ivas_dirac_dec.c | 2 +- lib_dec/ivas_mc_param_dec.c | 2 +- lib_dec/ivas_mc_paramupmix_dec.c | 2 +- lib_dec/ivas_omasa_dec.c | 2 +- lib_dec/ivas_osba_dec.c | 2 +- lib_dec/ivas_stat_dec.h | 2 +- lib_dec/lib_dec.c | 8 ++--- lib_rend/ivas_dirac_dec_binaural_functions.c | 4 +-- 10 files changed, 39 insertions(+), 39 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index cfb5362d36..43a922f1b0 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -40,7 +40,7 @@ #ifdef FIX_1119_SPLIT_RENDERING_VOIP -struct CldfbRingBuffer { +struct CldfbRingBuf { float* real; float* imag; uint32_t capacity; @@ -49,13 +49,13 @@ struct CldfbRingBuffer { int16_t is_full; }; -ivas_error CLDFB_RB_Open( CLDFB_RING_BUFFER_HANDLE *ph, int16_t capacity_columns ) +ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ) { - CLDFB_RING_BUFFER_HANDLE h; + CLDFB_RINGBUF_HANDLE h; int32_t capacity; capacity = capacity_columns * CLDFB_NO_CHANNELS_MAX; - if ( ( h = malloc( sizeof( struct CldfbRingBuffer ) ) ) == NULL ) + if ( ( h = malloc( sizeof( struct CldfbRingBuf ) ) ) == NULL ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); } @@ -80,9 +80,9 @@ ivas_error CLDFB_RB_Open( CLDFB_RING_BUFFER_HANDLE *ph, int16_t capacity_columns return IVAS_ERR_OK; } -void CLDFB_RB_Close( CLDFB_RING_BUFFER_HANDLE *ph ) +void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ) { - CLDFB_RING_BUFFER_HANDLE h; + CLDFB_RINGBUF_HANDLE h; if ( ph == NULL ) { @@ -110,10 +110,10 @@ void CLDFB_RB_Close( CLDFB_RING_BUFFER_HANDLE *ph ) return; } -void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* imag, uint16_t num_bands ) +void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float* real, const float* imag, uint16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !CLDFB_RB_IsFull( h ) ); + assert( !CLDFB_RINGBUF_IsFull( h ) ); mvr2r( real, &h->real[h->write_pos], (int16_t) num_bands ); mvr2r( imag, &h->imag[h->write_pos], (int16_t) num_bands ); @@ -132,10 +132,10 @@ void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* return; } -void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ) +void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float* real, float* imag, uint16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !CLDFB_RB_IsEmpty( h ) ); + assert( !CLDFB_RINGBUF_IsEmpty( h ) ); if ( real != NULL ) { @@ -160,11 +160,11 @@ void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_ return; } -static uint32_t rb_num_floats( CLDFB_RING_BUFFER_HANDLE h ) +static uint32_t rb_num_floats( CLDFB_RINGBUF_HANDLE h ) { uint16_t ret; - if ( CLDFB_RB_IsFull( h ) ) + if ( CLDFB_RINGBUF_IsFull( h ) ) { return h->capacity; } @@ -182,7 +182,7 @@ static uint32_t rb_num_floats( CLDFB_RING_BUFFER_HANDLE h ) return ret; } -int16_t CLDFB_RB_GetByIdx( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, int16_t col_idx ) +int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float** p_real, float** p_imag, int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; int32_t num_floats = (int32_t) rb_num_floats(h); @@ -210,17 +210,17 @@ int16_t CLDFB_RB_GetByIdx( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p return 0; } -int16_t CLDFB_RB_IsEmpty( CLDFB_RING_BUFFER_HANDLE h ) +int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ) { return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); } -int16_t CLDFB_RB_IsFull( CLDFB_RING_BUFFER_HANDLE h ) +int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ) { return h->is_full; } -uint16_t CLDFB_RB_Size( CLDFB_RING_BUFFER_HANDLE h ) +uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ) { return rb_num_floats( h ) / CLDFB_NO_CHANNELS_MAX; } diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index eee1bf1faa..59fbcce5ee 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -30,31 +30,31 @@ *******************************************************************************************************/ -#ifndef CLDFB_RING_BUFFER_H -#define CLDFB_RING_BUFFER_H +#ifndef CLDFB_RINGBUF_H +#define CLDFB_RINGBUF_H #include "ivas_error.h" #include #ifdef FIX_1119_SPLIT_RENDERING_VOIP -typedef struct CldfbRingBuffer* CLDFB_RING_BUFFER_HANDLE; +typedef struct CldfbRingBuf* CLDFB_RINGBUF_HANDLE; -ivas_error CLDFB_RB_Open( CLDFB_RING_BUFFER_HANDLE *ph, int16_t capacity_columns ); +ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ); -void CLDFB_RB_Close( CLDFB_RING_BUFFER_HANDLE *ph ); +void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ); -void CLDFB_RB_Push( CLDFB_RING_BUFFER_HANDLE h, const float* real, const float* imag, uint16_t num_bands ); +void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float* real, const float* imag, uint16_t num_bands ); -void CLDFB_RB_Pop( CLDFB_RING_BUFFER_HANDLE h, float* real, float* imag, uint16_t num_bands ); +void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float* real, float* imag, uint16_t num_bands ); -int16_t CLDFB_RB_GetByIdx( CLDFB_RING_BUFFER_HANDLE h, float** p_real, float** p_imag, int16_t idx ); +int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float** p_real, float** p_imag, int16_t idx ); -int16_t CLDFB_RB_IsEmpty( CLDFB_RING_BUFFER_HANDLE h ); +int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ); -int16_t CLDFB_RB_IsFull( CLDFB_RING_BUFFER_HANDLE h ); +int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ); -uint16_t CLDFB_RB_Size( CLDFB_RING_BUFFER_HANDLE h ); +uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ); #endif /* FIX_1119_SPLIT_RENDERING_VOIP */ #endif diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 80262f605a..2ca50dc21f 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1668,7 +1668,7 @@ static void binRenderer_split( for ( ch = 0; ch < nchan_out; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_Push( + CLDFB_RINGBUF_Push( hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index 50f156e552..0775d1e15b 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -1787,7 +1787,7 @@ void ivas_param_mc_dec_render( for ( ch = 0; ch < nchan_out_cldfb; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index 45fcd5f440..d5732fec4e 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -791,7 +791,7 @@ static void ivas_mc_paramupmix_dec_sf( for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index cd1ae88521..af8465e787 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -800,7 +800,7 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); + CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); #else diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index 39a927c74a..65392ec896 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -185,7 +185,7 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( cldfbAnalysis_ts( &( output_f[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); + CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); for ( b = 0; b < num_cldfb_bands; b++ ) { diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index f1fa590de5..ef9b8b3d36 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -849,7 +849,7 @@ typedef struct typedef struct { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RING_BUFFER_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; + CLDFB_RINGBUF_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; #else ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE hMultiBinCldfbData; /*scratch buffer for frame by frame processing*/ #endif diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index e42138534e..8a0e8f2fa9 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1960,7 +1960,7 @@ static ivas_error isar_generate_metadata_and_bitstream( { /* Save pointers to first CLDFB column in the ring buffer. Allows us to save * significant amounts of memory by not copying CLDFB values into a separate buffer. */ - CLDFB_RB_GetByIdx( + CLDFB_RINGBUF_GetByIdx( hSplitBinRend->hMultiBinCldfbData[i], &p_Cldfb_RealBuffer_Binaural[i][j], &p_Cldfb_ImagBuffer_Binaural[i][j], @@ -1970,7 +1970,7 @@ static ivas_error isar_generate_metadata_and_bitstream( * the saved columns only before any new columns are pushed to the buffer - the new * columns could potentially overwrite the old columns we wanted to use. * This requirement is fulfilled in this case. */ - CLDFB_RB_Pop( + CLDFB_RINGBUF_Pop( hSplitBinRend->hMultiBinCldfbData[i], NULL, NULL, @@ -5225,7 +5225,7 @@ static void ivas_destroy_handle_isar( { if ( ( *hSplitBinRend )->hMultiBinCldfbData[i] != NULL ) { - CLDFB_RB_Close( &( *hSplitBinRend )->hMultiBinCldfbData[i] ); + CLDFB_RINGBUF_Close( &( *hSplitBinRend )->hMultiBinCldfbData[i] ); } } #else @@ -5454,7 +5454,7 @@ static ivas_error ivas_dec_init_split_rend( for ( i = 0; i < (int16_t) ( num_poses * BINAURAL_CHANNELS ); ++i ) { /* note: this is intra-frame heap memory */ - error = CLDFB_RB_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], cldfb_buffer_capacity ); + error = CLDFB_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], cldfb_buffer_capacity ); if ( error != IVAS_ERR_OK ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 85d6c57483..4aad1d076f 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -796,7 +796,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], @@ -874,7 +874,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RB_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], -- GitLab From c050055e2964622132fd9889faaf037d5eb76fd7 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 11:43:44 +0200 Subject: [PATCH 038/340] Disable VoIP split rendering tests for mixed formats (will be finalised separately) --- tests/split_rendering/test_split_rendering.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index a4bdca36dd..43af49b5f1 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -602,8 +602,9 @@ IN_FORMATS = [ "ISM4", "FOA", "MASA2TC", - "OSBA_ISM3_HOA3", - "OMASA_ISM4", + # Mixed formats to be finalised in a follow-up issue + # "OSBA_ISM3_HOA3", + # "OMASA_ISM4", ] DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] @@ -613,7 +614,7 @@ DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] # chain to ensure time-scaling and other JBM operations are BE between the two. @pytest.mark.parametrize("in_format", IN_FORMATS) @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) -def test_be_splitrend_vs_binaural( +def test_voip_be_splitrend_vs_binaural( in_format, delay_profile, dut_encoder_frontend, -- GitLab From f88052ca0b0181c541d6019d7e3bad15e8528248 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 11:55:36 +0200 Subject: [PATCH 039/340] Fix formatting --- apps/decoder.c | 98 +++++----- lib_dec/cldfb_ring_buffer.c | 15 +- lib_dec/cldfb_ring_buffer.h | 8 +- lib_dec/ivas_dirac_dec.c | 5 +- lib_dec/ivas_mc_param_dec.c | 5 +- lib_dec/ivas_mc_paramupmix_dec.c | 5 +- lib_dec/ivas_omasa_dec.c | 2 +- lib_dec/ivas_osba_dec.c | 4 +- lib_dec/ivas_output_config.c | 15 +- lib_dec/ivas_stat_dec.h | 2 +- lib_dec/lib_dec.c | 185 +++++++++---------- lib_isar/isar_splitRendererPre.c | 63 ++++--- lib_isar/lib_isar_pre_rend.c | 32 ++-- lib_rend/ivas_dirac_dec_binaural_functions.c | 6 +- 14 files changed, 217 insertions(+), 228 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 0a64284a99..abf7468d2b 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -171,11 +171,12 @@ typedef struct static bool parseCmdlIVAS_dec( int16_t argc, char **argv, DecArguments *arg ); static void usage_dec( void ); static ivas_error decodeG192( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); -static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, #endif - IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); + IVAS_DEC_HANDLE hIvasDec, + int16_t *pcmBuf ); static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary, IVAS_DEC_HANDLE hIvasDec, const IVAS_AUDIO_CONFIG OutputConfig, const int32_t output_Fs ); #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); @@ -758,9 +759,9 @@ int main( { error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - &splitRendBits, + &splitRendBits, #endif - hIvasDec, pcmBuf ); + hIvasDec, pcmBuf ); } else { @@ -2456,9 +2457,9 @@ static ivas_error decodeG192( /* decode transport channels, do TSM and feed to renderer */ if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, #ifndef FIX_1119_SPLIT_RENDERING_VOIP - isSplitRend, + isSplitRend, #endif - splitRendBits ) ) != IVAS_ERR_OK ) + splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -3377,37 +3378,36 @@ static ivas_error decodeVoIP( while ( nSamplesRendered < nOutSamples ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( isSplitRend ) - { - if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, + if ( isSplitRend ) + { + if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, #ifdef SUPPORT_JBM_TRACEFILE - writeJbmTraceFileFrameWrapper, - jbmTraceWriter, + writeJbmTraceFileFrameWrapper, + jbmTraceWriter, #endif - &bitstreamReadDone, - &nSamplesRendered, - ¶metersAvailableForEditing, - systemTime_ms - ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSplitBinauralBitstream: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; + &bitstreamReadDone, + &nSamplesRendered, + ¶metersAvailableForEditing, + systemTime_ms ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSplitBinauralBitstream: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } } - } - else - { + else + { #endif #ifdef SUPPORT_JBM_TRACEFILE - if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, &bitstreamReadDone, &nSamplesRendered, ¶meterAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #endif - { - fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } + { + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - } + } #endif if ( bitstreamReadDone == true ) @@ -3494,11 +3494,11 @@ static ivas_error decodeVoIP( if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - &vec_pos_len, + &vec_pos_len, #else - NULL, + NULL, #endif - delayNumSamples_orig, &delayNumSamples, &delayTimeScale, + delayNumSamples_orig, &delayNumSamples, &delayTimeScale, &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &splitRendWriter ) ) != IVAS_ERR_OK ) { fprintf( stderr, "Error in initOnFirstGoodFrame(): %s\n", IVAS_DEC_GetErrorMessage( error ) ); @@ -3526,19 +3526,19 @@ static ivas_error decodeVoIP( if ( !isSplitCoded ) { #endif - if ( delayNumSamples < nOutSamples ) - { - if ( ( error = AudioFileWriter_write( afWriter, &pcmBuf[delayNumSamples * nOutChannels], nOutSamples * nOutChannels - ( delayNumSamples * nOutChannels ) ) ) != IVAS_ERR_OK ) + if ( delayNumSamples < nOutSamples ) { - fprintf( stderr, "\nOutput audio file writer error\n" ); - goto cleanup; + if ( ( error = AudioFileWriter_write( afWriter, &pcmBuf[delayNumSamples * nOutChannels], nOutSamples * nOutChannels - ( delayNumSamples * nOutChannels ) ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nOutput audio file writer error\n" ); + goto cleanup; + } + delayNumSamples = 0; + } + else + { + delayNumSamples -= nOutSamples; } - delayNumSamples = 0; - } - else - { - delayNumSamples -= nOutSamples; - } #ifdef FIX_1119_SPLIT_RENDERING_VOIP } #endif @@ -3702,14 +3702,14 @@ static ivas_error decodeVoIP( memset( pcmBuf, 0, delayNumSamples_orig[0] * nOutChannels * sizeof( int16_t ) ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( afWriter != NULL) + if ( afWriter != NULL ) { #endif - if ( ( error = AudioFileWriter_write( afWriter, pcmBuf, delayNumSamples_orig[0] * nOutChannels ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError writing output file: %s\n", ivas_error_to_string( error ) ); - goto cleanup; - } + if ( ( error = AudioFileWriter_write( afWriter, pcmBuf, delayNumSamples_orig[0] * nOutChannels ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError writing output file: %s\n", ivas_error_to_string( error ) ); + goto cleanup; + } #ifdef FIX_1119_SPLIT_RENDERING_VOIP } #endif diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index 43a922f1b0..af64749025 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -40,9 +40,10 @@ #ifdef FIX_1119_SPLIT_RENDERING_VOIP -struct CldfbRingBuf { - float* real; - float* imag; +struct CldfbRingBuf +{ + float *real; + float *imag; uint32_t capacity; uint32_t write_pos; uint32_t read_pos; @@ -110,7 +111,7 @@ void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ) return; } -void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float* real, const float* imag, uint16_t num_bands ) +void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); assert( !CLDFB_RINGBUF_IsFull( h ) ); @@ -132,7 +133,7 @@ void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float* real, const float* return; } -void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float* real, float* imag, uint16_t num_bands ) +void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); assert( !CLDFB_RINGBUF_IsEmpty( h ) ); @@ -182,10 +183,10 @@ static uint32_t rb_num_floats( CLDFB_RINGBUF_HANDLE h ) return ret; } -int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float** p_real, float** p_imag, int16_t col_idx ) +int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; - int32_t num_floats = (int32_t) rb_num_floats(h); + int32_t num_floats = (int32_t) rb_num_floats( h ); uint32_t offset; if ( idx < -num_floats || num_floats <= idx ) diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index 59fbcce5ee..4486f0c628 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -38,17 +38,17 @@ #ifdef FIX_1119_SPLIT_RENDERING_VOIP -typedef struct CldfbRingBuf* CLDFB_RINGBUF_HANDLE; +typedef struct CldfbRingBuf *CLDFB_RINGBUF_HANDLE; ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ); void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ); -void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float* real, const float* imag, uint16_t num_bands ); +void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ); -void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float* real, float* imag, uint16_t num_bands ); +void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ); -int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float** p_real, float** p_imag, int16_t idx ); +int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t idx ); int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ); diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 2ca50dc21f..6c88bab9e6 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1670,10 +1670,9 @@ static void binRenderer_split( #ifdef FIX_1119_SPLIT_RENDERING_VOIP CLDFB_RINGBUF_Push( hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], - Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], + Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], - num_freq_bands - ); + num_freq_bands ); #else mvr2r( Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); mvr2r( Cldfb_ImagBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index 0775d1e15b..bb013b8d78 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -1787,12 +1787,11 @@ void ivas_param_mc_dec_render( for ( ch = 0; ch < nchan_out_cldfb; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], - hParamMC->num_freq_bands - ); + hParamMC->num_freq_bands ); #else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hParamMC->num_freq_bands ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], hParamMC->num_freq_bands ); diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index d5732fec4e..8fddef8ac8 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -791,12 +791,11 @@ static void ivas_mc_paramupmix_dec_sf( for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], - maxBand - ); + maxBand ); #else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index af8465e787..3ea63bfdf3 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -800,7 +800,7 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); + CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); #else diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index 65392ec896..e251f4da7a 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -189,8 +189,8 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( for ( b = 0; b < num_cldfb_bands; b++ ) { - re[b] = 0.5f * re[b] + 0.5f * Cldfb_RealBuffer[b]; - im[b] = 0.5f * im[b] + 0.5f * Cldfb_ImagBuffer[b]; + re[b] = 0.5f * re[b] + 0.5f * Cldfb_RealBuffer[b]; + im[b] = 0.5f * im[b] + 0.5f * Cldfb_ImagBuffer[b]; } #else for ( b = 0; b < num_cldfb_bands; b++ ) diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index f3e3e1b1cf..b74d2b6d8e 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -499,22 +499,21 @@ RENDERER_TYPE ivas_renderer_secondary_select( renderer_type = RENDERER_DISABLE; output_config = st_ivas->hDecoderConfig->output_config; - if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC && + if ( st_ivas->ivas_format == MASA_ISM_FORMAT && st_ivas->ism_mode == ISM_MASA_MODE_DISC && #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) #else - output_config == IVAS_AUDIO_CONFIG_BINAURAL + output_config == IVAS_AUDIO_CONFIG_BINAURAL #endif - ) + ) { renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } - else if ( st_ivas->ivas_format == SBA_ISM_FORMAT && st_ivas->ism_mode == ISM_SBA_MODE_DISC && ( - output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB + else if ( st_ivas->ivas_format == SBA_ISM_FORMAT && st_ivas->ism_mode == ISM_SBA_MODE_DISC && ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB #ifdef FIX_1119_SPLIT_RENDERING_VOIP - || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM + || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM #endif - ) ) + ) ) { renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index ef9b8b3d36..a05adf8f88 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -853,7 +853,7 @@ typedef struct #else ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE hMultiBinCldfbData; /*scratch buffer for frame by frame processing*/ #endif - ISAR_SPLIT_REND_BITS_HANDLE hSplitRendBits; /*scratch buffer for frame by frame processing*/ + ISAR_SPLIT_REND_BITS_HANDLE hSplitRendBits; /*scratch buffer for frame by frame processing*/ SPLIT_REND_WRAPPER splitrend; ISAR_DEC_SPLIT_REND_CLDFB_OUT_DATA_HANDLE hCldfbDataOut; /*buffer to store cldfb data before binauralization*/ int16_t numTdSamplesPerChannelCached; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 8a0e8f2fa9..0a64b9f352 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -116,9 +116,9 @@ static ivas_error input_format_API_to_internal( IVAS_DEC_INPUT_FORMAT input_form static void init_decoder_config( DECODER_CONFIG_HANDLE hDecoderConfig ); static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, #ifndef FIX_1119_SPLIT_RENDERING_VOIP - const int16_t isSplitRend, + const int16_t isSplitRend, #endif - ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); + ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); static ivas_error apa_setup( IVAS_DEC_HANDLE hIvasDec, const bool isInitialized_voip, const uint16_t nTransportChannels ); static PCM_RESOLUTION pcm_type_API_to_internal( const IVAS_DEC_PCM_TYPE pcmType ); static void *pcm_buffer_offset( void *buffer, const IVAS_DEC_PCM_TYPE pcmType, const int32_t offset ); @@ -1113,9 +1113,9 @@ ivas_error IVAS_DEC_ReadFormat( *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_GetSamplesDecoder( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ #ifndef FIX_1119_SPLIT_RENDERING_VOIP - const int16_t isSplitRend, /* i : split rendering enabled flag */ + const int16_t isSplitRend, /* i : split rendering enabled flag */ #endif ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) @@ -1149,9 +1149,9 @@ ivas_error IVAS_DEC_GetSamplesDecoder( if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, #ifndef FIX_1119_SPLIT_RENDERING_VOIP - isSplitRend, + isSplitRend, #endif - splitRendBits ) ) != IVAS_ERR_OK ) + splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -1842,8 +1842,7 @@ ivas_error IVAS_DEC_GetSamplesRenderer( #ifdef FIX_1119_SPLIT_RENDERING_VOIP static int16_t isar_get_frame_size( - Decoder_Struct* st_ivas -) + Decoder_Struct *st_ivas ) { int32_t output_Fs; int16_t nSamplesPerChannel; @@ -1865,11 +1864,11 @@ static int16_t isar_get_frame_size( } static ivas_error isar_render_poses( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ - float** p_head_pose_buf, - int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ - bool *needNewFrame /* o : indication that the decoder needs a new frame */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ + float **p_head_pose_buf, + int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ + bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) { Decoder_Struct *st_ivas; @@ -1921,9 +1920,9 @@ static ivas_error isar_render_poses( } static ivas_error isar_generate_metadata_and_bitstream( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - float** p_head_pose_buf, - ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + float **p_head_pose_buf, + ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { Decoder_Struct *st_ivas; @@ -1964,18 +1963,16 @@ static ivas_error isar_generate_metadata_and_bitstream( hSplitBinRend->hMultiBinCldfbData[i], &p_Cldfb_RealBuffer_Binaural[i][j], &p_Cldfb_ImagBuffer_Binaural[i][j], - 0 - ); - /* Pop the CLDFB column we just saved pointers to. This is fine as long as we use + 0 ); + /* Pop the CLDFB column we just saved pointers to. This is fine as long as we use * the saved columns only before any new columns are pushed to the buffer - the new * columns could potentially overwrite the old columns we wanted to use. * This requirement is fulfilled in this case. */ CLDFB_RINGBUF_Pop( - hSplitBinRend->hMultiBinCldfbData[i], + hSplitBinRend->hMultiBinCldfbData[i], NULL, NULL, - CLDFB_NO_CHANNELS_MAX - ); + CLDFB_NO_CHANNELS_MAX ); } } } @@ -2038,7 +2035,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( Decoder_Struct *st_ivas; ivas_error error; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; - float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; + float *p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; int32_t i; int16_t pcm_out_flag; int16_t numSamplesPerChannelToOutput; @@ -2050,21 +2047,21 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( error = IVAS_ERR_UNKNOWN; st_ivas = hIvasDec->st_ivas; - + if ( is_split_rendering_enabled( st_ivas->hDecoderConfig, st_ivas->hRenderConfig ) == 0 ) { return IVAS_ERR_WRONG_PARAMS; } pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; - numSamplesPerChannelToOutput = isar_get_frame_size(st_ivas); + numSamplesPerChannelToOutput = isar_get_frame_size( st_ivas ); for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { p_head_pose_buf[i] = head_pose_buf[i]; } - if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame)) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) { return error; } @@ -2297,10 +2294,10 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( *---------------------------------------------------------------------*/ static ivas_error ivas_dec_setup_all( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint8_t *nTransportChannels, /* o : number of decoded transport PCM channels */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + uint8_t *nTransportChannels, /* o : number of decoded transport PCM channels */ #ifndef FIX_1119_SPLIT_RENDERING_VOIP - const int16_t isSplitRend, /* i : split rendering enabled flag */ + const int16_t isSplitRend, /* i : split rendering enabled flag */ #endif ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) @@ -3698,23 +3695,23 @@ static ivas_error ivas_dec_voip_get_samples_common ivas_error IVAS_DEC_VoIP_GetSamples #endif -( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ - const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ - void *pcmBuf, /* o : output synthesis signal */ + ( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #endif #ifdef SUPPORT_JBM_TRACEFILE - JbmTraceFileWriterFn jbmWriterFn, - void *jbmWriter, + JbmTraceFileWriterFn jbmWriterFn, + void *jbmWriter, #endif - bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ - uint16_t *nSamplesRendered, /* o : number of samples rendered */ - bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ - const uint32_t systemTimestamp_ms /* i : current system timestamp */ -) + bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ + uint16_t *nSamplesRendered, /* o : number of samples rendered */ + bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ + const uint32_t systemTimestamp_ms /* i : current system timestamp */ + ) { Decoder_Struct *st_ivas; DECODER_CONFIG_HANDLE hDecoderConfig; @@ -3729,7 +3726,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples #ifdef FIX_1119_SPLIT_RENDERING_VOIP int32_t i; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; - float* p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; + float *p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; #endif if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->hVoIP == NULL ) @@ -3901,13 +3898,13 @@ ivas_error IVAS_DEC_VoIP_GetSamples { if ( hIvasDec->nSamplesAvailableNext == 0 || hIvasDec->nSamplesAvailableNext == hIvasDec->nSamplesFrame ) { - if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - splitRendBits + splitRendBits #else - 0, NULL + 0, NULL #endif - ) ) != IVAS_ERR_OK ) + ) ) != IVAS_ERR_OK ) { return error; } @@ -3930,7 +3927,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { /* Move output pointers forward */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) @@ -3939,7 +3936,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples } /* Render head poses from time-scaled transport channels */ - if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, &nSamplesRendered_loop, &tmp )) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) { return error; } @@ -3947,11 +3944,11 @@ ivas_error IVAS_DEC_VoIP_GetSamples else { #endif - /* render IVAS frames directly to the output buffer */ - if ( ( error = IVAS_DEC_GetSamplesRenderer( hIvasDec, nSamplesToRender, pcmType, pcm_buffer_offset( pcmBuf, pcmType, *nSamplesRendered * nOutChannels ), &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) - { - return error; - } + /* render IVAS frames directly to the output buffer */ + if ( ( error = IVAS_DEC_GetSamplesRenderer( hIvasDec, nSamplesToRender, pcmType, pcm_buffer_offset( pcmBuf, pcmType, *nSamplesRendered * nOutChannels ), &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) + { + return error; + } #ifdef FIX_1119_SPLIT_RENDERING_VOIP } #endif @@ -3962,9 +3959,9 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( hIvasDec->hasDecodedFirstGoodFrame && - ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || - hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) + if ( hIvasDec->hasDecodedFirstGoodFrame && + ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || + hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) { /* Set pointers to beginning of head pose buffers */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) @@ -3983,18 +3980,18 @@ ivas_error IVAS_DEC_VoIP_GetSamples { if ( st_ivas->hDecoderConfig->render_framesize == IVAS_RENDER_FRAMESIZE_5MS ) { - #ifndef DISABLE_LIMITER +#ifndef DISABLE_LIMITER ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, *nSamplesRendered, st_ivas->BER_detect ); - #endif +#endif } else { ivas_limiter_dec( st_ivas->hLimiter, p_head_pose_buf, st_ivas->hDecoderConfig->nchan_out, *nSamplesRendered, st_ivas->BER_detect ); } - #ifdef DEBUGGING +#ifdef DEBUGGING st_ivas->noClipping += - #endif +#endif ivas_syn_output( p_head_pose_buf, *nSamplesRendered, st_ivas->hDecoderConfig->nchan_out, (int16_t *) pcmBuf ); } } @@ -4010,7 +4007,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples * Main function to decode one frame in VoIP *---------------------------------------------------------------------*/ -ivas_error IVAS_DEC_VoIP_GetSamples ( +ivas_error IVAS_DEC_VoIP_GetSamples( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ @@ -4026,37 +4023,36 @@ ivas_error IVAS_DEC_VoIP_GetSamples ( ) { return ivas_dec_voip_get_samples_common( - hIvasDec, - nSamplesPerChannel, - pcmType, - pcmBuf, - NULL, + hIvasDec, + nSamplesPerChannel, + pcmType, + pcmBuf, + NULL, #ifdef SUPPORT_JBM_TRACEFILE - jbmWriterFn, - jbmWriter, + jbmWriterFn, + jbmWriter, #endif - bitstreamReadDone, - nSamplesRendered, - parametersAvailableForEditing, - systemTimestamp_ms - ); + bitstreamReadDone, + nSamplesRendered, + parametersAvailableForEditing, + systemTimestamp_ms ); } /*! r: error code */ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ - void *pcmBuf, /* o : output synthesis signal */ - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, - void* jbmWriter + void *jbmWriter #endif , - bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ - uint16_t *nSamplesRendered, /* o : number of samples rendered */ - bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ - const uint32_t systemTimestamp_ms /* i : current system timestamp */ + bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ + uint16_t *nSamplesRendered, /* o : number of samples rendered */ + bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ + const uint32_t systemTimestamp_ms /* i : current system timestamp */ ) { ivas_error error = IVAS_ERR_UNKNOWN; @@ -4069,20 +4065,19 @@ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( } return ivas_dec_voip_get_samples_common( - hIvasDec, - nSamplesPerChannel, - IVAS_DEC_PCM_INT16, - pcmBuf, - splitRendBits, + hIvasDec, + nSamplesPerChannel, + IVAS_DEC_PCM_INT16, + pcmBuf, + splitRendBits, #ifdef SUPPORT_JBM_TRACEFILE - jbmWriterFn, - jbmWriter, + jbmWriterFn, + jbmWriter, #endif - bitstreamReadDone, - nSamplesRendered, - parametersAvailableForEditing, - systemTimestamp_ms - ); + bitstreamReadDone, + nSamplesRendered, + parametersAvailableForEditing, + systemTimestamp_ms ); } #endif diff --git a/lib_isar/isar_splitRendererPre.c b/lib_isar/isar_splitRendererPre.c index 94aabfe3ce..370e02398d 100644 --- a/lib_isar/isar_splitRendererPre.c +++ b/lib_isar/isar_splitRendererPre.c @@ -55,13 +55,14 @@ static void isar_SplitRenderer_GetRotMd( ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float* Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], - float* Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], + float *Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], + float *Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], #else - float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], - float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], + float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], + float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], #endif - const int16_t low_res, const int16_t ro_md_flag ); + const int16_t low_res, + const int16_t ro_md_flag ); /*------------------------------------------------------------------------- @@ -288,8 +289,8 @@ static void ComputeBandedCrossCov( #endif const int16_t ch_start_idx1, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float* Cldfb_RealBuffer2[][CLDFB_NO_COL_MAX], - float* Cldfb_ImagBuffer2[][CLDFB_NO_COL_MAX], + float *Cldfb_RealBuffer2[][CLDFB_NO_COL_MAX], + float *Cldfb_ImagBuffer2[][CLDFB_NO_COL_MAX], #else float Cldfb_RealBuffer2[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer2[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], @@ -359,8 +360,8 @@ static void ComputeBandedCrossCov( static void ComputeBandedCov( #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float * Cldfb_RealBuffer[][CLDFB_NO_COL_MAX], - float * Cldfb_ImagBuffer[][CLDFB_NO_COL_MAX], + float *Cldfb_RealBuffer[][CLDFB_NO_COL_MAX], + float *Cldfb_ImagBuffer[][CLDFB_NO_COL_MAX], #else float Cldfb_RealBuffer[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_ImagBuffer[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], @@ -1368,11 +1369,11 @@ static void isar_SplitRenderer_quant_code( *------------------------------------------------------------------------*/ static void isar_SplitRenderer_GetRotMd( - ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i/o: binaural renderer handle */ - MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ + ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i/o: binaural renderer handle */ + MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ - float *Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ + float *Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ + float *Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX], /* o : Reference Binaural signals */ #else float Cldfb_RealBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* o : Reference Binaural signals */ float Cldfb_ImagBuffer_Ref_Binaural[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* o : Reference Binaural signals */ @@ -1458,20 +1459,20 @@ static void isar_SplitRenderer_GetRotMd( *------------------------------------------------------------------------*/ void isar_rend_CldfbSplitPreRendProcess( - const ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i : binaural pre-renderer handle */ - const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ - MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ + const ISAR_BIN_HR_SPLIT_PRE_REND_HANDLE hBinHrSplitPreRend, /* i : binaural pre-renderer handle */ + const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ + MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i : Binaural signals, real part */ - float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i : Binaural signals, imag. part */ + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i : Binaural signals, real part */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i : Binaural signals, imag. part */ #else - float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, real part */ - float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, imag. part */ + float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, real part */ + float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i : Binaural signals, imag. part */ #endif - ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits handle */ - const int32_t target_md_bits, /* i : ISAR MD bitrate */ - const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ - const int16_t ro_md_flag /* i : real only metadata for yaw flag */ + ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits handle */ + const int32_t target_md_bits, /* i : ISAR MD bitrate */ + const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ + const int16_t ro_md_flag /* i : real only metadata for yaw flag */ ) { push_wmops( "isar_rend_CldfbSplitPreRendProcess" ); @@ -1943,7 +1944,7 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( #ifdef FIX_1119_SPLIT_RENDERING_VOIP for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) { - for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) { p_Cldfb_In_BinReal[i][j] = Cldfb_In_BinReal[i][j]; p_Cldfb_In_BinImag[i][j] = Cldfb_In_BinImag[i][j]; @@ -2026,8 +2027,7 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( pBits, target_md_bits, low_res_pre_rend_rot, - ro_md_flag - ); + ro_md_flag ); } if ( pcm_out_flag == 0 ) @@ -2052,8 +2052,7 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( Cldfb_In_BinImag, #endif available_bits, - pBits - ); + pBits ); } else { @@ -2114,10 +2113,10 @@ ivas_error isar_renderMultiTDBinToSplitBinaural( *------------------------------------------------------------------------*/ void lc3plusTimeAlignCldfbPoseCorr( - SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renderer handle */ + SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renderer handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ - float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX] /* i/o: Binaural signals, imag. part */ + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX] /* i/o: Binaural signals, imag. part */ #else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: Binaural signals, real part */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX] /* ii/: Binaural signals, imag. part */ diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index 22807d1300..23ff136c51 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -273,27 +273,27 @@ void ISAR_PRE_REND_GetMultiBinPoseData( * *------------------------------------------------------------------------*/ -ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural ( - SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renerer handle */ - const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ - const int32_t SplitRendBitRate, /* i : Split renderer bitrate */ - ISAR_SPLIT_REND_CODEC splitCodec, /* i/o: Split renderer codec */ - const int16_t isar_frame_size_ms, /* i : ISAR framesize */ - int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ - ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ +ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( + SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renerer handle */ + const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ + const int32_t SplitRendBitRate, /* i : Split renderer bitrate */ + ISAR_SPLIT_REND_CODEC splitCodec, /* i/o: Split renderer codec */ + const int16_t isar_frame_size_ms, /* i : ISAR framesize */ + int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ + ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float* Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ - float* Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ #else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB real buffer */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB imag buffer */ #endif - const int16_t max_bands, /* i : CLDFB bands */ - float *output[], /* i/o: PCM in/out buffer */ - const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ - const int16_t cldfb_in_flag, /* i : Flag to indicate CLDFB or time doamin input */ - const int16_t pcm_out_flag, /* i : Flag to indicate PCM output */ - const int16_t ro_md_flag /* i : Flag to indicate real only metadata for yaw */ + const int16_t max_bands, /* i : CLDFB bands */ + float *output[], /* i/o: PCM in/out buffer */ + const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ + const int16_t cldfb_in_flag, /* i : Flag to indicate CLDFB or time doamin input */ + const int16_t pcm_out_flag, /* i : Flag to indicate PCM output */ + const int16_t ro_md_flag /* i : Flag to indicate real only metadata for yaw */ ) { ivas_error error; diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 4aad1d076f..33328e365c 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -800,8 +800,7 @@ static void ivas_dirac_dec_binaural_internal( st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], - CLDFB_NO_CHANNELS_MAX - ); + CLDFB_NO_CHANNELS_MAX ); #else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); @@ -878,8 +877,7 @@ static void ivas_dirac_dec_binaural_internal( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], - CLDFB_NO_CHANNELS_MAX - ); + CLDFB_NO_CHANNELS_MAX ); #else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); -- GitLab From a236486e232f4ddce6c9c88080e8e68f8248211e Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:00:42 +0200 Subject: [PATCH 040/340] Remove unused variable --- lib_dec/ivas_dirac_dec.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 6c88bab9e6..07c164d222 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1637,7 +1637,9 @@ static void binRenderer_split( float Cldfb_ImagBuffer_Binaural[][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX], /* o : Rotated Binaural signals */ float RealBuffer[][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX], /* i : LS signals */ float ImagBuffer[][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX], /* i : LS signals */ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP const int16_t slot_idx_start, +#endif const int16_t num_freq_bands, const int16_t nchan_out ) { @@ -2397,7 +2399,13 @@ void ivas_dirac_dec_render_sf( if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { binRenderer_split( st_ivas->hBinRenderer, st_ivas->hSplitBinRend, st_ivas->hCombinedOrientationData, hSpatParamRendCom->subframe_nbslots[subframe_idx], - Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, Cldfb_RealBuffer, Cldfb_ImagBuffer, slot_idx_start, hSpatParamRendCom->num_freq_bands, st_ivas->hDecoderConfig->nchan_out ); + Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, Cldfb_RealBuffer, Cldfb_ImagBuffer, slot_idx_start, hSpatParamRendCom->num_freq_bands +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + , + st_ivas->hDecoderConfig->nchan_out +#endif + ); + } else { -- GitLab From e95780d9134e3c3535ccbf8ef4a066b4a7e1acce Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:02:51 +0200 Subject: [PATCH 041/340] Fix formatting again --- lib_dec/ivas_dirac_dec.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 07c164d222..c1445a2c87 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2404,8 +2404,7 @@ void ivas_dirac_dec_render_sf( , st_ivas->hDecoderConfig->nchan_out #endif - ); - + ); } else { -- GitLab From e8c2162d2d15a7e1fbb95a76941d8ad190604edb Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:09:20 +0200 Subject: [PATCH 042/340] Fix msvc warnings --- lib_dec/lib_dec.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0a64b9f352..d40da41681 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -2061,7 +2061,8 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( p_head_pose_buf[i] = head_pose_buf[i]; } - if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) + error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ); + if ( error != IVAS_ERR_OK ) { return error; } @@ -2070,7 +2071,8 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( return IVAS_ERR_OK; } - if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ) ) ) + error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); + if ( error != IVAS_ERR_OK ) { return error; } @@ -3970,7 +3972,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples } /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ - if ( ( error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ) ) ) + error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); + if ( error != IVAS_ERR_OK ) { return error; } -- GitLab From fad8d217b58631feafb08f47027cf981b0fe1587 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:16:34 +0200 Subject: [PATCH 043/340] Add new files to msvc workspace --- Workspace_msvc/lib_dec.vcxproj | 4 +++- Workspace_msvc/lib_dec.vcxproj.filters | 8 +++++++- lib_dec/cldfb_ring_buffer.h | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index 3c7ea3e597..944e264667 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -147,6 +147,7 @@ + @@ -312,6 +313,7 @@ + @@ -348,4 +350,4 @@ - \ No newline at end of file + diff --git a/Workspace_msvc/lib_dec.vcxproj.filters b/Workspace_msvc/lib_dec.vcxproj.filters index 8eddbb60a0..5197d66457 100644 --- a/Workspace_msvc/lib_dec.vcxproj.filters +++ b/Workspace_msvc/lib_dec.vcxproj.filters @@ -277,6 +277,9 @@ decoder_all_c + + decoder_all_c + decoder_all_c @@ -517,6 +520,9 @@ + + decoder_h + decoder_h @@ -569,4 +575,4 @@ {c33b80b3-67ce-466b-91c0-4adfc9efcb5c} - \ No newline at end of file + diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index 4486f0c628..20979174c6 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -30,8 +30,8 @@ *******************************************************************************************************/ -#ifndef CLDFB_RINGBUF_H -#define CLDFB_RINGBUF_H +#ifndef CLDFB_RING_BUFFER_H +#define CLDFB_RING_BUFFER_H #include "ivas_error.h" #include -- GitLab From 2ba5e1a27f4f3722751d0303317f13d4abd4bacd Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:24:54 +0200 Subject: [PATCH 044/340] Fix new msvc warnings --- lib_dec/cldfb_ring_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index af64749025..fab273bd63 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -163,7 +163,7 @@ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16 static uint32_t rb_num_floats( CLDFB_RINGBUF_HANDLE h ) { - uint16_t ret; + uint32_t ret; if ( CLDFB_RINGBUF_IsFull( h ) ) { @@ -223,7 +223,7 @@ int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ) uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ) { - return rb_num_floats( h ) / CLDFB_NO_CHANNELS_MAX; + return (uint16_t) ( rb_num_floats( h ) / CLDFB_NO_CHANNELS_MAX ); } #endif -- GitLab From 263aa417281f19f23ad180df1c9c5bdba2456274 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 12:35:21 +0200 Subject: [PATCH 045/340] Increase timeout for test cases in split-rendering-smoke-test --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8e3e1cba90..11589a6fc4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -680,7 +680,7 @@ split-rendering-smoke-test: stage: test script: - make -j - - testcase_timeout=10 + - testcase_timeout=15 - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" -- GitLab From b2bbac705eff9fef4697e6bc138e6b3d50a22ba7 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 13:30:28 +0200 Subject: [PATCH 046/340] Increase timeout even more for test cases in split-rendering-smoke-test --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11589a6fc4..0337687ed9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -680,7 +680,7 @@ split-rendering-smoke-test: stage: test script: - make -j - - testcase_timeout=15 + - testcase_timeout=20 - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" -- GitLab From f622375e99a8caef9e185a4309aab2f3e01df4d7 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 13:52:22 +0200 Subject: [PATCH 047/340] Fix wrong argument removed --- lib_dec/ivas_dirac_dec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index c1445a2c87..16ca09a42e 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2399,11 +2399,11 @@ void ivas_dirac_dec_render_sf( if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { binRenderer_split( st_ivas->hBinRenderer, st_ivas->hSplitBinRend, st_ivas->hCombinedOrientationData, hSpatParamRendCom->subframe_nbslots[subframe_idx], - Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, Cldfb_RealBuffer, Cldfb_ImagBuffer, slot_idx_start, hSpatParamRendCom->num_freq_bands + Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, Cldfb_RealBuffer, Cldfb_ImagBuffer, #ifndef FIX_1119_SPLIT_RENDERING_VOIP - , - st_ivas->hDecoderConfig->nchan_out + slot_idx_start, #endif + hSpatParamRendCom->num_freq_bands, st_ivas->hDecoderConfig->nchan_out ); } else -- GitLab From 74cf1096d7c724b17286bf96505f22294577cf88 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 14:08:38 +0200 Subject: [PATCH 048/340] Fix formatting again, again --- lib_dec/ivas_dirac_dec.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 16ca09a42e..df747b0d1e 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2403,8 +2403,7 @@ void ivas_dirac_dec_render_sf( #ifndef FIX_1119_SPLIT_RENDERING_VOIP slot_idx_start, #endif - hSpatParamRendCom->num_freq_bands, st_ivas->hDecoderConfig->nchan_out - ); + hSpatParamRendCom->num_freq_bands, st_ivas->hDecoderConfig->nchan_out ); } else { -- GitLab From 5f8e03b45ad3a3d174ab6a8125131d7756ac000d Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 26 Jun 2025 14:13:09 +0200 Subject: [PATCH 049/340] Increase timeout (again) for test cases in split-rendering-smoke-test --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0337687ed9..9ca78cbe07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -680,7 +680,7 @@ split-rendering-smoke-test: stage: test script: - make -j - - testcase_timeout=20 + - testcase_timeout=30 - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" -- GitLab From 53cd2f556a2c4e9a27500da4099ae1e5e41979ae Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 27 Jun 2025 15:20:50 +0200 Subject: [PATCH 050/340] Fix merge error that broke CLDFB path in split rendering --- lib_dec/ivas_dirac_dec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index df747b0d1e..b075df36b9 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1672,8 +1672,8 @@ static void binRenderer_split( #ifdef FIX_1119_SPLIT_RENDERING_VOIP CLDFB_RINGBUF_Push( hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], - Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], - Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], + Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], + Cldfb_ImagBuffer_Binaural_loc[pos_idx][ch][slot_idx], num_freq_bands ); #else mvr2r( Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_idx_start + slot_idx], num_freq_bands ); -- GitLab From bd046407fd5eef4531676baa26326d7b4ca77bfd Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 27 Jun 2025 16:10:38 +0200 Subject: [PATCH 051/340] Do not overwrite BFI status in ISAR_post_rend --- apps/isar_post_rend.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/isar_post_rend.c b/apps/isar_post_rend.c index 843bbe85c1..9595c8d380 100644 --- a/apps/isar_post_rend.c +++ b/apps/isar_post_rend.c @@ -1110,10 +1110,13 @@ int main( #ifdef FIX_1119_SPLIT_RENDERING_VOIP /* Set BFI if frame is empty */ int16_t frameEmpty = (int16_t) ( bitsBuffer.config.bitsWritten == 0 ); - if ( ( error = ISAR_POST_REND_SetSplitRendBFI( hIsarPostRend, frameEmpty ) ) != IVAS_ERR_OK ) + if ( frameEmpty ) { - fprintf( stderr, "Error in ISAR_POST_REND_SetSplitRendBFI(): %s\n", ivas_error_to_string( error ) ); - goto cleanup; + if ( ( error = ISAR_POST_REND_SetSplitRendBFI( hIsarPostRend, 1 ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "Error in ISAR_POST_REND_SetSplitRendBFI(): %s\n", ivas_error_to_string( error ) ); + goto cleanup; + } } #endif } -- GitLab From ba624cf8f74ed0b32a175bab627ef05a92dfdef0 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 30 Jun 2025 10:05:51 +0300 Subject: [PATCH 052/340] Add isGoodFrame parameter to function call --- lib_util/ivas_rtp_payload.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index e99ff585d6..d952450b52 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -687,7 +687,7 @@ IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( if ( !ivasPayload_unpackFrame( self->rtpPacket.data + self->rtpPacket.headerSize, self->rtpPacket.payloadSize, self->frameIndex, evsIvasModeBit, &self->frameFollowing, bitrateIndex, ivasIndicatorBit, - frame, frameSizeBits, &self->PIdataDepackerState ) ) + isGoodFrame, frame, frameSizeBits, &self->PIdataDepackerState ) ) { return IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR; } @@ -811,6 +811,7 @@ void IVAS_RTPDUMP_PACKER_writeEbytes( } else { + // TODO(pajunen): add EVS CMRs here /* EVS CMR */ initialEbyteWritten = false; } -- GitLab From 6f1bb9ec7b70cbebe36a6d81d879107c29f7aed2 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 30 Jun 2025 10:57:28 +0200 Subject: [PATCH 053/340] Move test_voip_be_splitrend_vs_binaural into a separate file and CI job --- .gitlab-ci.yml | 24 ++- tests/split_rendering/test_split_rendering.py | 119 +------------- .../test_voip_be_splitrend_vs_binaural.py | 152 ++++++++++++++++++ tests/test_be_for_jbm_neutral_dly_profile.py | 4 +- 4 files changed, 178 insertions(+), 121 deletions(-) create mode 100644 tests/split_rendering/test_voip_be_splitrend_vs_binaural.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ca78cbe07..ff74e8f7e3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -680,7 +680,7 @@ split-rendering-smoke-test: stage: test script: - make -j - - testcase_timeout=30 + - testcase_timeout=10 - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" @@ -693,6 +693,28 @@ split-rendering-smoke-test: junit: - report-junit.xml +# test split rendering in VoIP mode against BINAURAL output +split-rendering-voip-be-to-binaural: + extends: + - .test-job-linux + - .rules-merge-request-to-main + needs: ["build-codec-linux-make"] + stage: test + script: + - make -j + - testcase_timeout=30 + - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/split_rendering/test_voip_be_splitrend_vs_binaural.py --testcase_timeout=$testcase_timeout + artifacts: + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" + expire_in: 1 week + when: always + paths: + - report-junit.xml + expose_as: "VoIP split rendering vs BINAURAL make pytest results" + reports: + junit: + - report-junit.xml + lc3-wrapper-unit-test: extends: - .test-job-linux diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index 43af49b5f1..71db62a4b3 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -32,19 +32,9 @@ import pytest -from tempfile import TemporaryDirectory -from pathlib import Path -import filecmp - from tests.split_rendering.utils import * -from tests.split_rendering.constants import SCRIPTS_DIR, TESTV_DIR -from tests.test_be_for_jbm_neutral_dly_profile import ( - INPUT_FILES, - get_options_cod, -) -from split_rendering.isar_bstool import IsarBitstream -from pyaudio3dtools import audioarray, audiofile + """ Ambisonics """ @@ -595,110 +585,3 @@ def test_framing_combinations_full_chain_split( get_odg=get_odg, get_odg_bin=get_odg_bin, ) - - -IN_FORMATS = [ - "MC_5_1", - "ISM4", - "FOA", - "MASA2TC", - # Mixed formats to be finalised in a follow-up issue - # "OSBA_ISM3_HOA3", - # "OMASA_ISM4", -] - -DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] - - -# Compares PCM output and tracefile from a VoIP BINAURAL_SPLIT_PCM chain with equivalent BINAURAL -# chain to ensure time-scaling and other JBM operations are BE between the two. -@pytest.mark.parametrize("in_format", IN_FORMATS) -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) -def test_voip_be_splitrend_vs_binaural( - in_format, - delay_profile, - dut_encoder_frontend, - dut_decoder_frontend, - bitrate=128000, -): - with TemporaryDirectory() as tmp_dir: - tmp_dir = Path(tmp_dir) - - sampling_rate_khz = 48 - delay_profile_path = SCRIPTS_DIR / "dly_error_profiles" / delay_profile - delay_profile_id = int(delay_profile[-5]) - - # run encoder - bitstream_file = (tmp_dir / f"{in_format}-dly{delay_profile_id}.192").absolute() - dtx = False - wav_in = TESTV_DIR / INPUT_FILES[in_format] - dut_encoder_frontend.run( - bitrate, - sampling_rate_khz, - wav_in, - bitstream_file, - add_option_list=get_options_cod(in_format, dtx), - run_dir=tmp_dir, - ) - - def run_decoder(out_format): - options = [] - - # Head trajectory must be static due to the slight time shift between audio outputs of BINAURAL/SPLIT_PCM - head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") - options.extend(["-T", str(head_traj)]) - - wav_out = ( - tmp_dir - / f"{in_format}-{bitrate}-{out_format}-dly{delay_profile_id}.wav" - ).absolute() - - trace_out = wav_out.with_suffix(".trace") - options.extend(["-Tracefile", str(trace_out)]) - - if out_format == "BINAURAL_SPLIT_PCM": - isar_md_file = wav_out.with_suffix(".isarmd") - options.extend(["-om", str(isar_md_file)]) - else: - isar_md_file = None - - dut_decoder_frontend.run( - out_format, - sampling_rate_khz, - bitstream_file, - wav_out, - netsim_profile=delay_profile_path, - add_option_list=options, - ) - - return wav_out, trace_out, isar_md_file - - wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") - wav_out_sr, trace_out_sr, isar_md_out_sr = run_decoder("BINAURAL_SPLIT_PCM") - - # Delay-align audio - assert isar_md_out_sr is not None - sr_delay_samples = IsarBitstream(isar_md_out_sr).delay_samples - audio_sr, _ = audiofile.readfile(str(wav_out_sr)) - audio_bin, _ = audiofile.readfile(str(wav_out_bin)) - audio_sr = audio_sr[sr_delay_samples:] - audio_bin = audio_bin[:-sr_delay_samples] - - # Ensure audio and tracefiles are BE - audio_cmp_result = audioarray.compare( - audio_bin, audio_sr, fs=sampling_rate_khz * 1000, per_frame=False - ) - tracefiles_equal = filecmp.cmp(trace_out_bin, trace_out_sr) - failed = not audio_cmp_result["bitexact"] or not tracefiles_equal - if failed: - message = [] - if not audio_cmp_result["bitexact"]: - message.append( - "Difference found between delay-aligned BINAURAL audio and BINAURAL_SPLIT_PCM audio! " - f"Max abs diff: {audio_cmp_result['max_abs_diff']}" - ) - if not tracefiles_equal: - message.append( - "Difference found between BINAURAL tracefile and BINAURAL_SPLIT_PCM tracefile!" - ) - pytest.fail("; ".join(message)) diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py new file mode 100644 index 0000000000..1df8c05634 --- /dev/null +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +""" + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. +""" + +import pytest + +from tempfile import TemporaryDirectory +from pathlib import Path +import filecmp + +from tests.split_rendering.utils import * +from tests.split_rendering.constants import SCRIPTS_DIR, TESTV_DIR +from tests.test_be_for_jbm_neutral_dly_profile import ( + INPUT_FILES, + get_options_cod, +) +from split_rendering.isar_bstool import IsarBitstream +from pyaudio3dtools import audioarray, audiofile + +IN_FORMATS = [ + "MC_5_1", + "ISM4", + "FOA", + "MASA2TC", + # Mixed formats to be finalised in a follow-up issue + # "OSBA_ISM3_HOA3", + # "OMASA_ISM4", +] + +DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] + + +# Compares PCM output and tracefile from a VoIP BINAURAL_SPLIT_PCM chain with equivalent BINAURAL +# chain to ensure time-scaling and other JBM operations are BE between the two. +@pytest.mark.parametrize("in_format", IN_FORMATS) +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) +def test_voip_be_splitrend_vs_binaural( + in_format, + delay_profile, + dut_encoder_frontend, + dut_decoder_frontend, + bitrate=128000, +): + with TemporaryDirectory() as tmp_dir: + tmp_dir = Path(tmp_dir) + + sampling_rate_khz = 48 + delay_profile_path = SCRIPTS_DIR / "dly_error_profiles" / delay_profile + delay_profile_id = int(delay_profile[-5]) + + # run encoder + bitstream_file = (tmp_dir / f"{in_format}-dly{delay_profile_id}.192").absolute() + dtx = False + wav_in = TESTV_DIR / INPUT_FILES[in_format] + dut_encoder_frontend.run( + bitrate, + sampling_rate_khz, + wav_in, + bitstream_file, + add_option_list=get_options_cod(in_format, dtx), + run_dir=tmp_dir, + ) + + def run_decoder(out_format): + options = [] + + # Head trajectory must be static due to the slight time shift between audio outputs of BINAURAL/SPLIT_PCM + head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") + options.extend(["-T", str(head_traj)]) + + wav_out = ( + tmp_dir + / f"{in_format}-{bitrate}-{out_format}-dly{delay_profile_id}.wav" + ).absolute() + + trace_out = wav_out.with_suffix(".trace") + options.extend(["-Tracefile", str(trace_out)]) + + if out_format == "BINAURAL_SPLIT_PCM": + isar_md_file = wav_out.with_suffix(".isarmd") + options.extend(["-om", str(isar_md_file)]) + else: + isar_md_file = None + + dut_decoder_frontend.run( + out_format, + sampling_rate_khz, + bitstream_file, + wav_out, + netsim_profile=delay_profile_path, + add_option_list=options, + ) + + return wav_out, trace_out, isar_md_file + + wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") + wav_out_sr, trace_out_sr, isar_md_out_sr = run_decoder("BINAURAL_SPLIT_PCM") + + # Delay-align audio + assert isar_md_out_sr is not None + sr_delay_samples = IsarBitstream(isar_md_out_sr).delay_samples + audio_sr, _ = audiofile.readfile(str(wav_out_sr)) + audio_bin, _ = audiofile.readfile(str(wav_out_bin)) + audio_sr = audio_sr[sr_delay_samples:] + audio_bin = audio_bin[:-sr_delay_samples] + + # Ensure audio and tracefiles are BE + audio_cmp_result = audioarray.compare( + audio_bin, audio_sr, fs=sampling_rate_khz * 1000, per_frame=False + ) + tracefiles_equal = filecmp.cmp(trace_out_bin, trace_out_sr) + failed = not audio_cmp_result["bitexact"] or not tracefiles_equal + if failed: + message = [] + if not audio_cmp_result["bitexact"]: + message.append( + "Difference found between delay-aligned BINAURAL audio and BINAURAL_SPLIT_PCM audio! " + f"Max abs diff: {audio_cmp_result['max_abs_diff']}" + ) + if not tracefiles_equal: + message.append( + "Difference found between BINAURAL tracefile and BINAURAL_SPLIT_PCM tracefile!" + ) + pytest.fail("; ".join(message)) diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index 0e2b2420f1..c755c11e6b 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -101,7 +101,7 @@ def is_split_rend(format) -> bool: def get_options_cod(in_format, dtx): - # NOTE: this function is shared with another test in tests/split_rendering/test_split_rendering.py + # NOTE: this function is shared with another test in tests/split_rendering/test_voip_be_splitrend_vs_binaural.py options = list() if dtx: @@ -169,7 +169,7 @@ def get_options_dec( return options -# NOTE: this list is shared with another test in tests/split_rendering/test_split_rendering.py +# NOTE: this list is shared with another test in tests/split_rendering/test_voip_be_splitrend_vs_binaural.py INPUT_FILES = { "stereo": "stvST48n.wav", "ISM1": "stv1ISM48s.wav", -- GitLab From cbffa05dbbe6d730858d0131362919320259f2e6 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 30 Jun 2025 11:46:06 +0200 Subject: [PATCH 054/340] Add documentation to cldfb_ring_buffer.h --- lib_dec/cldfb_ring_buffer.h | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index 20979174c6..3403bc1add 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -40,20 +40,70 @@ typedef struct CldfbRingBuf *CLDFB_RINGBUF_HANDLE; +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_Open( ) + * + * Allocate a ring buffer for CLDFB data with the given capacity of CLDFB columns. + * Each column is expected to contain at most CLDFB_NO_CHANNELS_MAX frequency bands. + * + * May return IVAS_ERR_FAILED_ALLOC on failed allocation, or IVAS_ERR_OK otherwise. + *---------------------------------------------------------------------*/ ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_Close( ) + * + * Dellocate CLDFB ring buffer. The given handle will be set to NULL. + *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_Push( ) + * + * Push a single column onto the back of the CLDFB ring buffer from real and imag arrays. + *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_Pop( ) + * + * Pop a single column from the front of the CLDFB ring buffer into real and imag arrays. + *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_GetByIdx( ) + * + * Get pointers into a specific column in the CLDFB ring buffer based on given index. + * Non-negative indices access from the front of the ring buffer, negative indexes access + * from the back, similar to Python arrays. For example: + * + * - index 0 accesses the front of the buffer, i.e. the oldest CLDFB column in the queue. + * - index -1 accesses the back of the buffer, i.e. the newest (last pushed) CLDFB column in the queue. + * + * Returns -1 if given an invalid index (p_real and p_imag will be set to NULL in that case), or 0 otherwise. + *---------------------------------------------------------------------*/ int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t idx ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_IsEmpty( ) + * + * Returns 1 if the ring buffer is empty, or 0 otherwise. + *---------------------------------------------------------------------*/ int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_IsFull( ) + * + * Returns 1 if the ring buffer is full, or 0 otherwise. + *---------------------------------------------------------------------*/ int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ); +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_Size( ) + * + * Returns the number of CLDFB columns currently stored in the ring buffer. + *---------------------------------------------------------------------*/ uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ); #endif /* FIX_1119_SPLIT_RENDERING_VOIP */ -- GitLab From 460039173e01a9ded385763630ea2fa5b5c50366 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 30 Jun 2025 12:07:06 +0200 Subject: [PATCH 055/340] Fix copyright date --- scripts/split_rendering/isar_bstool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/split_rendering/isar_bstool.py b/scripts/split_rendering/isar_bstool.py index 6b1e53b662..735d221f49 100755 --- a/scripts/split_rendering/isar_bstool.py +++ b/scripts/split_rendering/isar_bstool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -- GitLab From 41c559e5fe14851a71839bbb772facb25dcf8226 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 30 Jun 2025 16:44:50 +0200 Subject: [PATCH 056/340] Clean up whitespace --- tests/split_rendering/test_split_rendering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index 71db62a4b3..a881a7ad23 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -34,7 +34,7 @@ import pytest from tests.split_rendering.utils import * - + """ Ambisonics """ -- GitLab From 514658b5111ac672a3670ca27413cbc6c93a492f Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 30 Jun 2025 16:47:32 +0200 Subject: [PATCH 057/340] Remove leftover TODO (already resolved on main) --- tests/test_be_for_jbm_neutral_dly_profile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index c755c11e6b..8642bc5658 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -210,7 +210,6 @@ def compare_audio(non_voip_output, voip_output, sampling_rate_khz): x_jbm, _ = audiofile.readfile(voip_output) # strip jbm delay - # TODO: this may need to be adapted to handle variable offsets based on outcome of #1122 cmp_result = audioarray.compare( x, x_jbm, -- GitLab From db229008338ccc6ee8f5f305d5daaf46b0cf801a Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 1 Jul 2025 10:06:53 +0200 Subject: [PATCH 058/340] Add issue number to TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR switch --- lib_com/options.h | 2 +- lib_dec/ivas_jbm_dec.c | 2 +- lib_dec/ivas_stat_dec.h | 2 +- lib_dec/lib_dec.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 4260d7894c..1fc99e7c72 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -161,7 +161,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ -#define TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Workaround for incorrect implementation of decoder flush with split rendering */ +#define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1335_EXTREND_RETCODE /* FhG: Add modification of returncode for external renderer when an error occurs */ diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 15a6898f75..835f896efa 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -1312,7 +1312,7 @@ ivas_error ivas_jbm_dec_render( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { nchan_out_syn_output = BINAURAL_CHANNELS * st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; -#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR +#ifdef TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR if ( st_ivas->flushing ) { nchan_out_syn_output = BINAURAL_CHANNELS; diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index a05adf8f88..53651d186a 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -1147,7 +1147,7 @@ typedef struct Decoder_Struct int16_t ism_extmeta_active; /* Extended metadata active in decoder */ int16_t ism_extmeta_cnt; /* Change frame counter for extended metadata */ -#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR +#ifdef TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR int16_t flushing; #endif diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index d40da41681..b57971b897 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -4134,7 +4134,7 @@ ivas_error IVAS_DEC_Flush( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } -#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR +#ifdef TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR hIvasDec->st_ivas->flushing = 1; #endif @@ -4153,7 +4153,7 @@ ivas_error IVAS_DEC_Flush( *nSamplesFlushed = 0; } -#ifdef TMP_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR +#ifdef TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR hIvasDec->st_ivas->flushing = 0; #endif -- GitLab From e0d505fe1dabc16af2b5dc1da098309a441c67b8 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 1 Jul 2025 10:37:19 +0200 Subject: [PATCH 059/340] Add issue number to disabled OMASA and OSBA tests in test_voip_be_splitrend_vs_binaural.py --- tests/split_rendering/test_voip_be_splitrend_vs_binaural.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py index 1df8c05634..3c7b1da6ec 100644 --- a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -50,7 +50,7 @@ IN_FORMATS = [ "ISM4", "FOA", "MASA2TC", - # Mixed formats to be finalised in a follow-up issue + # Mixed formats to be finalised in a follow-up issue #1343 # "OSBA_ISM3_HOA3", # "OMASA_ISM4", ] -- GitLab From a425704b90c4337443f42317eff6ced83af9c5ac Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 2 Jul 2025 12:11:17 +0200 Subject: [PATCH 060/340] Do not allocate a bigger CLDFB ring buffer when TSM is enabled --- lib_dec/lib_dec.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index b57971b897..bdb69ab8fc 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5418,7 +5418,7 @@ static ivas_error ivas_dec_init_split_rend( int16_t cldfb_in_flag, pcm_out_flag; int16_t mixed_td_cldfb_flag; #ifdef FIX_1119_SPLIT_RENDERING_VOIP - int16_t i, num_poses, cldfb_buffer_capacity; + int16_t i, num_poses; #endif pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; @@ -5439,20 +5439,10 @@ static ivas_error ivas_dec_init_split_rend( if ( cldfb_in_flag ) { - if ( st_ivas->hDecoderConfig->Opt_tsm ) - { - /* With TSM we need more space for stretched frames */ - cldfb_buffer_capacity = CLDFB_NO_COL_MAX * 2; - } - else - { - cldfb_buffer_capacity = CLDFB_NO_COL_MAX; - } - for ( i = 0; i < (int16_t) ( num_poses * BINAURAL_CHANNELS ); ++i ) { /* note: this is intra-frame heap memory */ - error = CLDFB_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], cldfb_buffer_capacity ); + error = CLDFB_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], CLDFB_NO_COL_MAX ); if ( error != IVAS_ERR_OK ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); -- GitLab From e568f54d6d6f05ec232fd6d3d5eb1b64fe8e7629 Mon Sep 17 00:00:00 2001 From: "Singh, Ripinder" Date: Thu, 3 Jul 2025 12:01:55 +1000 Subject: [PATCH 061/340] Introduce an ivas rtp api for pack/unpack * Add support for pack/unpack of ivas and evs frames * Support for pi data * Support for remote requests and frame info Signed-off-by: Singh, Ripinder --- lib_util/ivas_rtp_api.h | 470 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 470 insertions(+) create mode 100644 lib_util/ivas_rtp_api.h diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h new file mode 100644 index 0000000000..7581b183a2 --- /dev/null +++ b/lib_util/ivas_rtp_api.h @@ -0,0 +1,470 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_RTP_API_H +#define IVAS_RTP_API_H + +#pragma once +#include +#include +#include "ivas_error.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* + * +-----------------------+---------------------+--------------------+----------+ + * | RTP Header (+ HDREXT) | payload header | frame data | PI data | + * +-----------------------+---------------------+--------------------+----------+ + * \--------------------\ /------------------------------/ + * IVAS payload + * + * This api provides a mechanism to generate/unpack the IVAS payload. The RTP Header + * and header extension fields must be handled by caller. + * + * IVAS General Payload structure + * =============================== + * + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * H H H H F H + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1| T | D |1| ET1 |x x x x|1| ET2 |x x x x|0|1|0 1| BR |1| ET3 |x x x x|… + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \--------------/\--------------/\--------------/\--------------/\--------------/ + * Initial E byte Subsqnt Ebyte1 Subsqnt Ebyte2 ToC1 Subsqnt Ebyte3 + * + * H F + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * …|0|0|0 1| BR | IVAS frame 1 ... | IVAS frame 2 ... |PI data| + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * \--------------/ + * ToC2 + * + */ + +#define NO_BITRATE_REQ ( 0u ) /* If no bitrate is requested from remote */ + + /* IVAS Codec Types */ + typedef enum + { + IVAS_RTP_EVS, /* EVS */ + IVAS_RTP_IVAS /* IVAS */ + } IVAS_RTP_CODECS; + + /* IVAS Bandwidth Requests */ + typedef enum + { + IVAS_BANDWIDTH_NB, /* Narrowband */ + IVAS_BANDWIDTH_WB, /* Wideband*/ + IVAS_BANDWIDTH_SWB, /* SuperWideband*/ + IVAS_BANDWIDTH_FB, /* Fullband */ + IVAS_BANDWIDTH_NO_REQ, /* No Preference */ + } IVAS_RTP_BANDWIDTH; + + /* Channel Aware Coding */ + typedef enum + { + IVAS_RTP_CA_LO_O2, /* FER=LO, OFFSET=2 */ + IVAS_RTP_CA_LO_O3, /* FER=LO, OFFSET=3 */ + IVAS_RTP_CA_LO_O5, /* FER=LO, OFFSET=5 */ + IVAS_RTP_CA_LO_O7, /* FER=LO, OFFSET=7 */ + IVAS_RTP_CA_HI_O2, /* FER=HI, OFFSET=2 */ + IVAS_RTP_CA_HI_O3, /* FER=HI, OFFSET=3 */ + IVAS_RTP_CA_HI_O5, /* FER=HI, OFFSET=5 */ + IVAS_RTP_CA_HI_O7, /* FER=HI, OFFSET=7 */ + IVAS_RTP_CA_NO_REQ /* No request */ + } IVAS_RTP_CA_MODE; + + /* Coded Format Requests */ + typedef enum + { + IVAS_FMT_STEREO, /* Stereo */ + IVAS_FMT_SBA, /* Scene Based Audio */ + IVAS_FMT_MASA, /* Metadata Assisted Spatial Audio */ + IVAS_FMT_ISM, /* Object Based Audio */ + IVAS_FMT_MC, /* Multichannel Audio */ + IVAS_FMT_OMASA, /* Object + MASA */ + IVAS_FMT_OSBA, /* Object + SBA */ + IVAS_FMT_NO_REQ, /* No preference */ + } IVAS_RTP_FORMAT; + + /* Coded Subformat Requests */ + typedef enum + { + IVAS_SUBFMT_FOA_PLANAR, + IVAS_SUBFMT_HOA2_PLANAR, + IVAS_SUBFMT_HOA3_PLANAR, + IVAS_SUBFMT_FOA, + IVAS_SUBFMT_HOA2, + IVAS_SUBFMT_HOA3, + IVAS_SUBFMT_MASA1, + IVAS_SUBFMT_MASA2, + IVAS_SUBFMT_ISM1, + IVAS_SUBFMT_ISM2, + IVAS_SUBFMT_ISM3, + IVAS_SUBFMT_ISM4, + IVAS_SUBFMT_ISM1_EXTENDED_METADATA, + IVAS_SUBFMT_ISM2_EXTENDED_METADATA, + IVAS_SUBFMT_ISM3_EXTENDED_METADATA, + IVAS_SUBFMT_ISM4_EXTENDED_METADATA, + IVAS_SUBFMT_MC_5_1, + IVAS_SUBFMT_MC_7_1, + IVAS_SUBFMT_MC_5_1_2, + IVAS_SUBFMT_MC_5_1_4, + IVAS_SUBFMT_MC_7_1_4, + IVAS_SUBFMT_RESERVED_21, + IVAS_SUBFMT_RESERVED_22, + IVAS_SUBFMT_RESERVED_23, + IVAS_SUBFMT_RESERVED_24, + IVAS_SUBFMT_RESERVED_25, + IVAS_SUBFMT_RESERVED_26, + IVAS_SUBFMT_RESERVED_27, + IVAS_SUBFMT_RESERVED_28, + IVAS_SUBFMT_RESERVED_29, + IVAS_SUBFMT_RESERVED_30, + IVAS_SUBFMT_RESERVED_31, + IVAS_SUBFMT_OMASA_ISM1_1TC, + IVAS_SUBFMT_OMASA_ISM2_1TC, + IVAS_SUBFMT_OMASA_ISM3_1TC, + IVAS_SUBFMT_OMASA_ISM4_1TC, + IVAS_SUBFMT_OMASA_ISM1_2TC, + IVAS_SUBFMT_OMASA_ISM2_2TC, + IVAS_SUBFMT_OMASA_ISM3_2TC, + IVAS_SUBFMT_OMASA_ISM4_2TC, + IVAS_SUBFMT_OSBA_ISM1_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_FOA, + IVAS_SUBFMT_OSBA_ISM2_FOA, + IVAS_SUBFMT_OSBA_ISM3_FOA, + IVAS_SUBFMT_OSBA_ISM4_FOA, + IVAS_SUBFMT_OSBA_ISM1_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_HOA2, + IVAS_SUBFMT_OSBA_ISM2_HOA2, + IVAS_SUBFMT_OSBA_ISM3_HOA2, + IVAS_SUBFMT_OSBA_ISM4_HOA2, + IVAS_SUBFMT_OSBA_ISM1_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_HOA3, + IVAS_SUBFMT_OSBA_ISM2_HOA3, + IVAS_SUBFMT_OSBA_ISM3_HOA3, + IVAS_SUBFMT_OSBA_ISM4_HOA3, + IVAS_SUBFMT_NO_REQ + } IVAS_RTP_SUBFORMAT; + + /* Split Rendering Requests */ + typedef struct { + uint32_t diegetic : 1; /* enabling diegetic support for Split Rendering */ + uint32_t yaw : 1; /* transmission metadata for correction around yaw axis */ + uint32_t pitch : 1; /* transmission metadata for correction around pitch axis */ + uint32_t roll: 1; /* transmission metadata for correction around roll axis */ + uint32_t reserved: 28; /* reserved */ + } IVAS_RTP_SPLITRENDER; + + /* Remote Requests Types (Keys) */ + typedef enum + { + IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODECS */ + IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ + IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ + IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ + IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ + IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ + IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ + IVAS_REQUEST_MAX /* Max number of requests */ + } IVAS_RTP_REQUEST_TYPE; + + /* Remote Request Values */ + typedef union { + uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ + IVAS_RTP_CODECS codec; /* codec id when request type is IVAS_REQUEST_CODEC */ + IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ + IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ + IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ + IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ + IVAS_RTP_SPLITRENDER srConfig; /* split rendering config when request type is IVAS_REQUEST_SR_CONFIG */ + } IVAS_RTP_REQUEST_VALUE; + + /* Template for pi data types, all defined pi data follow this template + * for example scene orientation pi data can be represented as :- + * + * typedef struct { + * uint32_t size; // sizeof(IVAS_PIDATA_SCENE_ORIENTATION) + * uint32_t piDataType; // IVAS_PI_SCENE_ORIENTATION + * float w, x, y, z; // pi data of scene orientation in quaternions + * } IVAS_PIDATA_SCENE_ORIENTATION; + * + */ + typedef struct + { + uint32_t size; /* size of this structure */ + uint32_t piDataType; /* IVAS PI data type */ + uint8_t data[1]; /* Variable length array (length calculated as size - 2*sizeof(uint32_t))*/ + } IVAS_PIDATA_GENERIC; + + /* Generic data buffer for sending/receiving coded frames / rtp packets + * data buffer is owned and initialized by caller, rtp api will ensure + * buffer write does not exceed allocSize. + */ + typedef struct + { + uint32_t allocSize; /* allocated size of the data buffer */ + uint32_t length; /* length of the initialized data in the buffer */ + uint8_t *buffer; /* pointer to the packet buffer */ + } IVAS_DATA_BUFFER; + + /**********************************************/ + /* IVAS RTP PACKER API */ + /**********************************************/ + + /* Forward declaration of rtp pack/unpack handle types */ + typedef struct IVAS_RTP_PACK *IVAS_RTP_PACK_HANDLE; /* rtp packer handle type */ + + /* Initial configuration for rtp packer + * - maxFramesPerPacket is used to define if more than one frame should be packed + * in the same rtp packet. + * + * - piDataRepeatIntervalMs is used to define pi data repetition interval + * + */ + typedef struct + { + uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ + uint32_t piDataRepeatIntervalMs; /* period for pi data repeat 0 = each frame, UINT_MAX=never */ + } IVAS_RTP_PACK_CONFIG; + + /* Open an instance of the RTP packer and return a handle to rtp packer on success + * error code is retured on failure and handle is set to NULL + */ + ivas_error IVAS_RTP_PACK_Open( + IVAS_RTP_PACK_HANDLE *phIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + const IVAS_RTP_PACK_CONFIG *config /* i : pointer to initial config for RTP Packer */ + ); + + /* Close and free an existing instance of rtp packer */ + void IVAS_RTP_PACK_Close( + IVAS_RTP_PACK_HANDLE *phIvasPack /* i/o : pointer to an IVAS rtp packer handle to be closed */ + ); + + /* Add requests for remote sender using a key value pair api + * each key must be provided with a corresponding value type + * + * Cross validation of some key,value pairs will not be done + * in this API. E.g. Codec ID and supported bitrates/bandwidths + * will not be performed at this level. + */ + ivas_error IVAS_RTP_PACK_RemoteRequest( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE reqType, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE reqValue /* i : value of the requested type */ + ); + + /* Push a single IVAS/EVS frame to rtp packer + * + * If multiple frames per RTP packet are desired, multiple frames must be explicitly + * pushed before a call to IVAS_RTP_PACK_GetPacket to generate a rtp packet. + * + * It is possible to have variable frames per packet until maxFramesPerPacket frames + * if IVAS_RTP_PACK_GetPacket is invoked asyncronously w.r.t this api. + * + */ + ivas_error IVAS_RTP_PACK_PushFrame( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_CODECS codecId, /* i : Codec type (IVAS/EVS) */ + const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ + ); + + /* Push single PI data to rtp packer + * + * Provide PI data for a current RTP packet. All PI data is locally cached in the packer + * and set to the rtp packet with policy defined in initial configuration during call to + * IVAS_RTP_PACK_GetPacket. + * + */ + ivas_error IVAS_RTP_PACK_PushPiData( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ + ); + + /* Generate a rtp packet using available pushed frames + * + * Available remote requests, pi data and frames will be packed into a rtp packet. If no + * frame is pushed before call to this api, NO_DATA_FRAME will be generated + * + */ + ivas_error IVAS_RTP_PACK_GetPacket( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *packet /* o : encapsulated rtp packet */ + ); + + + /**********************************************/ + /* IVAS RTP UNPACKER API */ + /**********************************************/ + + /* Forward declaration of rtp unpack handle types */ + typedef struct IVAS_RTP_UNPACK *IVAS_RTP_UNPACK_HANDLE; /* rtp unpacker handle type */ + + /* Initial configuration for rtp unpacker */ + typedef struct + { + uint32_t maxNumberOfPacketInFifo; /* input packet fifo 's max length */ + } IVAS_RTP_UNPACK_CONFIG; + + /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success + * error code is retured on failure and handle is set to NULL + */ + ivas_error IVAS_RTP_UNPACK_Open( + IVAS_RTP_UNPACK_HANDLE *phIvasUnpack, /* i/o : rtp unpacker handle */ + const IVAS_RTP_UNPACK_CONFIG *config /* i : initial configuration for rtp unpacker */ + ); + + /* Close and free an existing instance of rtp unpacker */ + void IVAS_RTP_UNPACK_Close( + IVAS_RTP_UNPACK_HANDLE *phIvasUnpack /* i/o : IVAS rtp unpacker handle */ + ); + + /* Push a received rtp Ivas Payload to unpacker to extract number of frames, pi data and + * any remote request present in the packet. Caller must extract RTP header and header + * extension and feed Ivas Payload alongwith RTP Timestamp and sequence number. + * + * Example usage : - + * ================== + * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, payload, rtpTs, seqNum, + * &nFrames, &nPiData, &reqBitmap); + * if (err != IVAS_ERR_OK) { return err; } + * + * // Read the frames in packet and feed to decoder + * while (nFrames-- > 0) { + * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &frame, &frameTs, &seqNum); + * if (err != IVAS_ERR_OK) { return err; } + * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); + * if (err != IVAS_ERR_OK) { return err; } + * } + * + * // Read PI Data + * while (nPiData-- > 0) { + * err = IVAS_RTP_UNPACK_GetNextPiData(hIvasUnpack, &piData, &piTs); + * if (err != IVAS_ERR_OK) { return err; } + * // handle pi data based on fwd/rev pi data types + * handlePIData(&piData, piTs) + * } + * + * // Read remote requests + * for (req = 0; req < IVAS_REQUEST_MAX; req++) { + * if (reqBitmap & (1u << req)) { + * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); + * if (err != IVAS_ERR_OK) { return err; } + * switch(req) { + * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; + * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; + * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; + * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; + * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; + * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; + * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; + * } + * } + * } + * + */ + ivas_error IVAS_RTP_UNPACK_PushPacket( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *packet, /* i : received rtp packet */ + uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ + uint16_t sequenceNumbers, /* i : sequence number from rtp header */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ + ); + + /* Push a received rtp extension header to unpacker to extract pi data + * Caller must feed rtp extension header alongwith RTP Timestamp and sequence number. + */ + ivas_error IVAS_RTP_UNPACK_HeaderExtension ( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *hdrExtnBuffer, /* i : received rtp header extension buffer */ + uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ + uint16_t sequenceNumbers, /* i : sequence number from rtp header */ + uint32_t *numPiDataInPacket /* o : number of PI data received in rtp packet */ + ); + + /* Fetch requests from sender using a key value pair api + * each key must be provided with a corresponding value storage type + * + * On call to IVAS_RTP_UNPACK_PushPacket(), remoteRequestBitmap can be used + * an indicator of new request available this frame + * + */ + ivas_error IVAS_RTP_UNPACK_GetRequest( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE type, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE *value /* o : pointer of the requested type */ + ); + + /* Extract a single IVAS/EVS frame from provided rtp packet alongwith rtp timestamp + * and sequence number + * + * If multiple frames per RTP packet are available, multiple calls to IVAS_RTP_UNPACK_PullFrame + * are needed. A frameBuffer.length == 0 indicates NO_DATA_FRAME + * + */ + ivas_error IVAS_RTP_UNPACK_PullFrame( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_RTP_CODECS *receivedCodecId, /* i : Codec type (IVAS/EVS) */ + IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ + uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ + uint16_t *sequenceNumbers /* o : sequence number from rtp header */ + ); + + /* Pull a single PI data from rtp unpacker instance for current packet + * Each Pi data is accompanied with a corresponding timestamp + */ + ivas_error IVAS_RTP_UNPACK_GetNextPiData( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ + uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ + ); + +#ifdef __cplusplus +} +#endif + +#endif /* IVAS_RTP_API_H */ -- GitLab From d04cb56426f420cfc5a6c9cdc1c74d436be5c111 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 8 Jul 2025 13:53:27 +0200 Subject: [PATCH 062/340] Minor changes to CLDFB_RINGBUF --- lib_dec/cldfb_ring_buffer.c | 31 +++++++++---------------------- lib_dec/cldfb_ring_buffer.h | 20 +++++++++----------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index fab273bd63..c6be06b1ea 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -161,10 +161,9 @@ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16 return; } -static uint32_t rb_num_floats( CLDFB_RINGBUF_HANDLE h ) +/* Returns total number of buffered samples (including number of channels) */ +static uint32_t total_size( CLDFB_RINGBUF_HANDLE h ) { - uint32_t ret; - if ( CLDFB_RINGBUF_IsFull( h ) ) { return h->capacity; @@ -172,29 +171,19 @@ static uint32_t rb_num_floats( CLDFB_RINGBUF_HANDLE h ) if ( h->read_pos <= h->write_pos ) { - ret = h->write_pos - h->read_pos; - } - else - { - /* wrap around */ - ret = h->write_pos + h->capacity - h->read_pos; + return h->write_pos - h->read_pos; } - - return ret; + /* else wrap around */ + return h->write_pos + h->capacity - h->read_pos; } -int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t col_idx ) +void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; - int32_t num_floats = (int32_t) rb_num_floats( h ); + int32_t num_floats = (int32_t) total_size( h ); uint32_t offset; - if ( idx < -num_floats || num_floats <= idx ) - { - *p_real = NULL; - *p_imag = NULL; - return -1; - } + assert( -num_floats <= idx && idx <= num_floats ); if ( idx >= 0 ) { @@ -207,8 +196,6 @@ int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float ** *p_real = &h->real[offset]; *p_imag = &h->imag[offset]; - - return 0; } int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ) @@ -223,7 +210,7 @@ int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ) uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ) { - return (uint16_t) ( rb_num_floats( h ) / CLDFB_NO_CHANNELS_MAX ); + return (uint16_t) ( total_size( h ) / CLDFB_NO_CHANNELS_MAX ); } #endif diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h index 3403bc1add..68143bdfa0 100644 --- a/lib_dec/cldfb_ring_buffer.h +++ b/lib_dec/cldfb_ring_buffer.h @@ -41,7 +41,7 @@ typedef struct CldfbRingBuf *CLDFB_RINGBUF_HANDLE; /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Open( ) + * CLDFB_RINGBUF_Open() * * Allocate a ring buffer for CLDFB data with the given capacity of CLDFB columns. * Each column is expected to contain at most CLDFB_NO_CHANNELS_MAX frequency bands. @@ -51,28 +51,28 @@ typedef struct CldfbRingBuf *CLDFB_RINGBUF_HANDLE; ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Close( ) + * CLDFB_RINGBUF_Close() * * Dellocate CLDFB ring buffer. The given handle will be set to NULL. *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Push( ) + * CLDFB_RINGBUF_Push() * * Push a single column onto the back of the CLDFB ring buffer from real and imag arrays. *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Pop( ) + * CLDFB_RINGBUF_Pop() * * Pop a single column from the front of the CLDFB ring buffer into real and imag arrays. *---------------------------------------------------------------------*/ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_GetByIdx( ) + * CLDFB_RINGBUF_GetByIdx() * * Get pointers into a specific column in the CLDFB ring buffer based on given index. * Non-negative indices access from the front of the ring buffer, negative indexes access @@ -80,27 +80,25 @@ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16 * * - index 0 accesses the front of the buffer, i.e. the oldest CLDFB column in the queue. * - index -1 accesses the back of the buffer, i.e. the newest (last pushed) CLDFB column in the queue. - * - * Returns -1 if given an invalid index (p_real and p_imag will be set to NULL in that case), or 0 otherwise. *---------------------------------------------------------------------*/ -int16_t CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t idx ); +void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t idx ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsEmpty( ) + * CLDFB_RINGBUF_IsEmpty() * * Returns 1 if the ring buffer is empty, or 0 otherwise. *---------------------------------------------------------------------*/ int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsFull( ) + * CLDFB_RINGBUF_IsFull() * * Returns 1 if the ring buffer is full, or 0 otherwise. *---------------------------------------------------------------------*/ int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ); /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Size( ) + * CLDFB_RINGBUF_Size() * * Returns the number of CLDFB columns currently stored in the ring buffer. *---------------------------------------------------------------------*/ -- GitLab From 1d0f51e9373271ef5ea3cc6feafe359ed39d8f8d Mon Sep 17 00:00:00 2001 From: "Singh, Ripinder" Date: Thu, 10 Jul 2025 14:45:38 +1000 Subject: [PATCH 063/340] Code review comments #1 Signed-off-by: Singh, Ripinder --- lib_com/options.h | 1 + lib_util/ivas_rtp_api.h | 104 +++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 54 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 941685105c..1f6e493889 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -155,6 +155,7 @@ /* ################## Start DEVELOPMENT switches ######################### */ +//#define RTP_S4_251135_CR26253_0016_REV1 /* RTP Pack/Unpack API corresponding to CR 26253 */ #define IVAS_RTPDUMP /* RTPDUMP writing and reading for IVAS payloads */ /* ################### Start BE switches ################################# */ diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 7581b183a2..5b008a04ca 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -34,8 +34,10 @@ #define IVAS_RTP_API_H #pragma once +#include #include #include +#include "options.h" #include "ivas_error.h" #ifdef __cplusplus @@ -80,7 +82,7 @@ extern "C" { IVAS_RTP_EVS, /* EVS */ IVAS_RTP_IVAS /* IVAS */ - } IVAS_RTP_CODECS; + } IVAS_RTP_CODEC; /* IVAS Bandwidth Requests */ typedef enum @@ -119,6 +121,7 @@ extern "C" IVAS_FMT_NO_REQ, /* No preference */ } IVAS_RTP_FORMAT; +#ifdef RTP_S4_251135_CR26253_0016_REV1 /* Coded Subformat Requests */ typedef enum { @@ -197,36 +200,41 @@ extern "C" uint32_t roll: 1; /* transmission metadata for correction around roll axis */ uint32_t reserved: 28; /* reserved */ } IVAS_RTP_SPLITRENDER; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ /* Remote Requests Types (Keys) */ typedef enum { - IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODECS */ + IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODEC */ IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ - IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ IVAS_REQUEST_MAX /* Max number of requests */ } IVAS_RTP_REQUEST_TYPE; /* Remote Request Values */ typedef union { uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ - IVAS_RTP_CODECS codec; /* codec id when request type is IVAS_REQUEST_CODEC */ + IVAS_RTP_CODEC codec; /* codec id when request type is IVAS_REQUEST_CODEC */ IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ - IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ IVAS_RTP_SPLITRENDER srConfig; /* split rendering config when request type is IVAS_REQUEST_SR_CONFIG */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } IVAS_RTP_REQUEST_VALUE; /* Template for pi data types, all defined pi data follow this template * for example scene orientation pi data can be represented as :- * * typedef struct { - * uint32_t size; // sizeof(IVAS_PIDATA_SCENE_ORIENTATION) + * size_t size; // sizeof(IVAS_PIDATA_SCENE_ORIENTATION) * uint32_t piDataType; // IVAS_PI_SCENE_ORIENTATION * float w, x, y, z; // pi data of scene orientation in quaternions * } IVAS_PIDATA_SCENE_ORIENTATION; @@ -234,20 +242,20 @@ extern "C" */ typedef struct { - uint32_t size; /* size of this structure */ + size_t size; /* size of this structure */ uint32_t piDataType; /* IVAS PI data type */ - uint8_t data[1]; /* Variable length array (length calculated as size - 2*sizeof(uint32_t))*/ + uint8_t data[1]; /* Variable length array */ } IVAS_PIDATA_GENERIC; - /* Generic data buffer for sending/receiving coded frames / rtp packets + /* Generic data buffer for sending/receiving coded frames / rtp payloads * data buffer is owned and initialized by caller, rtp api will ensure - * buffer write does not exceed allocSize. + * buffer write does not exceed capacity. */ typedef struct { - uint32_t allocSize; /* allocated size of the data buffer */ - uint32_t length; /* length of the initialized data in the buffer */ - uint8_t *buffer; /* pointer to the packet buffer */ + size_t capacity; /* allocated size of the data buffer */ + size_t length; /* length of the initialized data in the buffer */ + uint8_t *buffer; /* pointer to the payload buffer */ } IVAS_DATA_BUFFER; /**********************************************/ @@ -260,14 +268,10 @@ extern "C" /* Initial configuration for rtp packer * - maxFramesPerPacket is used to define if more than one frame should be packed * in the same rtp packet. - * - * - piDataRepeatIntervalMs is used to define pi data repetition interval - * */ typedef struct { uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ - uint32_t piDataRepeatIntervalMs; /* period for pi data repeat 0 = each frame, UINT_MAX=never */ } IVAS_RTP_PACK_CONFIG; /* Open an instance of the RTP packer and return a handle to rtp packer on success @@ -290,7 +294,7 @@ extern "C" * in this API. E.g. Codec ID and supported bitrates/bandwidths * will not be performed at this level. */ - ivas_error IVAS_RTP_PACK_RemoteRequest( + ivas_error IVAS_RTP_PACK_PushRemoteRequest( IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ IVAS_RTP_REQUEST_TYPE reqType, /* i : remote request type */ IVAS_RTP_REQUEST_VALUE reqValue /* i : value of the requested type */ @@ -299,23 +303,23 @@ extern "C" /* Push a single IVAS/EVS frame to rtp packer * * If multiple frames per RTP packet are desired, multiple frames must be explicitly - * pushed before a call to IVAS_RTP_PACK_GetPacket to generate a rtp packet. + * pushed before a call to IVAS_RTP_PACK_GetPayload to generate a rtp payload. * * It is possible to have variable frames per packet until maxFramesPerPacket frames - * if IVAS_RTP_PACK_GetPacket is invoked asyncronously w.r.t this api. + * if IVAS_RTP_PACK_GetPayload is invoked asyncronously w.r.t this api. * */ ivas_error IVAS_RTP_PACK_PushFrame( IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_RTP_CODECS codecId, /* i : Codec type (IVAS/EVS) */ + IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ ); /* Push single PI data to rtp packer * * Provide PI data for a current RTP packet. All PI data is locally cached in the packer - * and set to the rtp packet with policy defined in initial configuration during call to - * IVAS_RTP_PACK_GetPacket. + * and set to the rtp payload with policy defined in initial configuration during call to + * IVAS_RTP_PACK_GetPayload. * */ ivas_error IVAS_RTP_PACK_PushPiData( @@ -323,15 +327,15 @@ extern "C" const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ ); - /* Generate a rtp packet using available pushed frames + /* Generate a rtp payload using available pushed frames * - * Available remote requests, pi data and frames will be packed into a rtp packet. If no + * Available remote requests, pi data and frames will be packed into a rtp payload. If no * frame is pushed before call to this api, NO_DATA_FRAME will be generated * */ - ivas_error IVAS_RTP_PACK_GetPacket( + ivas_error IVAS_RTP_PACK_GetPayload( IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_DATA_BUFFER *packet /* o : encapsulated rtp packet */ + IVAS_DATA_BUFFER *payload /* o : encapsulated rtp payload */ ); @@ -345,7 +349,7 @@ extern "C" /* Initial configuration for rtp unpacker */ typedef struct { - uint32_t maxNumberOfPacketInFifo; /* input packet fifo 's max length */ + uint32_t maxNumberOfPayloadsInFifo; /* input payload fifo 's max length */ } IVAS_RTP_UNPACK_CONFIG; /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success @@ -362,16 +366,20 @@ extern "C" ); /* Push a received rtp Ivas Payload to unpacker to extract number of frames, pi data and - * any remote request present in the packet. Caller must extract RTP header and header + * any remote request present in the payload. Caller must extract RTP header and header * extension and feed Ivas Payload alongwith RTP Timestamp and sequence number. * + * In case of DTX transmission modes, the number of frames in packet will be reduced by + * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall + * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. + * * Example usage : - * ================== - * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, payload, rtpTs, seqNum, - * &nFrames, &nPiData, &reqBitmap); + * err = IVAS_RTP_UNPACK_PushPayload(hIvasUnpack, payload, rtpTs, seqNum, + * &nFrames, &nPiData, &reqBitmap); * if (err != IVAS_ERR_OK) { return err; } * - * // Read the frames in packet and feed to decoder + * // Read the frames in payload and feed to decoder * while (nFrames-- > 0) { * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &frame, &frameTs, &seqNum); * if (err != IVAS_ERR_OK) { return err; } @@ -381,7 +389,7 @@ extern "C" * * // Read PI Data * while (nPiData-- > 0) { - * err = IVAS_RTP_UNPACK_GetNextPiData(hIvasUnpack, &piData, &piTs); + * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); * if (err != IVAS_ERR_OK) { return err; } * // handle pi data based on fwd/rev pi data types * handlePIData(&piData, piTs) @@ -405,31 +413,20 @@ extern "C" * } * */ - ivas_error IVAS_RTP_UNPACK_PushPacket( + ivas_error IVAS_RTP_UNPACK_PushPayload( IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - const IVAS_DATA_BUFFER *packet, /* i : received rtp packet */ + const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ - uint16_t sequenceNumbers, /* i : sequence number from rtp header */ + uint16_t sequenceNumber, /* i : sequence number from rtp header */ uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ ); - /* Push a received rtp extension header to unpacker to extract pi data - * Caller must feed rtp extension header alongwith RTP Timestamp and sequence number. - */ - ivas_error IVAS_RTP_UNPACK_HeaderExtension ( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - const IVAS_DATA_BUFFER *hdrExtnBuffer, /* i : received rtp header extension buffer */ - uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ - uint16_t sequenceNumbers, /* i : sequence number from rtp header */ - uint32_t *numPiDataInPacket /* o : number of PI data received in rtp packet */ - ); - /* Fetch requests from sender using a key value pair api * each key must be provided with a corresponding value storage type * - * On call to IVAS_RTP_UNPACK_PushPacket(), remoteRequestBitmap can be used + * On call to IVAS_RTP_UNPACK_PushPayload(), remoteRequestBitmap can be used * an indicator of new request available this frame * */ @@ -439,25 +436,24 @@ extern "C" IVAS_RTP_REQUEST_VALUE *value /* o : pointer of the requested type */ ); - /* Extract a single IVAS/EVS frame from provided rtp packet alongwith rtp timestamp + /* Extract a single IVAS/EVS frame from provided rtp payload alongwith rtp timestamp * and sequence number * * If multiple frames per RTP packet are available, multiple calls to IVAS_RTP_UNPACK_PullFrame - * are needed. A frameBuffer.length == 0 indicates NO_DATA_FRAME - * + * are needed. */ ivas_error IVAS_RTP_UNPACK_PullFrame( IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - IVAS_RTP_CODECS *receivedCodecId, /* i : Codec type (IVAS/EVS) */ + IVAS_RTP_CODEC *receivedCodecId, /* i : Codec type (IVAS/EVS) */ IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ - uint16_t *sequenceNumbers /* o : sequence number from rtp header */ + uint16_t *sequenceNumber /* o : sequence number from rtp header */ ); /* Pull a single PI data from rtp unpacker instance for current packet * Each Pi data is accompanied with a corresponding timestamp */ - ivas_error IVAS_RTP_UNPACK_GetNextPiData( + ivas_error IVAS_RTP_UNPACK_PullNextPiData( IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ -- GitLab From ba61e9a73be35370a04f2c961837ecad827c57ed Mon Sep 17 00:00:00 2001 From: vaclav Date: Thu, 17 Jul 2025 14:53:23 +0200 Subject: [PATCH 064/340] framework-related and formal improvements --- Workspace_msvc/lib_dec.vcxproj | 5 +- Workspace_msvc/lib_dec.vcxproj.filters | 11 +- Workspace_msvc/lib_isar.vcxproj | 2 +- apps/decoder.c | 45 ++--- lib_com/ivas_prot.h | 37 ++++ lib_dec/cldfb_ring_buffer.h | 108 ----------- ...ring_buffer.c => ivas_cldfb_ring_buffer.c} | 155 ++++++++++----- lib_dec/ivas_dirac_dec.c | 2 +- lib_dec/ivas_mc_param_dec.c | 2 +- lib_dec/ivas_mc_paramupmix_dec.c | 2 +- lib_dec/ivas_omasa_dec.c | 2 +- lib_dec/ivas_osba_dec.c | 2 +- lib_dec/ivas_stat_dec.h | 7 +- lib_dec/lib_dec.c | 182 +++++++++--------- lib_dec/lib_dec.h | 11 +- lib_isar/isar_prot.h | 6 +- lib_isar/isar_splitRend_lcld_enc.c | 8 +- lib_isar/isar_stat.h | 13 ++ lib_rend/ivas_dirac_dec_binaural_functions.c | 4 +- lib_rend/lib_rend.c | 5 +- 20 files changed, 300 insertions(+), 309 deletions(-) delete mode 100644 lib_dec/cldfb_ring_buffer.h rename lib_dec/{cldfb_ring_buffer.c => ivas_cldfb_ring_buffer.c} (56%) diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index 944e264667..a8639d192c 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -147,7 +147,6 @@ - @@ -207,6 +206,7 @@ + @@ -313,7 +313,6 @@ - @@ -350,4 +349,4 @@ - + \ No newline at end of file diff --git a/Workspace_msvc/lib_dec.vcxproj.filters b/Workspace_msvc/lib_dec.vcxproj.filters index 5197d66457..e081cb0870 100644 --- a/Workspace_msvc/lib_dec.vcxproj.filters +++ b/Workspace_msvc/lib_dec.vcxproj.filters @@ -277,9 +277,6 @@ decoder_all_c - - decoder_all_c - decoder_all_c @@ -518,11 +515,11 @@ decoder_all_c + + decoder_ivas_c + - - decoder_h - decoder_h @@ -575,4 +572,4 @@ {c33b80b3-67ce-466b-91c0-4adfc9efcb5c} - + \ No newline at end of file diff --git a/Workspace_msvc/lib_isar.vcxproj b/Workspace_msvc/lib_isar.vcxproj index fceeb731ce..5bee827041 100644 --- a/Workspace_msvc/lib_isar.vcxproj +++ b/Workspace_msvc/lib_isar.vcxproj @@ -197,4 +197,4 @@ - + \ No newline at end of file diff --git a/apps/decoder.c b/apps/decoder.c index abf7468d2b..4fde8aa1c5 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -171,12 +171,11 @@ typedef struct static bool parseCmdlIVAS_dec( int16_t argc, char **argv, DecArguments *arg ); static void usage_dec( void ); static ivas_error decodeG192( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); -static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); +#else +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); #endif - IVAS_DEC_HANDLE hIvasDec, - int16_t *pcmBuf ); static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary, IVAS_DEC_HANDLE hIvasDec, const IVAS_AUDIO_CONFIG OutputConfig, const int32_t output_Fs ); #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); @@ -757,11 +756,11 @@ int main( if ( arg.voipMode ) { - error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - &splitRendBits, + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, &splitRendBits, hIvasDec, pcmBuf ); +#else + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, hIvasDec, pcmBuf ); #endif - hIvasDec, pcmBuf ); } else { @@ -1906,7 +1905,6 @@ static ivas_error initOnFirstGoodFrame( else #endif { - if ( *pRemainingDelayNumSamples < *numOutSamples ) { if ( ( error = AudioFileWriter_write( *ppAfWriter, zeroBuf, *numOutSamples * *pNumOutChannels - ( *pRemainingDelayNumSamples * *pNumOutChannels ) ) ) != IVAS_ERR_OK ) @@ -2115,7 +2113,7 @@ static ivas_error decodeG192( #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( !isSplitRend ) { - /* Ensure split rendering output struct is not used when not outputting to a split rendering format */ + /* Ensure split rendering output struct is not used when not outputting to a split rendering output configuration */ splitRendBits = NULL; } #endif @@ -2455,11 +2453,11 @@ static ivas_error decodeG192( } /* decode transport channels, do TSM and feed to renderer */ - if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, -#ifndef FIX_1119_SPLIT_RENDERING_VOIP - isSplitRend, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, splitRendBits ) ) != IVAS_ERR_OK ) +#else + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, isSplitRend, splitRendBits ) ) != IVAS_ERR_OK ) #endif - splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -3380,15 +3378,11 @@ static ivas_error decodeVoIP( #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( isSplitRend ) { - if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, #ifdef SUPPORT_JBM_TRACEFILE - writeJbmTraceFileFrameWrapper, - jbmTraceWriter, + if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, nOutSamples, (void *) pcmBuf, splitRendBits, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) +#else + if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #endif - &bitstreamReadDone, - &nSamplesRendered, - ¶metersAvailableForEditing, - systemTime_ms ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSplitBinauralBitstream: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; @@ -3398,7 +3392,7 @@ static ivas_error decodeVoIP( { #endif #ifdef SUPPORT_JBM_TRACEFILE - if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, &bitstreamReadDone, &nSamplesRendered, ¶meterAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #endif @@ -3490,15 +3484,13 @@ static ivas_error decodeVoIP( { #ifndef FIX_1119_SPLIT_RENDERING_VOIP SplitFileReadWrite *splitRendWriter = NULL; -#endif - if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, +#endif #ifdef FIX_1119_SPLIT_RENDERING_VOIP - &vec_pos_len, + if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, &vec_pos_len, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, #else - NULL, + if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, NULL, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, #endif - delayNumSamples_orig, &delayNumSamples, &delayTimeScale, &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &splitRendWriter ) ) != IVAS_ERR_OK ) { fprintf( stderr, "Error in initOnFirstGoodFrame(): %s\n", IVAS_DEC_GetErrorMessage( error ) ); @@ -3523,6 +3515,7 @@ static ivas_error decodeVoIP( goto cleanup; } } + if ( !isSplitCoded ) { #endif diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index 8121030b1c..283d96ff4b 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -5195,6 +5195,43 @@ void ivas_binaural_add_LFE( float *output_f[] /* o : synthesized core-coder transport channels/DirAC output */ ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + +/*---------------------------------------------------------------------------------* + * Multi-pose ring buffer Prototypes +*-----------------------------------------------------------------------------------*/ + +ivas_error ivas_CLDFB_RINGBUF_Open( + ISAR_CLDFB_RINGBUF_HANDLE *ph, + const int16_t capacity_columns +); + +void ivas_CLDFB_RINGBUF_Close( + ISAR_CLDFB_RINGBUF_HANDLE *ph +); + +void ivas_CLDFB_RINGBUF_Push( + ISAR_CLDFB_RINGBUF_HANDLE h, + const float *real, + const float *imag, + const int16_t num_bands +); + +void ivas_CLDFB_RINGBUF_Pop( + ISAR_CLDFB_RINGBUF_HANDLE h, + float *real, + float *imag, + const int16_t num_bands +); + +void ivas_CLDFB_RINGBUF_GetByIdx( + ISAR_CLDFB_RINGBUF_HANDLE h, + float **p_real, + float **p_imag, + const int16_t idx +); + +#endif /*----------------------------------------------------------------------------------* * renderer prototypes diff --git a/lib_dec/cldfb_ring_buffer.h b/lib_dec/cldfb_ring_buffer.h deleted file mode 100644 index 68143bdfa0..0000000000 --- a/lib_dec/cldfb_ring_buffer.h +++ /dev/null @@ -1,108 +0,0 @@ -/****************************************************************************************************** - - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. - -*******************************************************************************************************/ - -#ifndef CLDFB_RING_BUFFER_H -#define CLDFB_RING_BUFFER_H - -#include "ivas_error.h" -#include - -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - -typedef struct CldfbRingBuf *CLDFB_RINGBUF_HANDLE; - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Open() - * - * Allocate a ring buffer for CLDFB data with the given capacity of CLDFB columns. - * Each column is expected to contain at most CLDFB_NO_CHANNELS_MAX frequency bands. - * - * May return IVAS_ERR_FAILED_ALLOC on failed allocation, or IVAS_ERR_OK otherwise. - *---------------------------------------------------------------------*/ -ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Close() - * - * Dellocate CLDFB ring buffer. The given handle will be set to NULL. - *---------------------------------------------------------------------*/ -void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Push() - * - * Push a single column onto the back of the CLDFB ring buffer from real and imag arrays. - *---------------------------------------------------------------------*/ -void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Pop() - * - * Pop a single column from the front of the CLDFB ring buffer into real and imag arrays. - *---------------------------------------------------------------------*/ -void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_GetByIdx() - * - * Get pointers into a specific column in the CLDFB ring buffer based on given index. - * Non-negative indices access from the front of the ring buffer, negative indexes access - * from the back, similar to Python arrays. For example: - * - * - index 0 accesses the front of the buffer, i.e. the oldest CLDFB column in the queue. - * - index -1 accesses the back of the buffer, i.e. the newest (last pushed) CLDFB column in the queue. - *---------------------------------------------------------------------*/ -void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t idx ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsEmpty() - * - * Returns 1 if the ring buffer is empty, or 0 otherwise. - *---------------------------------------------------------------------*/ -int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsFull() - * - * Returns 1 if the ring buffer is full, or 0 otherwise. - *---------------------------------------------------------------------*/ -int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ); - -/*---------------------------------------------------------------------* - * CLDFB_RINGBUF_Size() - * - * Returns the number of CLDFB columns currently stored in the ring buffer. - *---------------------------------------------------------------------*/ -uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ); - -#endif /* FIX_1119_SPLIT_RENDERING_VOIP */ -#endif diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/ivas_cldfb_ring_buffer.c similarity index 56% rename from lib_dec/cldfb_ring_buffer.c rename to lib_dec/ivas_cldfb_ring_buffer.c index c6be06b1ea..01ce9cd7c0 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/ivas_cldfb_ring_buffer.c @@ -30,33 +30,63 @@ *******************************************************************************************************/ -#include "cldfb_ring_buffer.h" +#include +#include "options.h" #include "cnst.h" #include "prot.h" -#include "ivas_error_utils.h" +#include "ivas_prot.h" #include -#include -#include +#ifdef DEBUGGING +#include "debug.h" +#endif +#include "wmc_auto.h" #ifdef FIX_1119_SPLIT_RENDERING_VOIP -struct CldfbRingBuf +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_IsEmpty() + * + * Returns 1 if the ring buffer is empty, or 0 otherwise. + *---------------------------------------------------------------------*/ + +static int16_t ivas_CLDFB_RINGBUF_IsEmpty( + ISAR_CLDFB_RINGBUF_HANDLE h ) +{ + return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); +} + +/*---------------------------------------------------------------------* + * CLDFB_RINGBUF_IsFull() + * + * Returns 1 if the ring buffer is full, or 0 otherwise. + *---------------------------------------------------------------------*/ + +static int16_t ivas_CLDFB_RINGBUF_IsFull( + ISAR_CLDFB_RINGBUF_HANDLE h ) { - float *real; - float *imag; - uint32_t capacity; - uint32_t write_pos; - uint32_t read_pos; - int16_t is_full; -}; - -ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_columns ) + return h->is_full; +} + + +/*---------------------------------------------------------------------* + * ivas_CLDFB_RINGBUF_Open() + * + * Allocate a ring buffer for CLDFB data with the given capacity of CLDFB columns. + * Each column is expected to contain at most CLDFB_NO_CHANNELS_MAX frequency bands. + * + * May return IVAS_ERR_FAILED_ALLOC on failed allocation, or IVAS_ERR_OK otherwise. + *---------------------------------------------------------------------*/ + +ivas_error ivas_CLDFB_RINGBUF_Open( + ISAR_CLDFB_RINGBUF_HANDLE *ph, + const int16_t capacity_columns ) { - CLDFB_RINGBUF_HANDLE h; + ISAR_CLDFB_RINGBUF_HANDLE h; int32_t capacity; + capacity = capacity_columns * CLDFB_NO_CHANNELS_MAX; - if ( ( h = malloc( sizeof( struct CldfbRingBuf ) ) ) == NULL ) + if ( ( h = malloc( sizeof( ISAR_CLDFB_RINGBUF_DATA ) ) ) == NULL ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); } @@ -81,9 +111,17 @@ ivas_error CLDFB_RINGBUF_Open( CLDFB_RINGBUF_HANDLE *ph, int16_t capacity_column return IVAS_ERR_OK; } -void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ) + +/*---------------------------------------------------------------------* + * ivas_CLDFB_RINGBUF_Close() + * + * Dellocate CLDFB ring buffer. The given handle will be set to NULL. + *---------------------------------------------------------------------*/ + +void ivas_CLDFB_RINGBUF_Close( + ISAR_CLDFB_RINGBUF_HANDLE *ph ) { - CLDFB_RINGBUF_HANDLE h; + ISAR_CLDFB_RINGBUF_HANDLE h; if ( ph == NULL ) { @@ -111,13 +149,24 @@ void CLDFB_RINGBUF_Close( CLDFB_RINGBUF_HANDLE *ph ) return; } -void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float *imag, uint16_t num_bands ) + +/*---------------------------------------------------------------------* + * ivas_CLDFB_RINGBUF_Push() + * + * Push a single column onto the back of the CLDFB ring buffer from real and imag arrays. + *---------------------------------------------------------------------*/ + +void ivas_CLDFB_RINGBUF_Push( + ISAR_CLDFB_RINGBUF_HANDLE h, + const float *real, + const float *imag, + const int16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !CLDFB_RINGBUF_IsFull( h ) ); + assert( !ivas_CLDFB_RINGBUF_IsFull( h ) ); - mvr2r( real, &h->real[h->write_pos], (int16_t) num_bands ); - mvr2r( imag, &h->imag[h->write_pos], (int16_t) num_bands ); + mvr2r( real, &h->real[h->write_pos], num_bands ); + mvr2r( imag, &h->imag[h->write_pos], num_bands ); h->write_pos += CLDFB_NO_CHANNELS_MAX; if ( h->write_pos == h->capacity ) @@ -133,18 +182,29 @@ void CLDFB_RINGBUF_Push( CLDFB_RINGBUF_HANDLE h, const float *real, const float return; } -void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16_t num_bands ) + +/*---------------------------------------------------------------------* + * ivas_CLDFB_RINGBUF_Pop() + * + * Pop a single column from the front of the CLDFB ring buffer into real and imag arrays. + *---------------------------------------------------------------------*/ + +void ivas_CLDFB_RINGBUF_Pop( + ISAR_CLDFB_RINGBUF_HANDLE h, + float *real, + float *imag, + const int16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !CLDFB_RINGBUF_IsEmpty( h ) ); + assert( !ivas_CLDFB_RINGBUF_IsEmpty( h ) ); if ( real != NULL ) { - mvr2r( &h->real[h->read_pos], real, (int16_t) num_bands ); + mvr2r( &h->real[h->read_pos], real, num_bands ); } if ( imag != NULL ) { - mvr2r( &h->imag[h->read_pos], imag, (int16_t) num_bands ); + mvr2r( &h->imag[h->read_pos], imag, num_bands ); } h->read_pos += CLDFB_NO_CHANNELS_MAX; @@ -161,10 +221,12 @@ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16 return; } + /* Returns total number of buffered samples (including number of channels) */ -static uint32_t total_size( CLDFB_RINGBUF_HANDLE h ) +static uint32_t CLDFB_RINGBUF_total_size( + ISAR_CLDFB_RINGBUF_HANDLE h ) { - if ( CLDFB_RINGBUF_IsFull( h ) ) + if ( ivas_CLDFB_RINGBUF_IsFull( h ) ) { return h->capacity; } @@ -173,14 +235,31 @@ static uint32_t total_size( CLDFB_RINGBUF_HANDLE h ) { return h->write_pos - h->read_pos; } + /* else wrap around */ return h->write_pos + h->capacity - h->read_pos; } -void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_imag, int16_t col_idx ) + +/*---------------------------------------------------------------------* + * ivas_CLDFB_RINGBUF_GetByIdx() + * + * Get pointers into a specific column in the CLDFB ring buffer based on given index. + * Non-negative indices access from the front of the ring buffer, negative indexes access + * from the back, similar to Python arrays. For example: + * + * - index 0 accesses the front of the buffer, i.e. the oldest CLDFB column in the queue. + * - index -1 accesses the back of the buffer, i.e. the newest (last pushed) CLDFB column in the queue. + *---------------------------------------------------------------------*/ + +void ivas_CLDFB_RINGBUF_GetByIdx( + ISAR_CLDFB_RINGBUF_HANDLE h, + float **p_real, + float **p_imag, + const int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; - int32_t num_floats = (int32_t) total_size( h ); + int32_t num_floats = (int32_t) CLDFB_RINGBUF_total_size( h ); uint32_t offset; assert( -num_floats <= idx && idx <= num_floats ); @@ -196,21 +275,7 @@ void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_i *p_real = &h->real[offset]; *p_imag = &h->imag[offset]; -} - -int16_t CLDFB_RINGBUF_IsEmpty( CLDFB_RINGBUF_HANDLE h ) -{ - return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); -} -int16_t CLDFB_RINGBUF_IsFull( CLDFB_RINGBUF_HANDLE h ) -{ - return h->is_full; -} - -uint16_t CLDFB_RINGBUF_Size( CLDFB_RINGBUF_HANDLE h ) -{ - return (uint16_t) ( total_size( h ) / CLDFB_NO_CHANNELS_MAX ); + return; } - #endif diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index 0ea8d67a6a..b53d8cc200 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -1670,7 +1670,7 @@ static void binRenderer_split( for ( ch = 0; ch < nchan_out; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + ivas_CLDFB_RINGBUF_Push( hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural_loc[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural_loc[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index bb013b8d78..b0ce85ce51 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -1787,7 +1787,7 @@ void ivas_param_mc_dec_render( for ( ch = 0; ch < nchan_out_cldfb; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index 8fddef8ac8..ccae387180 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -791,7 +791,7 @@ static void ivas_mc_paramupmix_dec_sf( for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index 3ea63bfdf3..f92d987c3f 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -800,7 +800,7 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); + ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); #else diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index e251f4da7a..582fc08b65 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -185,7 +185,7 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( cldfbAnalysis_ts( &( output_f[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); + ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); for ( b = 0; b < num_cldfb_bands; b++ ) { diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 53651d186a..6cc208cf8b 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -40,9 +40,6 @@ #include "stat_dec.h" #include "ivas_stat_com.h" #include "ivas_stat_rend.h" -#ifdef FIX_1119_SPLIT_RENDERING_VOIP -#include "cldfb_ring_buffer.h" -#endif /*----------------------------------------------------------------------------------* @@ -831,8 +828,8 @@ typedef struct float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; } ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA, *ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE; -#endif +#endif typedef struct { #ifdef FIX_1119_SPLIT_RENDERING_VOIP @@ -849,7 +846,7 @@ typedef struct typedef struct { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; + ISAR_CLDFB_RINGBUF_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; #else ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE hMultiBinCldfbData; /*scratch buffer for frame by frame processing*/ #endif diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index bdb69ab8fc..0160b48549 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -114,11 +114,11 @@ static void store_JbmData( IVAS_DEC_VOIP *hVoIP, JB4_DATAUNIT_HANDLE dataUnit, c static ivas_error evs_dec_main( Decoder_Struct *st_ivas ); static ivas_error input_format_API_to_internal( IVAS_DEC_INPUT_FORMAT input_format, int16_t *bitstream_format_internal, int16_t *sdp_hf_only, const bool is_voip_enabled ); static void init_decoder_config( DECODER_CONFIG_HANDLE hDecoderConfig ); -static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, -#ifndef FIX_1119_SPLIT_RENDERING_VOIP - const int16_t isSplitRend, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); +#else +static ivas_error ivas_dec_setup_all( IVAS_DEC_HANDLE hIvasDec, uint8_t *nTransportChannels, const int16_t isSplitRend, ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); #endif - ISAR_SPLIT_REND_BITS_DATA *splitRendBits ); static ivas_error apa_setup( IVAS_DEC_HANDLE hIvasDec, const bool isInitialized_voip, const uint16_t nTransportChannels ); static PCM_RESOLUTION pcm_type_API_to_internal( const IVAS_DEC_PCM_TYPE pcmType ); static void *pcm_buffer_offset( void *buffer, const IVAS_DEC_PCM_TYPE pcmType, const int32_t offset ); @@ -1147,11 +1147,11 @@ ivas_error IVAS_DEC_GetSamplesDecoder( * Setup all decoder parts (IVAS decoder, ISAR) *-----------------------------------------------------------------*/ - if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, -#ifndef FIX_1119_SPLIT_RENDERING_VOIP - isSplitRend, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, splitRendBits ) ) != IVAS_ERR_OK ) +#else + if ( ( error = ivas_dec_setup_all( hIvasDec, &nTransportChannels, isSplitRend, splitRendBits ) ) != IVAS_ERR_OK ) #endif - splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -1841,11 +1841,19 @@ ivas_error IVAS_DEC_GetSamplesRenderer( #ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * isar_get_frame_size( ) + * + * + *---------------------------------------------------------------------*/ + static int16_t isar_get_frame_size( - Decoder_Struct *st_ivas ) + Decoder_Struct *st_ivas /* i : IVAS decoder handle */ +) { int32_t output_Fs; int16_t nSamplesPerChannel; + output_Fs = st_ivas->hDecoderConfig->output_Fs; if ( st_ivas->hDecoderConfig->render_framesize != IVAS_RENDER_FRAMESIZE_20MS && @@ -1863,27 +1871,29 @@ static int16_t isar_get_frame_size( return nSamplesPerChannel; } + +/*---------------------------------------------------------------------* + * isar_render_poses( ) + * + * + *---------------------------------------------------------------------*/ + static ivas_error isar_render_poses( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ - float **p_head_pose_buf, - int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ - bool *needNewFrame /* o : indication that the decoder needs a new frame */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ + float **p_head_pose_buf, /* o : PCM buffer with head-pose data */ + int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ + bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) { - Decoder_Struct *st_ivas; float pcmBuf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES * L_FRAME48k]; int16_t i, j; ivas_error error; - ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; int16_t numPoses; - error = IVAS_ERR_OK; - st_ivas = hIvasDec->st_ivas; *needNewFrame = false; - hSplitBinRend = st_ivas->hSplitBinRend; - numPoses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; + numPoses = hIvasDec->st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; /* init flush buffer for rate switch if not already initizalized */ if ( hIvasDec->flushbuffer == NULL ) @@ -1902,6 +1912,7 @@ static ivas_error isar_render_poses( { return error; } + if ( !hIvasDec->hasBeenFedFirstGoodFrame ) { return IVAS_ERR_OK; @@ -1919,15 +1930,19 @@ static ivas_error isar_render_poses( return error; } + +/*---------------------------------------------------------------------* + * isar_generate_metadata_and_bitstream( ) + * + * + *---------------------------------------------------------------------*/ + static ivas_error isar_generate_metadata_and_bitstream( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - float **p_head_pose_buf, - ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ + Decoder_Struct *st_ivas, /* i/o: IVAS decoder handle */ + float **p_head_pose_buf, /* i/o: PCM buffer with head-pose data */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { - Decoder_Struct *st_ivas; - AUDIO_CONFIG output_config; - int32_t output_Fs; ivas_error error; ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; int16_t max_band; @@ -1939,14 +1954,10 @@ static ivas_error isar_generate_metadata_and_bitstream( float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; - error = IVAS_ERR_OK; - st_ivas = hIvasDec->st_ivas; - output_config = st_ivas->hDecoderConfig->output_config; - output_Fs = st_ivas->hDecoderConfig->output_Fs; hSplitBinRend = st_ivas->hSplitBinRend; - max_band = (int16_t) ( ( BINAURAL_MAXBANDS * output_Fs ) / 48000 ); - pcm_out_flag = ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; + max_band = (int16_t) ( ( BINAURAL_MAXBANDS * st_ivas->hDecoderConfig->output_Fs ) / 48000 ); + pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; td_input = st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC_ROOM && st_ivas->renderer_type != RENDERER_STEREO_PARAMETRIC; if ( !td_input ) @@ -1959,20 +1970,13 @@ static ivas_error isar_generate_metadata_and_bitstream( { /* Save pointers to first CLDFB column in the ring buffer. Allows us to save * significant amounts of memory by not copying CLDFB values into a separate buffer. */ - CLDFB_RINGBUF_GetByIdx( - hSplitBinRend->hMultiBinCldfbData[i], - &p_Cldfb_RealBuffer_Binaural[i][j], - &p_Cldfb_ImagBuffer_Binaural[i][j], - 0 ); + ivas_CLDFB_RINGBUF_GetByIdx( hSplitBinRend->hMultiBinCldfbData[i], &p_Cldfb_RealBuffer_Binaural[i][j], &p_Cldfb_ImagBuffer_Binaural[i][j], 0 ); + /* Pop the CLDFB column we just saved pointers to. This is fine as long as we use * the saved columns only before any new columns are pushed to the buffer - the new * columns could potentially overwrite the old columns we wanted to use. * This requirement is fulfilled in this case. */ - CLDFB_RINGBUF_Pop( - hSplitBinRend->hMultiBinCldfbData[i], - NULL, - NULL, - CLDFB_NO_CHANNELS_MAX ); + ivas_CLDFB_RINGBUF_Pop( hSplitBinRend->hMultiBinCldfbData[i], NULL, NULL, CLDFB_NO_CHANNELS_MAX ); } } } @@ -2036,7 +2040,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( ivas_error error; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; float *p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; - int32_t i; + int16_t i; int16_t pcm_out_flag; int16_t numSamplesPerChannelToOutput; @@ -2045,7 +2049,6 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - error = IVAS_ERR_UNKNOWN; st_ivas = hIvasDec->st_ivas; if ( is_split_rendering_enabled( st_ivas->hDecoderConfig, st_ivas->hRenderConfig ) == 0 ) @@ -2061,18 +2064,17 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( p_head_pose_buf[i] = head_pose_buf[i]; } - error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ); - if ( error != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) { return error; } + if ( !hIvasDec->hasBeenFedFirstGoodFrame ) { return IVAS_ERR_OK; } - error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); - if ( error != IVAS_ERR_OK ) + if ( ( error = isar_generate_metadata_and_bitstream( st_ivas, p_head_pose_buf, splitRendBits ) ) != IVAS_ERR_OK ) { return error; } @@ -2285,7 +2287,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( } #endif - return error; + return IVAS_ERR_OK; } @@ -3681,13 +3683,16 @@ ivas_error IVAS_DEC_TSM_SetQuality( #endif #ifdef FIX_1119_SPLIT_RENDERING_VOIP + /*---------------------------------------------------------------------* * ivas_dec_voip_get_samples_common( ) * * Main function to output one frame in VoIP. Holds common code for * regular output configs and split rendering configs. *---------------------------------------------------------------------*/ + static ivas_error ivas_dec_voip_get_samples_common + #else /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_GetSamples( ) @@ -3703,7 +3708,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ void *pcmBuf, /* o : output synthesis signal */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ + ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ + float **p_head_pose_buf, /* i : PCM buffer with head-pose data */ #endif #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, @@ -3724,11 +3730,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples int16_t result; ivas_error error; uint8_t nOutChannels; - #ifdef FIX_1119_SPLIT_RENDERING_VOIP int32_t i; - float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; - float *p_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; #endif if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->hVoIP == NULL ) @@ -3753,8 +3756,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples { return IVAS_ERROR( IVAS_ERR_NOT_IMPLEMENTED, "Split rendering is not integrated with VoIP mode" ); } -#endif +#endif /* make sure that the FIFO after decoder/scaler contains at least one sound card frame (i.e. 20ms) */ while ( *nSamplesRendered < nSamplesPerChannel ) { @@ -3776,7 +3779,6 @@ ivas_error IVAS_DEC_VoIP_GetSamples extBufferedTime_ms = extBufferedSamples * 1000 / hDecoderConfig->output_Fs; dataUnit = NULL; - /* pop one access unit from the jitter buffer */ result = JB4_PopDataUnit( hVoIP->hJBM, systemTimestamp_ms, extBufferedTime_ms, &dataUnit, &scale, &maxScaling ); if ( result != 0 ) @@ -3900,13 +3902,11 @@ ivas_error IVAS_DEC_VoIP_GetSamples { if ( hIvasDec->nSamplesAvailableNext == 0 || hIvasDec->nSamplesAvailableNext == hIvasDec->nSamplesFrame ) { - if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - splitRendBits + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, splitRendBits ) ) != IVAS_ERR_OK ) #else - 0, NULL + if ( ( error = IVAS_DEC_GetSamplesDecoder( hIvasDec, 0, NULL ) ) != IVAS_ERR_OK ) #endif - ) ) != IVAS_ERR_OK ) { return error; } @@ -3929,12 +3929,12 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + if ( splitRendBits != NULL ) { /* Move output pointers forward */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { - p_head_pose_buf[i] = &head_pose_buf[i][*nSamplesRendered]; + p_head_pose_buf[i] += *nSamplesRendered; } /* Render head poses from time-scaled transport channels */ @@ -3942,6 +3942,12 @@ ivas_error IVAS_DEC_VoIP_GetSamples { return error; } + + /* Set pointers back to the beginning of head pose buffers */ + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] -= *nSamplesRendered; + } } else { @@ -3961,25 +3967,16 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - if ( hIvasDec->hasDecodedFirstGoodFrame && - ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || - hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) + if ( hIvasDec->hasDecodedFirstGoodFrame && splitRendBits != NULL ) { - /* Set pointers to beginning of head pose buffers */ - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] = head_pose_buf[i]; - } - /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ - error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); - if ( error != IVAS_ERR_OK ) + if ( ( error = isar_generate_metadata_and_bitstream( st_ivas, p_head_pose_buf, splitRendBits ) ) != IVAS_ERR_OK ) { return error; } /* Synthesise PCM output if split PCM */ - if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { if ( st_ivas->hDecoderConfig->render_framesize == IVAS_RENDER_FRAMESIZE_5MS ) { @@ -4004,6 +4001,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #ifdef FIX_1119_SPLIT_RENDERING_VOIP + /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_GetSamples( ) * @@ -4011,10 +4009,9 @@ ivas_error IVAS_DEC_VoIP_GetSamples *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_VoIP_GetSamples( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ - const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ - void *pcmBuf, /* o : output synthesis signal */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + void *pcmBuf, /* o : output synthesis signal */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, void *jbmWriter, @@ -4028,9 +4025,10 @@ ivas_error IVAS_DEC_VoIP_GetSamples( return ivas_dec_voip_get_samples_common( hIvasDec, nSamplesPerChannel, - pcmType, + IVAS_DEC_PCM_INT16, pcmBuf, NULL, + NULL, #ifdef SUPPORT_JBM_TRACEFILE jbmWriterFn, jbmWriter, @@ -4044,27 +4042,27 @@ ivas_error IVAS_DEC_VoIP_GetSamples( /*! r: error code */ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ void *pcmBuf, /* o : output synthesis signal */ ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, - void *jbmWriter + void *jbmWriter, #endif - , bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ uint16_t *nSamplesRendered, /* o : number of samples rendered */ bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ const uint32_t systemTimestamp_ms /* i : current system timestamp */ ) { - ivas_error error = IVAS_ERR_UNKNOWN; - int16_t nSamplesPerChannel = 0; + int16_t i; + float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; + float *pp_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; - if ( ( error = IVAS_DEC_GetRenderFramesizeSamples( hIvasDec, &nSamplesPerChannel ) ) != IVAS_ERR_OK ) + /* Set pointers to beginning of head pose buffers */ + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { - fprintf( stderr, "\nError getting render frame size in samples\n" ); - return error; + pp_head_pose_buf[i] = head_pose_buf[i]; } return ivas_dec_voip_get_samples_common( @@ -4073,6 +4071,7 @@ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( IVAS_DEC_PCM_INT16, pcmBuf, splitRendBits, + pp_head_pose_buf, #ifdef SUPPORT_JBM_TRACEFILE jbmWriterFn, jbmWriter, @@ -5186,7 +5185,7 @@ static ivas_error ivas_create_handle_isar( isar_init_split_rend_handles( &hSplitBinRend->splitrend ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP - for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) + for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) { hSplitBinRend->hMultiBinCldfbData[i] = NULL; } @@ -5219,11 +5218,11 @@ static void ivas_destroy_handle_isar( if ( *hSplitBinRend != NULL ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) + for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) { if ( ( *hSplitBinRend )->hMultiBinCldfbData[i] != NULL ) { - CLDFB_RINGBUF_Close( &( *hSplitBinRend )->hMultiBinCldfbData[i] ); + ivas_CLDFB_RINGBUF_Close( &( *hSplitBinRend )->hMultiBinCldfbData[i] ); } } #else @@ -5439,11 +5438,10 @@ static ivas_error ivas_dec_init_split_rend( if ( cldfb_in_flag ) { - for ( i = 0; i < (int16_t) ( num_poses * BINAURAL_CHANNELS ); ++i ) + for ( i = 0; i < num_poses * BINAURAL_CHANNELS; ++i ) { /* note: this is intra-frame heap memory */ - error = CLDFB_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], CLDFB_NO_COL_MAX ); - if ( error != IVAS_ERR_OK ) + if ( ( error = ivas_CLDFB_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinCldfbData[i], CLDFB_NO_COL_MAX ) ) != IVAS_ERR_OK ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); } diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 73c55bd1f0..181417c70a 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -311,7 +311,9 @@ ivas_error IVAS_DEC_TSM_SetQuality( ivas_error IVAS_DEC_VoIP_GetSamples( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ +#ifndef FIX_1119_SPLIT_RENDERING_VOIP const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ +#endif void *pcmBuf, /* o : output synthesis signal */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, @@ -327,17 +329,16 @@ ivas_error IVAS_DEC_VoIP_GetSamples( /*! r: error code */ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ void *pcmBuf, /* o : output synthesis signal */ ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, - void* jbmWriter + void* jbmWriter, #endif - , bool *bitstreamReadDone, /* o : flag indicating that bitstream was read */ - uint16_t *nSamplesRendered, - bool *parametersAvailableForEditing, + uint16_t *nSamplesRendered, /* o : number of samples rendered */ + bool *parametersAvailableForEditing, /* o : indicates whether objects editing is available */ const uint32_t systemTimestamp_ms /* i : current system timestamp */ ); #endif diff --git a/lib_isar/isar_prot.h b/lib_isar/isar_prot.h index f842b0a53b..53d70184f0 100644 --- a/lib_isar/isar_prot.h +++ b/lib_isar/isar_prot.h @@ -189,13 +189,13 @@ void isar_splitBinLCLDEncClose( void isar_splitBinLCLDEncProcess( ISAR_BIN_HR_SPLIT_LCLD_ENC_HANDLE hSplitBinLCLDEnc, /* i/o: ISAR LCLD encoder handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], - float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], + float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ + float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, imag. part */ #else float Cldfb_In_Real[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_In_Imag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], #endif - const int32_t available_bits, + const int32_t available_bits, /* i : available bit-budget */ ISAR_SPLIT_REND_BITS_HANDLE pBits /* i/o: ISAR bits handle */ ); diff --git a/lib_isar/isar_splitRend_lcld_enc.c b/lib_isar/isar_splitRend_lcld_enc.c index 171b5162b3..1ecb83887b 100644 --- a/lib_isar/isar_splitRend_lcld_enc.c +++ b/lib_isar/isar_splitRend_lcld_enc.c @@ -159,14 +159,14 @@ void isar_splitBinLCLDEncClose( void isar_splitBinLCLDEncProcess( ISAR_BIN_HR_SPLIT_LCLD_ENC_HANDLE hSplitBinLCLDEnc, /* i/o: ISAR LCLD encoder handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], - float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], + float *Cldfb_In_Real[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, real part */ + float *Cldfb_In_Imag[][CLDFB_NO_COL_MAX], /* i/o: Binaural signals, imag. part */ #else float Cldfb_In_Real[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], float Cldfb_In_Imag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], #endif - const int32_t available_bits, - ISAR_SPLIT_REND_BITS_HANDLE pBits /* i/o: ISAR bits handle */ + const int32_t available_bits, /* i : available bit-budget */ + ISAR_SPLIT_REND_BITS_HANDLE pBits /* i/o: ISAR bits handle */ ) { int32_t iBitsWritten, itr, available_bits_itr, rem_itr, available_bits_local; diff --git a/lib_isar/isar_stat.h b/lib_isar/isar_stat.h index 587c3c8ea7..0e0577d570 100644 --- a/lib_isar/isar_stat.h +++ b/lib_isar/isar_stat.h @@ -345,4 +345,17 @@ typedef struct } SPLIT_REND_WRAPPER; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +typedef struct +{ + float *real; + float *imag; + uint32_t capacity; + uint32_t write_pos; + uint32_t read_pos; + int16_t is_full; + +} ISAR_CLDFB_RINGBUF_DATA, *ISAR_CLDFB_RINGBUF_HANDLE; +#endif + #endif /* ISAR_STAT_H */ diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 33328e365c..98f05e7b8e 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -796,7 +796,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], @@ -873,7 +873,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - CLDFB_RINGBUF_Push( + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 9401b9e553..7edde29027 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7462,11 +7462,10 @@ static ivas_error getSamplesInternal( int16_t ch; int16_t i, ro_md_flag; float *tmpBinaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS], tmpBinaural_buff[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][L_FRAME48k]; - #ifdef FIX_1119_SPLIT_RENDERING_VOIP float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; - int32_t j; + int16_t j; for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { @@ -7521,7 +7520,7 @@ static ivas_error getSamplesInternal( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From 5aa659573e23e6308fdad18c5b719f93d3d2c9b9 Mon Sep 17 00:00:00 2001 From: "Singh, Ripinder" Date: Thu, 10 Jul 2025 15:26:39 +1000 Subject: [PATCH 065/340] First draft of the RTP Pack API * Introduce RTP Packet and Payload API implementation for Pack and Unpack * Introduce general handling of PI data and data type structures * Introduce the mutex encapsulation * Introduce a buffer pool manager * Example pack implementation in Encoder wrapper * Code Review comments Signed-off-by: Singh, Ripinder --- apps/decoder.c | 259 +++- apps/encoder.c | 174 ++- lib_com/ivas_error.h | 9 + lib_dec/lib_dec.c | 8 +- lib_dec/lib_dec.h | 6 +- lib_util/ivas_bpool.c | 154 +++ lib_util/ivas_bpool.h | 58 + lib_util/ivas_queue.c | 126 ++ lib_util/ivas_queue.h | 68 + lib_util/ivas_rtp_api.h | 251 +++- lib_util/ivas_rtp_internal.h | 136 ++ lib_util/ivas_rtp_payload.c | 2387 ++++++++++++++++++++++------------ lib_util/ivas_rtp_pi_data.c | 601 +++++++++ lib_util/ivas_rtp_pi_data.h | 441 +++++++ lib_util/ivas_rtp_rom.c | 64 + lib_util/mutex.h | 104 ++ 16 files changed, 3829 insertions(+), 1017 deletions(-) create mode 100644 lib_util/ivas_bpool.c create mode 100644 lib_util/ivas_bpool.h create mode 100644 lib_util/ivas_queue.c create mode 100644 lib_util/ivas_queue.h create mode 100644 lib_util/ivas_rtp_internal.h create mode 100644 lib_util/ivas_rtp_pi_data.c create mode 100644 lib_util/ivas_rtp_pi_data.h create mode 100644 lib_util/ivas_rtp_rom.c create mode 100644 lib_util/mutex.h diff --git a/apps/decoder.c b/apps/decoder.c index 213b77985a..39c4f19824 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -39,7 +39,8 @@ #include "evs_rtp_payload.h" #include "ism_file_writer.h" #ifdef IVAS_RTPDUMP -#include "ivas_rtp_payload.h" +#include "ivas_rtp_api.h" +#include "ivas_rtp_pi_data.h" #endif #include "jbm_file_writer.h" #include "hrtf_file_reader.h" @@ -166,6 +167,170 @@ typedef struct } IVAS_DEC_HRTF_BINARY_WRAPPER; +#ifdef IVAS_RTPDUMP + +typedef struct +{ + PIDATA data; + uint32_t timestamp; +} PIDATA_TS; + +typedef struct +{ + uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * MAX_FRAMES_PER_RTP_PACKET]; + PIDATA_TS piData[IVAS_PI_MAX_ID * MAX_FRAMES_PER_RTP_PACKET]; + + FILE *f_rtpstream; + IVAS_RTP_CODEC codecId; + uint32_t nReadPiData; + uint32_t nProcPiData; + uint32_t numFramesInPacket; + uint32_t numPiDataInPacket; + uint32_t remoteRequestBitmap; + + IVAS_DATA_BUFFER rtpPacket; + IVAS_RTP_UNPACK_HANDLE hUnpack; + IVAS_RTP_UNPACK_CONFIG unpackCfg; + PI_DATA_DEPACKER_STATE piDataDepackerState; +} IVAS_RTP; + +static void IVAS_RTP_Term( IVAS_RTP *rtp ) +{ + if ( rtp->f_rtpstream ) + { + fclose( rtp->f_rtpstream ); + rtp->f_rtpstream = NULL; + } + IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); +} + +static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, FILE *f_rtpstream ) +{ + ivas_error error = IVAS_ERR_OK; + + memset( rtp, 0, sizeof( IVAS_RTP ) ); + + error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); + + rtp->f_rtpstream = f_rtpstream; + rtp->rtpPacket.buffer = rtp->packet; + rtp->rtpPacket.capacity = sizeof( rtp->packet ); + + return error; +} + +static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) +{ + ivas_error error = IVAS_ERR_OK; + IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; + uint32_t packetLen = 0u; + bool speechLostIndicated = false; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO srInfo; +#endif + + packedFrame.buffer = au; + packedFrame.capacity = ( IVAS_MAX_BITS_PER_FRAME / 8 ); + if ( rtp->numFramesInPacket == 0 ) + { + size_t nread = fread( &packetLen, sizeof( uint32_t ), 1, rtp->f_rtpstream ); /* Read Packet Length */ + if ( nread == 0 ) + { + return IVAS_ERR_END_OF_FILE; + } + if ( packetLen > sizeof( rtp->packet ) ) + { + fprintf( stderr, "RTP packet > buffer capacity %lu bytes\n", sizeof( rtp->packet ) ); + return IVAS_ERR_INVALID_OUTPUT_BUFFER_SIZE; + } + + nread = fread( rtp->packet, sizeof( uint8_t ), packetLen, rtp->f_rtpstream ); /* Read Packet */ + if ( nread == 0 ) + { + return IVAS_ERR_END_OF_FILE; + } + + rtp->rtpPacket.buffer = rtp->packet; + rtp->rtpPacket.length = packetLen; + + if ( ( error = IVAS_RTP_UNPACK_PushPacket( rtp->hUnpack, &rtp->rtpPacket, + &rtp->numFramesInPacket, &rtp->numPiDataInPacket, + &rtp->remoteRequestBitmap ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to unpack RTP packet\n" ); + return error; + } + + rtp->nReadPiData = 0; + rtp->nProcPiData = 0; + + /* Pre-read all PI data */ + while ( rtp->numPiDataInPacket != 0 ) + { + PIDATA_TS *piData = &rtp->piData[rtp->nReadPiData]; + if ( ( error = IVAS_RTP_UNPACK_PullNextPiData( rtp->hUnpack, (IVAS_PIDATA_GENERIC *) &piData->data, sizeof( piData->data ), &piData->timestamp ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to pull PI Data, error = %s\n", ivas_error_to_string( error ) ); + return error; + } + rtp->nReadPiData++; + rtp->numPiDataInPacket--; + } + } + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &rtp->codecId, &srInfo, &packedFrame, rtpTimeStamp, rtpSequenceNumber, &speechLostIndicated ); +#else + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &rtp->codecId, &packedFrame, rtpTimeStamp, rtpSequenceNumber, &speechLostIndicated ); +#endif + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to pull frame after unpack\n" ); + return error; + } + + *auSizeBits = (int16_t) ( packedFrame.length * 8 ); + *qBit = !speechLostIndicated; + rtp->numFramesInPacket--; + *nextPacketRcvTime_ms += 20; + + while ( rtp->nProcPiData < rtp->nReadPiData && + rtp->piData[rtp->nProcPiData].timestamp <= *rtpTimeStamp ) + { + PIDATA_TS *piData = &rtp->piData[rtp->nProcPiData++]; + if ( hIvasDec ) + { + uint32_t piDataType = ( (IVAS_PIDATA_GENERIC *) &piData->data )->piDataType; + switch ( piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + { + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, &piData->data.scene.orientation ); + } + break; + + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + { + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, &piData->data.deviceCompensated.orientation ); + } + break; + + default: + { + fprintf( stderr, "Unhandled PI data of type : %d\n", piDataType ); + } + break; + } + if ( error != IVAS_ERR_OK ) + { + return error; + } + } + } + + return IVAS_ERR_OK; +} +#endif /* IVAS_RTPDUMP */ /*------------------------------------------------------------------------------------------* * Local functions prototypes @@ -2883,8 +3048,7 @@ static ivas_error printBitstreamInfoVoip( ivas_error error = IVAS_ERR_OK; FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP - IVAS_RTPDUMP_DEPACKER rtpdumpDepacker; - IVAS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_NO_ERROR; + IVAS_RTP ivasRtp; #else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; @@ -2892,21 +3056,16 @@ static ivas_error printBitstreamInfoVoip( uint8_t au[( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3]; int16_t auSizeBits; uint8_t *auPtr = NULL; -#ifdef IVAS_RTPDUMP - bool evsIvasModeBit; - uint16_t bitrateIndex; - bool ivasIndicatorBit; - bool isGoodFrame; -#else bool isAMRWB_IOmode; uint16_t frameTypeIndex; bool qBit; -#endif uint32_t nextPacketRcvTime_ms = 0; uint16_t rtpSequenceNumber; uint32_t rtpTimeStamp; +#ifndef IVAS_RTPDUMP rtpdumpDepacker.rtpdump = NULL; +#endif switch ( arg.inputFormat ) { case IVAS_DEC_INPUT_FORMAT_RTPDUMP: @@ -2920,15 +3079,19 @@ static ivas_error printBitstreamInfoVoip( } #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream ); + if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); + goto cleanup; + } #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream, arg.inputFormat == IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF ); -#endif if ( rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) { fprintf( stderr, "error in EVS_RTPDUMP_DEPACKER_open(): %d\n", rtpdumpDepackerError ); goto cleanup; } +#endif break; case IVAS_DEC_INPUT_FORMAT_G192: auPtr = au; @@ -2944,17 +3107,13 @@ static ivas_error printBitstreamInfoVoip( if ( arg.inputFormat == IVAS_DEC_INPUT_FORMAT_G192 ) { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSizeBits, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); -#ifdef IVAS_RTPDUMP - isGoodFrame = 1; /* good frame for INPUT_FORMAT_G192 */ -#else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ -#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, &isGoodFrame, &auPtr, (uint16_t *) &auSizeBits ); + error = readNextFrame( &ivasRtp, NULL, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); #endif @@ -2967,11 +3126,7 @@ static ivas_error printBitstreamInfoVoip( fprintf( stderr, "failed to read first RTP packet\n" ); goto cleanup; } -#ifdef IVAS_RTPDUMP - } while ( !isGoodFrame || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); -#else } while ( !qBit || auSizeBits < MIN_NUM_BITS_ACTIVE_FRAME || auSizeBits == NUM_BITS_SID_IVAS_5K2 ); -#endif BS_Reader_Rewind( hBsReader ); @@ -2986,7 +3141,7 @@ static ivas_error printBitstreamInfoVoip( cleanup: #ifdef IVAS_RTPDUMP - IVAS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); + IVAS_RTP_Term( &ivasRtp ); #else EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); #endif @@ -3057,23 +3212,17 @@ static ivas_error decodeVoIP( int16_t i; FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP - IVAS_RTPDUMP_DEPACKER rtpdumpDepacker; - IVAS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_NO_ERROR; + IVAS_RTP ivasRtp; #else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; #endif uint8_t *auPtr = NULL; -#ifdef IVAS_RTPDUMP - bool evsIvasModeBit; - uint16_t bitrateIndex; - bool ivasIndicatorBit; - bool isGoodFrame; -#else +#ifndef IVAS_RTPDUMP bool isAMRWB_IOmode; uint16_t frameTypeIndex; - bool qBit; #endif + bool qBit; IVAS_DEC_BS_FORMAT bsFormat = IVAS_DEC_BS_UNKOWN; IsmFileWriter *ismWriters[IVAS_MAX_NUM_OBJECTS]; @@ -3110,7 +3259,9 @@ static ivas_error decodeVoIP( delayNumSamples_orig[0] = -1; +#ifndef IVAS_RTPDUMP rtpdumpDepacker.rtpdump = NULL; +#endif switch ( arg.inputFormat ) { case IVAS_DEC_INPUT_FORMAT_RTPDUMP: @@ -3128,10 +3279,9 @@ static ivas_error decodeVoIP( } #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream ); - if ( rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) + if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "error in IVAS_RTPDUMP_DEPACKER_open(): %d\n", rtpdumpDepackerError ); + fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); goto cleanup; } #else @@ -3178,17 +3328,13 @@ static ivas_error decodeVoIP( if ( arg.inputFormat == IVAS_DEC_INPUT_FORMAT_G192 ) { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); -#ifdef IVAS_RTPDUMP - isGoodFrame = 1; /* good frame for INPUT_FORMAT_G192 */ -#else qBit = 1; /* good q_bit for INPUT_FORMAT_G192 */ -#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, &isGoodFrame, &auPtr, (uint16_t *) &auSize ); + error = readNextFrame( &ivasRtp, hIvasDec, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif @@ -3197,7 +3343,7 @@ static ivas_error decodeVoIP( rtpTimeStamp = rtpTimeStamp / 16; } #ifdef IVAS_RTPDUMP - if ( error != IVAS_ERR_OK || rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) + if ( error != IVAS_ERR_OK ) #else if ( error != IVAS_ERR_OK || rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) #endif @@ -3352,11 +3498,7 @@ static ivas_error decodeVoIP( while ( nextPacketRcvTime_ms <= systemTime_ms ) { /* feed the previous read packet into the receiver now */ -#ifdef IVAS_RTPDUMP - error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, isGoodFrame ); -#else error = IVAS_DEC_VoIP_FeedFrame( hIvasDec, auPtr, auSize, rtpSequenceNumber, rtpTimeStamp, nextPacketRcvTime_ms, qBit ); -#endif if ( error != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_FeedFrame: %s\n", IVAS_DEC_GetErrorMessage( error ) ); @@ -3369,30 +3511,16 @@ static ivas_error decodeVoIP( { error = BS_Reader_ReadVoipFrame_compact( hBsReader, au, &auSize, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms ); -#ifdef IVAS_RTPDUMP - isGoodFrame = 1; /* good frame for VOIP_G192_RTP */ -#else qBit = 1; /* good q_bit for VOIP_G192_RTP */ -#endif } else { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - rtpdumpDepackerError = IVAS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, - &nextPacketRcvTime_ms, - &evsIvasModeBit, &bitrateIndex, &ivasIndicatorBit, - &isGoodFrame, &auPtr, (uint16_t *) &auSize ); + error = readNextFrame( &ivasRtp, hIvasDec, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; - - /* feed PI data to decoder handle */ - error = IVAS_DEC_feedPIdata( hIvasDec, &rtpdumpDepacker.PIdataDepackerState ); - if ( error != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError in IVAS_DEC_feedPIdata: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, @@ -3403,7 +3531,7 @@ static ivas_error decodeVoIP( #endif } #ifdef IVAS_RTPDUMP - if ( error == IVAS_ERR_END_OF_FILE || rtpdumpDepackerError == IVAS_RTPDUMP_DEPACKER_EOF ) + if ( error == IVAS_ERR_END_OF_FILE ) #else if ( error == IVAS_ERR_END_OF_FILE || rtpdumpDepackerError == EVS_RTPDUMP_DEPACKER_EOF ) #endif @@ -3420,13 +3548,6 @@ static ivas_error decodeVoIP( fprintf( stderr, "\nError in BS_Reader_ReadVoipFrame_compact, error code: %d\n", error ); goto cleanup; } -#ifdef IVAS_RTPDUMP - else if ( rtpdumpDepackerError != IVAS_RTPDUMP_DEPACKER_NO_ERROR ) - { - fprintf( stderr, "\nError in IVAS_RTPDUMP_DEPACKER_readNextFrame, error code: %d\n", error ); - goto cleanup; - } -#endif } /* we are finished when all packets have been received and jitter buffer is empty */ @@ -3770,7 +3891,7 @@ static ivas_error decodeVoIP( cleanup: #ifdef IVAS_RTPDUMP - IVAS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); + IVAS_RTP_Term( &ivasRtp ); #else EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); #endif diff --git a/apps/encoder.c b/apps/encoder.c index fe444c4d11..861250a5a0 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -39,8 +39,8 @@ #include "jbm_file_reader.h" #include "masa_file_reader.h" #ifdef IVAS_RTPDUMP -#include "evs_rtp_payload.h" -#include "ivas_rtp_payload.h" +#include "ivas_rtp_api.h" +#include "ivas_rtp_pi_data.h" #include "rotation_file_reader.h" #endif #ifdef DEBUGGING @@ -48,7 +48,6 @@ #endif #include "wmc_auto.h" - #define WMC_TOOL_SKIP /*------------------------------------------------------------------------------------------* @@ -153,6 +152,7 @@ typedef struct bool ism_extended_metadata; #ifdef IVAS_RTPDUMP bool rtpdumpOutput; + uint32_t numFramesPerPacket; char *sceneOrientationTrajFileName; char *deviceOrientationTrajFileName; #endif @@ -222,8 +222,7 @@ int main( #ifdef IVAS_RTPDUMP FILE *f_rtpstream = NULL; - IVAS_RTPDUMP_PACKER *rtpdumpPacker = NULL; - IVAS_RTPDUMP_PACKER_ERROR rtpdumpPackerError = IVAS_RTPDUMP_PACKER_NO_ERROR; + IVAS_RTP_PACK_HANDLE hPack = NULL; #endif /*------------------------------------------------------------------------------------------* @@ -623,19 +622,38 @@ int main( if ( arg.rtpdumpOutput ) { + IVAS_RTP_PACK_CONFIG packCfg; + uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); + + packCfg.maxFramesPerPacket = arg.numFramesPerPacket; + /* Open the output file for RTPDump writing */ f_rtpstream = fopen( arg.outputBitstreamFilename, "wb" ); - if ( f_rtpstream == NULL ) { fprintf( stderr, "could not open: %s\n", arg.outputBitstreamFilename ); goto cleanup; } - rtpdumpPackerError = IVAS_RTPDUMP_PACKER_open( &rtpdumpPacker, f_rtpstream ); - if ( rtpdumpPackerError != IVAS_RTPDUMP_PACKER_NO_ERROR ) + error = IVAS_RTP_PACK_Open( &hPack, &packCfg ); + if ( error != IVAS_ERR_OK ) { - fprintf( stderr, "error in IVAS_RTPDUMP_PACKER_open(): %d\n", rtpdumpPackerError ); + fprintf( stderr, "error in IVAS_RTP_PACK_Open(): %d\n", error ); + goto cleanup; + } + + + error = IVAS_RTP_PACK_UpdateHeader( + hPack, + SSRC, + 0, + NULL, + 0, + 0, + NULL ); + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_PACK_UpdateHeader(): %d\n", error ); goto cleanup; } } @@ -669,6 +687,7 @@ int main( int16_t numSamplesRead = 0; uint16_t bitStream[IVAS_MAX_BITS_PER_FRAME]; + uint16_t numBits = 0; #ifdef DEBUG_SBA #ifdef DEBUG_AGC @@ -710,7 +729,11 @@ int main( goto cleanup; } +#ifdef IVAS_RTPDUMP + if ( ( numSamplesRead == 0 ) && ( IVAS_RTP_PACK_GetNumFrames( hPack ) == 0 ) ) +#else if ( numSamplesRead == 0 ) +#endif { /* end of input data */ break; @@ -838,43 +861,111 @@ int main( } #ifdef IVAS_RTPDUMP - if ( rtpdumpPacker && rtpdumpPacker->rtpdump ) + if ( hPack ) { + uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; + uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * MAX_FRAMES_PER_RTP_PACKET]; + uint32_t n = numBits / 8, x = 0, numFramesInPayload = 0, packetLength = 0; + uint16_t *bitstrm = bitStream; + IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; + IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; + + packedFrame.capacity = sizeof( au ); + packedFrame.buffer = au; + rtpPacket.capacity = sizeof( packet ); + rtpPacket.buffer = packet; + + /* Pack Encoded Stream */ + while ( n ) + { + x = ( ( bitstrm[0] << 7 ) | ( bitstrm[1] << 6 ) | ( bitstrm[2] << 5 ) | ( bitstrm[3] << 4 ) | + ( bitstrm[4] << 3 ) | ( bitstrm[5] << 2 ) | ( bitstrm[6] << 1 ) | ( bitstrm[7] << 0 ) ); + packedFrame.buffer[packedFrame.length++] = (uint8_t) x; + bitstrm += 8; + n--; + } + + /* AMWWB-IO has non octet sized bitstream */ + if ( numBits & 0x7 ) + { + x = 0; + for ( n = 0; n < (uint32_t) ( numBits & 0x7 ); n++ ) + { + x |= (uint8_t) ( bitstrm[n] << ( 7 - n ) ); + } + packedFrame.buffer[packedFrame.length++] = (uint8_t) x; + } + + /* Push Encoded Stream to */ + if ( ( error = IVAS_RTP_PACK_PushFrame( hPack, + IVAS_isImmersiveFormat( hIvasEnc ) ? IVAS_RTP_IVAS : IVAS_RTP_EVS, +#ifdef RTP_S4_251135_CR26253_0016_REV1 + NULL, +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + &packedFrame ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing audio frame to RTP pack\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } + /* scene orientation */ if ( sceneOrientationFileReader ) { - if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &rtpdumpPacker->piDataPacker.sceneOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + IVAS_PIDATA_ORIENTATION sceneOrientation = { + .size = sizeof( IVAS_PIDATA_ORIENTATION ), + .piDataType = IVAS_PI_SCENE_ORIENTATION, + .orientation = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &sceneOrientation.orientation, NULL ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while reading scene orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( sceneOrientationFileReader ) ); goto cleanup; } - rtpdumpPacker->piDataPacker.sceneOrientationPresent = true; + + if ( ( error = IVAS_RTP_PACK_PushPiData( hPack, (const IVAS_PIDATA_GENERIC *) &sceneOrientation ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing scene orientation\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } } /* device orientation */ if ( deviceOrientationFileReader ) { - if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &rtpdumpPacker->piDataPacker.deviceOrientationQuat, NULL ) ) != IVAS_ERR_OK ) + IVAS_PIDATA_ORIENTATION deviceOrientation = { + .size = sizeof( IVAS_PIDATA_ORIENTATION ), + .piDataType = IVAS_PI_DEVICE_ORIENTATION_COMPENSATED, + .orientation = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &deviceOrientation.orientation, NULL ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while reading device orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( deviceOrientationFileReader ) ); goto cleanup; } - rtpdumpPacker->piDataPacker.deviceOrientationPresent = true; - } - /* PI presence */ - IVAS_RTPDUMP_PACKER_determinePIpresence( rtpdumpPacker ); + if ( ( error = IVAS_RTP_PACK_PushPiData( hPack, (const IVAS_PIDATA_GENERIC *) &deviceOrientation ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing scene orientation\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } + } - /* write rtpdump */ - rtpdumpPackerError = IVAS_RTPDUMP_PACKER_writeNextFrame( rtpdumpPacker, bitStream, numBits, - IVAS_isImmersiveFormat( hIvasEnc ), 0, false, NO_BANDWIDTH_REQUEST, NO_FORMAT_REQUEST ); - if ( rtpdumpPackerError != IVAS_RTPDUMP_PACKER_NO_ERROR ) + if ( ( numSamplesRead < pcmBufSize ) || IVAS_RTP_PACK_GetNumFrames( hPack ) == arg.numFramesPerPacket ) { - fprintf( stderr, "IVAS_RTPDUMP_PACKER_writeNextFrame() failed, error code: %d\n", rtpdumpPackerError ); - goto cleanup; - } + /* Generate RTP Packet */ + if ( ( error = IVAS_RTP_PACK_GetPacket( hPack, &rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while packing RTP Header\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } - IVAS_RTPDUMP_PACKER_resetPIdata( rtpdumpPacker ); + /* File Format = Packet Length (uint32_t) + Packet bytes */ + packetLength = (uint32_t) rtpPacket.length; + fwrite( &packetLength, sizeof( packetLength ), 1, f_rtpstream ); + fwrite( rtpPacket.buffer, sizeof( uint8_t ), packetLength, f_rtpstream ); + } } else { @@ -966,9 +1057,9 @@ cleanup: } #ifdef IVAS_RTPDUMP - if ( rtpdumpPacker ) + if ( hPack ) { - IVAS_RTPDUMP_PACKER_close( &rtpdumpPacker ); + IVAS_RTP_PACK_Close( &hPack ); } if ( sceneOrientationFileReader ) @@ -980,6 +1071,11 @@ cleanup: { RotationFileReader_close( &deviceOrientationFileReader ); } + + if ( f_rtpstream ) + { + fclose( f_rtpstream ); + } #endif IVAS_ENC_Close( &hIvasEnc ); @@ -1858,9 +1954,25 @@ static bool parseCmdlIVAS_enc( else if ( strcmp( argv_to_upper, "-RTPDUMP" ) == 0 ) { + i++; arg->rtpdumpOutput = true; - fprintf( stdout, "Output format: RTPDump\n" ); - ++i; + if ( i < argc - 4 ) + { + if ( !is_digits_only( argv[i] ) ) + { + arg->numFramesPerPacket = 1; /* Default to 1 frame per packet */ + } + else + { + arg->numFramesPerPacket = atoi( argv[i++] ); + if ( arg->numFramesPerPacket > MAX_FRAMES_PER_RTP_PACKET ) + { + fprintf( stderr, "numFramesPerPacket(%d) exceeds max frames per packet (%d) \n", arg->numFramesPerPacket, MAX_FRAMES_PER_RTP_PACKET ); + arg->numFramesPerPacket = 1; + } + } + } + fprintf( stdout, "Output format: RTPDump using %d frames/packet \n", arg->numFramesPerPacket ); } /*-----------------------------------------------------------------* @@ -2133,10 +2245,10 @@ static void usage_enc( void ) fprintf( stdout, "-q : Quiet mode, no frame counters\n" ); fprintf( stdout, " default is deactivated\n" ); #ifdef IVAS_RTPDUMP - fprintf( stdout, "-rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the \n" ); + fprintf( stdout, "-rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the \n" ); fprintf( stdout, " bitstream frames into TS26.253 Annex A IVAS RTP Payload Format packets and \n" ); fprintf( stdout, " writes those to the output file. In EVS mono operating mode, TS26.445 Annex A.2.2 \n" ); - fprintf( stdout, " EVS RTP Payload Format is used. \n" ); + fprintf( stdout, " EVS RTP Payload Format is used. Optional N represents number of frames per RTP packet\n" ); fprintf( stdout, "-scene_orientation : Scene orientation trajectory file. Only used with rtpdump output.\n" ); fprintf( stdout, "-device_orientation : Device orientation trajectory file. Only used with rtpdump output.\n" ); #endif diff --git a/lib_com/ivas_error.h b/lib_com/ivas_error.h index a2f893ed78..24b576d696 100644 --- a/lib_com/ivas_error.h +++ b/lib_com/ivas_error.h @@ -150,6 +150,15 @@ typedef enum IVAS_ERR_LC3PLUS_INVALID_BITRATE, IVAS_ERR_INVALID_SPLIT_REND_CONFIG, + /*----------------------------------------* + * rtp errors * + *----------------------------------------*/ + IVAS_ERR_UNDERFLOW = 0x7000, + IVAS_ERR_PI_DATA_WITH_NO_INPUT_FRAME, + IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, + IVAS_ERR_UNPACK_PI_DATA, + IVAS_ERR_RTP_UNSUPPORTED_FRAME, + /*----------------------------------------* * unknown error * *----------------------------------------*/ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index a476430478..f9c9646c9f 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3742,7 +3742,7 @@ ivas_error IVAS_DEC_Flush( ivas_error IVAS_DEC_feedSinglePIorientation( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ ) { @@ -3750,7 +3750,7 @@ ivas_error IVAS_DEC_feedSinglePIorientation( ivas_error error = IVAS_ERR_OK; IVAS_QUATERNION savedInvOrientation; - if ( *isOrientationSaved ) + if ( isOrientationSaved ) { if ( !hIvasDec->st_ivas->hExtOrientationData ) { @@ -3797,13 +3797,13 @@ ivas_error IVAS_DEC_feedPIdata( ivas_error error = IVAS_ERR_OK; /* scene orientation */ - if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } /* device orientation */ - if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) { return error; } diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 40a5403373..2e98d9e0f1 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -335,9 +335,9 @@ ivas_error IVAS_DEC_Flush( #ifdef IVAS_RTPDUMP ivas_error IVAS_DEC_feedSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ ); ivas_error IVAS_DEC_feedPIdata( diff --git a/lib_util/ivas_bpool.c b/lib_util/ivas_bpool.c new file mode 100644 index 0000000000..cea33509ff --- /dev/null +++ b/lib_util/ivas_bpool.c @@ -0,0 +1,154 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ +#include +#include +#include +#include "ivas_bpool.h" +#include "ivas_error_utils.h" +#include "mutex.h" + +struct BPOOL +{ + mtx_t lock; + uint32_t bufferSize; + uint32_t numBuffers; + uint32_t numFreeBuffers; + void **freeBuffers; +}; + +ivas_error BPOOL_Create( BPOOL_HANDLE *pHandle, size_t bufferSize, uint32_t numBuffers ) +{ + uint32_t n; + uint8_t *base = NULL; + BPOOL_HANDLE handle; + size_t allocSize = sizeof( struct BPOOL ); + + if ( pHandle == NULL ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid pointer to Buffer Pool Handle" ); + } + + *pHandle = NULL; + + allocSize += bufferSize * numBuffers; /* pool memory */ + allocSize += sizeof( void * ) * numBuffers; /* free buffers stack */ + + base = calloc( allocSize, sizeof( uint8_t ) ); + if ( base == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Couldn't allocate Buffer pool" ); + } + + handle = (BPOOL_HANDLE) base; + base += sizeof( struct BPOOL ); + + mtx_init( &handle->lock, 0 ); + handle->bufferSize = bufferSize; + handle->numBuffers = numBuffers; + handle->numFreeBuffers = numBuffers; + handle->freeBuffers = (void **) base; + base += ( sizeof( void * ) * numBuffers ); + for ( n = 0; n < numBuffers; n++ ) + { + handle->freeBuffers[n] = base; + base += bufferSize; + } + + *pHandle = handle; + return IVAS_ERR_OK; +} + +void BPOOL_Destroy( BPOOL_HANDLE *pHandle ) +{ + if ( pHandle != NULL ) + { + mtx_destroy( &( *pHandle )->lock ); + free( *pHandle ); + *pHandle = NULL; + } +} + +ivas_error BPOOL_GetBuffer( BPOOL_HANDLE handle, void **dataPtr ) +{ + uint32_t idx = 0; + bool isFree = false; + + if ( handle == NULL || dataPtr == NULL ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid pointer args in GetBuffer" ); + } + + mtx_lock( &handle->lock ); + isFree = ( handle->numFreeBuffers > 0 ); + if ( isFree ) + { + idx = --handle->numFreeBuffers; + } + mtx_unlock( &handle->lock ); + + if ( !isFree ) + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow, no free buffers in pool" ); + } + + *dataPtr = handle->freeBuffers[idx]; + return IVAS_ERR_OK; +} + +/* return the buffer back to pool */ +ivas_error BPOOL_FreeBuffer( BPOOL_HANDLE handle, void *dataPtr ) +{ + uint32_t idx; + + if ( handle == NULL || dataPtr == NULL ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid pointer args in GetBuffer" ); + } + + mtx_lock( &handle->lock ); + idx = handle->numFreeBuffers++; + mtx_unlock( &handle->lock ); + + handle->freeBuffers[idx] = dataPtr; + + return IVAS_ERR_OK; +} + +/* return the number of free buffers available atm in the pool */ +uint32_t BPOOL_AvailableBuffers( BPOOL_HANDLE handle ) +{ + uint32_t idx; + mtx_lock( &handle->lock ); + idx = handle->numFreeBuffers; + mtx_unlock( &handle->lock ); + return idx; +} diff --git a/lib_util/ivas_bpool.h b/lib_util/ivas_bpool.h new file mode 100644 index 0000000000..c4cd45a736 --- /dev/null +++ b/lib_util/ivas_bpool.h @@ -0,0 +1,58 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_BPOOL_H +#define IVAS_BPOOL_H + +#include +#include +#include "ivas_error.h" + +/* Forward declaraiton of opaque buffer pool handle */ +typedef struct BPOOL *BPOOL_HANDLE; + +/* Create a buffer pool with given element size and max number of buffers */ +ivas_error BPOOL_Create(BPOOL_HANDLE *pHandle, size_t bufferSize, uint32_t numBuffers); + +/* Destroy the buffer pool and all free-up all allocated memory */ +void BPOOL_Destroy( BPOOL_HANDLE *pHandle ); + +/* request a buffer from the pool */ +ivas_error BPOOL_GetBuffer(BPOOL_HANDLE handle, void **dataPtr ); + +/* return the buffer back to pool */ +ivas_error BPOOL_FreeBuffer(BPOOL_HANDLE handle, void *dataPtr); + +/* return the number of free buffers available atm in the pool */ +uint32_t BPOOL_AvailableBuffers(BPOOL_HANDLE handle); + +#endif /* IVAS_BPOOL_H */ diff --git a/lib_util/ivas_queue.c b/lib_util/ivas_queue.c new file mode 100644 index 0000000000..db038938a5 --- /dev/null +++ b/lib_util/ivas_queue.c @@ -0,0 +1,126 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include +#include +#include "ivas_queue.h" +#include "ivas_error_utils.h" +#include "mutex.h" + +struct QUEUE +{ + mtx_t lock; + NODE *front; + NODE *back; + uint32_t size; +}; + +ivas_error QUEUE_Create( QUEUE_HANDLE *pHandle ) +{ + QUEUE_HANDLE handle = NULL; + *pHandle = NULL; + + if ( pHandle == NULL ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid pointer to Buffer Pool Handle" ); + } + + handle = calloc( 1, sizeof( struct QUEUE ) ); + if ( handle != NULL ) + { + mtx_init( &handle->lock, 0 ); + } + + *pHandle = handle; + return IVAS_ERR_OK; +} + +/* Destroy the queue and all free-up all allocated memory */ +void QUEUE_Destroy( QUEUE_HANDLE *pHandle ) +{ + if ( pHandle != NULL ) + { + mtx_destroy( &( *pHandle )->lock ); + free( *pHandle ); + *pHandle = NULL; + } +} + +void QUEUE_Push( QUEUE_HANDLE handle, NODE *node ) +{ + mtx_lock( &handle->lock ); + if ( handle->back == NULL ) + { + handle->front = node; + } + else + { + handle->back->next = node; + } + handle->back = node; + handle->size++; + mtx_unlock( &handle->lock ); +} + +/* return the buffer back to pool */ +NODE * QUEUE_Pop( QUEUE_HANDLE handle ) +{ + NODE * node; + mtx_lock( &handle->lock ); + node = handle->front; + handle->front = handle->front->next; + if ( NULL == handle->front ) + { + handle->back = NULL; + } + handle->size--; + mtx_unlock( &handle->lock ); + return node; +} + +/* returns the first element in the queue */ +NODE *QUEUE_Front( QUEUE_HANDLE handle ) +{ + return handle->front; +} + +/* returns the last element in the queue */ +NODE *QUEUE_Back( QUEUE_HANDLE handle ) +{ + return handle->back; +} + +/* return the number of elements in the queue */ +size_t QUEUE_Size( QUEUE_HANDLE handle ) +{ + return handle->size; +} diff --git a/lib_util/ivas_queue.h b/lib_util/ivas_queue.h new file mode 100644 index 0000000000..8a5d4d1017 --- /dev/null +++ b/lib_util/ivas_queue.h @@ -0,0 +1,68 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_QUEUE_H +#define IVAS_QUEUE_H + +#include +#include "ivas_error.h" + +typedef struct NODE +{ + struct NODE *next; +} NODE; + +/* Forward declaraiton of opaque queue handle */ +typedef struct QUEUE *QUEUE_HANDLE; + +/* Create a queue with given element size and max number of buffers */ +ivas_error QUEUE_Create( QUEUE_HANDLE *pHandle ); + +/* Destroy the queue and all free-up all allocated memory */ +void QUEUE_Destroy( QUEUE_HANDLE *pHandle ); + +/* push a buffer to a queue */ +void QUEUE_Push( QUEUE_HANDLE handle, NODE *data ); + +/* pop the buffer from the front */ +NODE *QUEUE_Pop( QUEUE_HANDLE handle ); + +/* returns the first element from the front */ +NODE *QUEUE_Front( QUEUE_HANDLE handle ); + +/* returns the last element from the back */ +NODE *QUEUE_Back( QUEUE_HANDLE handle ); + +/* return the number of elements in the queue */ +size_t QUEUE_Size( QUEUE_HANDLE handle ); + +#endif /* IVAS_QUEUE_H */ diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 5b008a04ca..6b8ebb470a 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -45,37 +45,46 @@ extern "C" { #endif -/* - * +-----------------------+---------------------+--------------------+----------+ - * | RTP Header (+ HDREXT) | payload header | frame data | PI data | - * +-----------------------+---------------------+--------------------+----------+ - * \--------------------\ /------------------------------/ - * IVAS payload - * - * This api provides a mechanism to generate/unpack the IVAS payload. The RTP Header - * and header extension fields must be handled by caller. - * - * IVAS General Payload structure - * =============================== - * - * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 - * H H H H F H - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |1| T | D |1| ET1 |x x x x|1| ET2 |x x x x|0|1|0 1| BR |1| ET3 |x x x x|… - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * \--------------/\--------------/\--------------/\--------------/\--------------/ - * Initial E byte Subsqnt Ebyte1 Subsqnt Ebyte2 ToC1 Subsqnt Ebyte3 - * - * H F - * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ - * …|0|0|0 1| BR | IVAS frame 1 ... | IVAS frame 2 ... |PI data| - * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ - * \--------------/ - * ToC2 - * + /* + * +-----------------------+---------------------+--------------------+----------+ + * | RTP Header (+ HDREXT) | payload header | frame data | PI data | + * +-----------------------+---------------------+--------------------+----------+ + * \--------------------\ /------------------------------/ + * IVAS payload + * + * This api provides a mechanism to generate/unpack the IVAS payload. The RTP Header + * and header extension fields must be handled by caller. + * + * IVAS General Payload structure + * =============================== + * + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * H H H H F H + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1| T | D |1| ET1 |x x x x|1| ET2 |x x x x|0|1|0 1| BR |1| ET3 |x x x x|… + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \--------------/\--------------/\--------------/\--------------/\--------------/ + * Initial E byte Subsqnt Ebyte1 Subsqnt Ebyte2 ToC1 Subsqnt Ebyte3 + * + * H F + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * …|0|0|0 1| BR | IVAS frame 1 ... | IVAS frame 2 ... |PI data| + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * \--------------/ + * ToC2 + * + */ + +#define MAX_FRAMES_PER_RTP_PACKET ( 8 ) /* Max supported frames per RTP packet */ + +/* It is difficult to decide the RTP Payload buffer's capacity intrinsically however computing + * using the maximum frame size and all currently supported PI data present gives a crude + * estimate or RTP packet size per frame. The additional PI data is assumed to add upto a 20% + * overhead in bitrate for the computation. */ +#define IVAS_NOMINAL_RTP_BYTES_PER_FRAME ( ( IVAS_MAX_BITS_PER_FRAME + ( IVAS_MAX_BITS_PER_FRAME / 5 ) ) / 8 ) -#define NO_BITRATE_REQ ( 0u ) /* If no bitrate is requested from remote */ +#define NO_BITRATE_REQ ( 0u ) /* If no bitrate is requested from remote */ /* IVAS Codec Types */ typedef enum @@ -193,41 +202,44 @@ extern "C" } IVAS_RTP_SUBFORMAT; /* Split Rendering Requests */ - typedef struct { - uint32_t diegetic : 1; /* enabling diegetic support for Split Rendering */ - uint32_t yaw : 1; /* transmission metadata for correction around yaw axis */ - uint32_t pitch : 1; /* transmission metadata for correction around pitch axis */ - uint32_t roll: 1; /* transmission metadata for correction around roll axis */ - uint32_t reserved: 28; /* reserved */ + typedef struct + { + uint32_t valid : 1; /* is split rendering request valid */ + uint32_t diegetic : 1; /* enabling diegetic support for Split Rendering */ + uint32_t yaw : 1; /* transmission metadata for correction around yaw axis */ + uint32_t pitch : 1; /* transmission metadata for correction around pitch axis */ + uint32_t roll : 1; /* transmission metadata for correction around roll axis */ + uint32_t reserved : 27; /* reserved */ } IVAS_RTP_SPLITRENDER; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ /* Remote Requests Types (Keys) */ typedef enum { - IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODEC */ - IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ - IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ - IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ - IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ + IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODEC */ + IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ + IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ + IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ + IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ - IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_REQUEST_MAX /* Max number of requests */ + IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ + IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_REQUEST_MAX /* Max number of requests */ } IVAS_RTP_REQUEST_TYPE; /* Remote Request Values */ - typedef union { - uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ - IVAS_RTP_CODEC codec; /* codec id when request type is IVAS_REQUEST_CODEC */ - IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ - IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ - IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ + typedef union + { + uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ + IVAS_RTP_CODEC codec; /* codec id when request type is IVAS_REQUEST_CODEC */ + IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ + IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ + IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ #ifdef RTP_S4_251135_CR26253_0016_REV1 IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ IVAS_RTP_SPLITRENDER srConfig; /* split rendering config when request type is IVAS_REQUEST_SR_CONFIG */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } IVAS_RTP_REQUEST_VALUE; /* Template for pi data types, all defined pi data follow this template @@ -258,12 +270,27 @@ extern "C" uint8_t *buffer; /* pointer to the payload buffer */ } IVAS_DATA_BUFFER; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + typedef enum + { + IVAS_SR_TRANSPORT_LCLD, + IVAS_SR_TRANSPORT_LC3PLUS + } IVAS_RTP_SR_TRANSPORT; + + typedef struct + { + bool valid; /* Valid Split Rendering Info for/in the ToC */ + bool diegetic; /* SR content digetic */ + IVAS_RTP_SR_TRANSPORT codec; /* SR Transport Codec used*/ + } IVAS_RTP_SR_INFO; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + /**********************************************/ /* IVAS RTP PACKER API */ /**********************************************/ /* Forward declaration of rtp pack/unpack handle types */ - typedef struct IVAS_RTP_PACK *IVAS_RTP_PACK_HANDLE; /* rtp packer handle type */ + typedef struct IVAS_RTP_PACK *IVAS_RTP_PACK_HANDLE; /* rtp packer handle type */ /* Initial configuration for rtp packer * - maxFramesPerPacket is used to define if more than one frame should be packed @@ -271,9 +298,20 @@ extern "C" */ typedef struct { - uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ + uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ } IVAS_RTP_PACK_CONFIG; + /* Update the RTP Header structure */ + ivas_error IVAS_RTP_PACK_UpdateHeader( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ + uint8_t numCC, /* i : numCC indicates no. of contributing sources */ + uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ + uint16_t extHeaderId, /* i : extension header ID */ + uint16_t numExtensionBytes, /* i : length of the extension data */ + uint8_t *extData /* i : extension data pointer */ + ); + /* Open an instance of the RTP packer and return a handle to rtp packer on success * error code is retured on failure and handle is set to NULL */ @@ -310,11 +348,19 @@ extern "C" * */ ivas_error IVAS_RTP_PACK_PushFrame( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info (NULL if absent) */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ ); + /* Get the number of frames in the FiFo currently */ + uint32_t IVAS_RTP_PACK_GetNumFrames( + IVAS_RTP_PACK_HANDLE hIvasPack /* i/o : IVAS rtp packer handle */ + ); + /* Push single PI data to rtp packer * * Provide PI data for a current RTP packet. All PI data is locally cached in the packer @@ -334,10 +380,23 @@ extern "C" * */ ivas_error IVAS_RTP_PACK_GetPayload( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_DATA_BUFFER *payload /* o : encapsulated rtp payload */ + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *payload, /* o : encapsulated rtp payload */ + uint32_t *numFramesInPayload /* o : no. of frames in payload */ ); + /* Generate a rtp packet using available pushed frames + * + * Available remote requests, pi data and frames will be packed into a rtp packet. If no + * frame is pushed before call to this api, NO_DATA_FRAME will be generated + * Takes care of updates to the RTP Header + * + */ + ivas_error IVAS_RTP_PACK_GetPacket( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ + uint32_t *numFramesInPacket /* o : no. of frames in packet */ + ); /**********************************************/ /* IVAS RTP UNPACKER API */ @@ -381,7 +440,7 @@ extern "C" * * // Read the frames in payload and feed to decoder * while (nFrames-- > 0) { - * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &frame, &frameTs, &seqNum); + * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); * if (err != IVAS_ERR_OK) { return err; } * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); * if (err != IVAS_ERR_OK) { return err; } @@ -415,14 +474,69 @@ extern "C" */ ivas_error IVAS_RTP_UNPACK_PushPayload( IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ - uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ - uint16_t sequenceNumber, /* i : sequence number from rtp header */ + const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ + uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ + uint16_t sequenceNumber, /* i : sequence number from rtp header */ uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ ); + /* Push a received rtp Ivas Packet to unpacker to extract number of frames, pi data and + * any remote request present in the Packet. Caller must extract RTP header and header + * extension and feed Ivas Packet alongwith RTP Timestamp and sequence number. + * + * In case of DTX transmission modes, the number of frames in packet will be reduced by + * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall + * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. + * + * Example usage : - + * ================== + * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, rtpTs, seqNum, + * &nFrames, &nPiData, &reqBitmap); + * if (err != IVAS_ERR_OK) { return err; } + * + * // Read the frames in packet and feed to decoder + * while (nFrames-- > 0) { + * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); + * if (err != IVAS_ERR_OK) { return err; } + * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); + * if (err != IVAS_ERR_OK) { return err; } + * } + * + * // Read PI Data + * while (nPiData-- > 0) { + * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); + * if (err != IVAS_ERR_OK) { return err; } + * // handle pi data based on fwd/rev pi data types + * handlePIData(&piData, piTs) + * } + * + * // Read remote requests + * for (req = 0; req < IVAS_REQUEST_MAX; req++) { + * if (reqBitmap & (1u << req)) { + * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); + * if (err != IVAS_ERR_OK) { return err; } + * switch(req) { + * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; + * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; + * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; + * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; + * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; + * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; + * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; + * } + * } + * } + */ + ivas_error IVAS_RTP_UNPACK_PushPacket( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *packet, /* i : received rtp Packet */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ + ); + /* Fetch requests from sender using a key value pair api * each key must be provided with a corresponding value storage type * @@ -444,19 +558,24 @@ extern "C" */ ivas_error IVAS_RTP_UNPACK_PullFrame( IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - IVAS_RTP_CODEC *receivedCodecId, /* i : Codec type (IVAS/EVS) */ + IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ - uint16_t *sequenceNumber /* o : sequence number from rtp header */ + uint16_t *sequenceNumber, /* o : sequence number from rtp header */ + bool *speechLostIndicated /* o : Is current frame indicated as Lost */ ); /* Pull a single PI data from rtp unpacker instance for current packet * Each Pi data is accompanied with a corresponding timestamp */ ivas_error IVAS_RTP_UNPACK_PullNextPiData( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ - uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ + size_t capacity, /* i : capacity of pi data buffer in bytes */ + uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ ); #ifdef __cplusplus diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h new file mode 100644 index 0000000000..b74bdbe8d5 --- /dev/null +++ b/lib_util/ivas_rtp_internal.h @@ -0,0 +1,136 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_RTP_INTERNAL_H +#define IVAS_RTP_INTERNAL_H + +#include "ivas_rtp_api.h" +#include "ivas_rtp_pi_data.h" + +#ifdef IVAS_RTPDUMP + +enum MASK_BITS +{ + MASK_1BIT = 0x1, + MASK_2BIT = 0x3, + MASK_3BIT = 0x7, + MASK_4BIT = 0xF, + MASK_5BIT = 0x1F, + MASK_6BIT = 0x3F, + MASK_7BIT = 0x7F, + MASK_8BIT = 0xFF, + +}; + +/* tables */ +#define NBITS_AEID ( 7 ) +#define NBITS_RT60 ( 5 ) +#define NBITS_DSR ( 6 ) +#define NBITS_DIM ( 4 ) +#define NBITS_ABS ( 2 ) + +#define MASK_AEID ( ( 1u << NBITS_AEID ) - 1 ) +#define MASK_RT60 ( ( 1u << NBITS_RT60 ) - 1 ) +#define MASK_DSR ( ( 1u << NBITS_DSR ) - 1 ) +#define MASK_DIM ( ( 1u << NBITS_DIM ) - 1 ) +#define MASK_ABS ( ( 1u << NBITS_ABS ) - 1 ) + +extern const float mapDSR[1u << NBITS_DSR]; +extern const float mapRT60[1u << NBITS_RT60]; +extern const float mapRoomDims[1u << NBITS_DIM]; +extern const float mapAbsorbtion[1u << NBITS_ABS]; + +enum IVAS_RTP_HEADER_BITS +{ + + EBYTE_TOC_HEADER_BIT = 0x80, /* Ebyte/ToC indicator H bit, 1 = EByte, 0 = ToC */ + + TOC_HEADER_FOLLOWS = 0x40, /* ToC header byte is followed by another header byte */ + TOC_INDICATE_NO_DATA = 0x0F, /* ToC header byte indicates NO_DATA in DTX mode */ + TOC_INDICATE_IVAS = 0x10, /* ToC header byte indicates IVAS data */ + TOC_INDICATE_EVS = 0x00, /* ToC header byte indicates EVS data */ + TOC_INDICATE_AMRWB = 0x30, /* ToC header byte indicates AMR-WB IO data */ + TOC_INDICATE_ARMWB_Q = 0x20, /* ToC header byte indicates AMR-WB IO lost frame */ + TOC_INDICATE_SR = 0x1E, /* Indicate Split Rendering in ToC bytes */ + + EBYTE_CMR_T_EVS_NB = 0x80, /* Initial E-byte indicating EVS NarrowBand modes */ + EBYTE_CMR_T_EVS_IO = 0x90, /* Initial E-byte indicating EVS AMRWB IO modes */ + EBYTE_CMR_T_EVS_WB = 0xA0, /* Initial E-byte indicating EVS WideBand modes */ + EBYTE_CMR_T_EVS_SWB = 0xB0, /* Initial E-byte indicating EVS SuperWideBand modes */ + EBYTE_CMR_T_EVS_FB = 0xC0, /* Initial E-byte indicating EVS FullBand modes */ + EBYTE_CMR_T_EVS_CA_WB = 0xD0, /* Initial E-byte indicating EVS CA WideBand modes */ + EBYTE_CMR_T_EVS_CA_SWB = 0xE0, /* Initial E-byte indicating EVS CA SuperWideBand modes */ + EBYTE_CMR_T_IVAS = 0xF0, /* Initial E-byte indicating IVAS modes */ + EBYTE_CMR_T_NO_REQ = 0xFF, /* If no bitrate and no CA mode requested for IVAS/EVS */ + + EBYTE_BANDWIDTH_REQUEST = 0x80, /* Subsequent E-byte for Bandwidth Request */ + EBYTE_FORMAT_REQUEST = 0x90, /* Subsequent E-byte for Format Request */ + EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ + EBYTE_PI_INDICATOR = 0xA0, /* Subsequent E-byte for PI Indicator */ + EBYTE_SR_REQUEST = 0xB0, /* Subsequent E-byte for Split Rendering Request */ + + PI_HEADER_PF_LAST = 0x00, /* Last PI header of the Payload in PI Header */ + PI_HEADER_PF_NOT_LAST = 0x80, /* Another PI header follows after this in PI Header */ + PI_HEADER_PM_NOT_LAST = 0x20, /* PI Frame Marker, not the last PI Header in current frame */ + PI_HEADER_PM_LAST = 0x40, /* PI Frame Marker, the last PI Header in current frame */ + PI_HEADER_PM_GENERIC = 0x60, /* PI Frame Marker, Generic applied to all frames */ + + PI_AD_SPEECH_INDICATED = 0x80, /* Audio Description indicate Speech in Audio data */ + PI_AD_MUSIC_INDICATED = 0x40, /* Audio Description indicate Misuc in Audio data */ + PI_AD_AMBIANCE_INDICATED = 0x20, /* Audio Description indicate Ambiance in Audio data */ + PI_AD_EDITABLE_INDICATED = 0x10, /* Audio Description indicate metadata is editable */ + PI_AD_BINAURAL_INDICATED = 0x8, /* Audio Description indicate Stereo stream in Binaural */ + +}; + + +#define ERR_CHECK_RETURN( err ) \ + { \ + if ( ( err ) != IVAS_ERR_OK ) \ + { \ + return ( err ); \ + } \ + } + + +typedef struct +{ + uint32_t size; + uint8_t data[IVAS_PI_MAX_DATA_SIZE]; +} PIDATA_PACKED; + +ivas_error PI_PackData( const IVAS_PIDATA_GENERIC *piData, PIDATA_PACKED *packed, uint8_t pmBits ); +ivas_error PI_UnPackData( uint8_t piDataType, uint32_t piSize, const uint8_t *piDataBuffer, IVAS_PIDATA_GENERIC *piData ); + +#endif /* IVAS_RTPDUMP */ + +#endif /* IVAS_RTP_INTERNAL_H */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index d952450b52..7bbc86171d 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -29,1068 +29,1767 @@ the United Nations Convention on Contracts on the International Sales of Goods. *******************************************************************************************************/ - -#include #include +#include #include -#include "evs_rtp_payload.h" -#include "ivas_rtp_payload.h" -#include "ivas_prot.h" -#include "prot.h" +#include +#include "common_api_types.h" #ifdef IVAS_RTPDUMP +#include "ivas_rtp_api.h" +#include "ivas_rtp_pi_data.h" +#include "ivas_rtp_internal.h" +#include "ivas_bpool.h" +#include "ivas_queue.h" +#include "ivas_error_utils.h" +#include "mutex.h" + +#define IVAS_MAX_FRAMES_IN_QUEUE ( 32 ) +#define IVAS_MAX_BYTES_PER_FRAME ( ( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3 ) +#define MAX_TOC_PER_FRAME ( 2u ) /* Main ToC Byte + SR-ToC byte */ + +/* Heuristically defined max values for no of frames per packet and no of pi data per packet */ +#define MAX_SUPPORTED_PI_DATA_SIZE ( 32 ) /* Currently no PI data exceeds 32 bytes */ +#define MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET ( 25u ) +#define MAX_SUPPORTED_PI_FRAMES_PER_PACKET ( 150u ) + +/* Generic RTP Header + Header Extension data structure */ +typedef struct +{ + uint32_t ssrc; /* synchronization source id as negotiated in SDP */ + uint8_t version; /* version of RTP protocol, currently 2 */ + bool padding; /* false = no padding, true = zeoo padded payload (last byte = padding count) */ + bool extension; /* true = extension header is present before payload */ + uint8_t CC; /* if some streams mixed together, CC indicates no. of contributing sources (4 bits) */ + bool marker; /* Marker bit field */ + uint8_t payloadType; /* 7-bit payload type field indicating IVAS */ + uint16_t seqNumber; /* 16-bit sequence number for RTP packets */ + uint32_t timestamp; /* timer ticks @ 16KHz clock corrsponding to 1st frame in the Payload */ + uint32_t CSRC[15]; /* SSRC of contributing Sources for a mixed stream */ + uint16_t extHeaderId; /* extension header ID */ + uint16_t numExtUnits; /* length of the extension data in 32-bit words */ + uint8_t *extData; /* Extension data pointer */ +} RTP_HEADER; + +typedef struct +{ + PIDATA_PACKED piData[IVAS_PI_MAX_ID]; /* pi data per frame */ + uint32_t piDataBitmap; /* bitmap showing available pi */ + uint32_t numPiDataAvailable; /* number of pi data available */ +} PIDATA_FRAME; -/*-----------------------------------------------------------------------* - * writeByteToRtp() - * - * Write a single byte (value) to RTP packet. - *-----------------------------------------------------------------------*/ - -static void writeByteToRtp( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - char value /* i : Value to be written to the RTP packet */ -) +static void initRequests( IVAS_RTP_REQUEST_VALUE *requests ) { - self->rtpPacket.data[self->writingIndex] = value; - ++self->writingIndex; - self->rtpPacket.payloadSize += 1; + requests[IVAS_REQUEST_CODEC].codec = IVAS_RTP_IVAS; + requests[IVAS_REQUEST_BITRATE].bitrate = 0; + requests[IVAS_REQUEST_BANDWIDTH].bandwidth = IVAS_BANDWIDTH_NO_REQ; + requests[IVAS_REQUEST_FORMAT].formatType = IVAS_FMT_NO_REQ; + requests[IVAS_REQUEST_CA_MODE].caMode = IVAS_RTP_CA_NO_REQ; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + requests[IVAS_REQUEST_SUBFORMAT].subFormatType = IVAS_SUBFMT_NO_REQ; + requests[IVAS_REQUEST_SR_CONFIG].srConfig.valid = false; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } - -/*-----------------------------------------------------------------------* - * determineBitrate() - * - * Determine bitrate based on the number of encoded bits. - *-----------------------------------------------------------------------*/ - -static void determineBitrate( - const int32_t numBits, /* i : Number of bits contained in the data frame */ - int32_t *frameBitrate /* o : Bitrate of the data frame */ -) +static void initPiDataFrame( PIDATA_FRAME *piDataFrm ) { - *frameBitrate = numBits * IVAS_NUM_FRAMES_PER_SEC; + /* Add NO_PI_DATA by default */ + piDataFrm->numPiDataAvailable = 1; + piDataFrm->piDataBitmap = ( 1u << IVAS_PI_NO_DATA ); + piDataFrm->piData[IVAS_PI_NO_DATA].size = 2; + piDataFrm->piData[IVAS_PI_NO_DATA].data[0] = ( IVAS_PI_NO_DATA ); /* PF/PM populated during final packing */ + piDataFrm->piData[IVAS_PI_NO_DATA].data[1] = 0; /* NO_PI_DATA is 0 bytes */ } +typedef struct FRAME_NODE +{ + struct FRAME_NODE *next; /* next node */ + PIDATA_FRAME piDataFrame; /* PI data for this frame */ + uint8_t au[IVAS_MAX_BYTES_PER_FRAME]; /* ivas/evs packed frame (AU) */ + uint8_t toc[MAX_TOC_PER_FRAME]; /* ToC bytes for this frame */ + uint32_t auNumBits; /* Actual frame size in bits */ + uint32_t tocNumBytes; /* valid ToC bytes (1 or 2) */ +} FRAME_NODE; + +struct IVAS_RTP_PACK +{ + mtx_t lock; /* Lock to handle concurrent API invocation */ + RTP_HEADER header; + BPOOL_HANDLE nodePool; + IVAS_RTP_PACK_CONFIG initConfig; + IVAS_RTP_REQUEST_VALUE requests[IVAS_REQUEST_MAX]; + QUEUE_HANDLE frameQ; /* frames queue */ +}; + +typedef struct +{ + IVAS_RTP_CODEC codecId; /* Coded frame type (IVAS/EVS) */ + uint32_t auNumBits; /* Actual frame size in bits */ + bool speechLostIndicated; /* Speech Lost indicated */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO srInfo; /* Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ +} TOC_INFO; + +typedef struct PIDATA_NODE +{ + struct PIDATA_NODE *next; /* next node is first element */ + PIDATA data; /* unpacked pi data per frame */ + uint32_t timestamp; /* rtp timestamp of this frame */ +} PIDATA_NODE; -/*-----------------------------------------------------------------------* - * ivasPayload_parseToc() - * - * Parse a single ToC byte for an IVAS payload. - *-----------------------------------------------------------------------*/ - -static void ivasPayload_parseToc( - uint8_t toc, /* i : ToC byte to be parsed */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ - int32_t *bitrate /* o : Bitrate of the data frame indicated in the ToC byte */ +typedef struct UNPACK_NODE +{ + struct UNPACK_NODE *next; /* next node is first element */ + uint8_t au[IVAS_MAX_BYTES_PER_FRAME]; /* ivas/evs packed frame (AU) */ + TOC_INFO toc; /* unpacked ToC information */ + uint32_t timestamp; /* timestamp of this frame */ + uint16_t seqNumber; /* sequence number of the packet */ +} UNPACK_NODE; + +struct IVAS_RTP_UNPACK +{ + mtx_t lock; /* Lock to handle concurrent API invocation */ + RTP_HEADER header; + BPOOL_HANDLE nodePool; + BPOOL_HANDLE piDataPool; + IVAS_RTP_UNPACK_CONFIG initConfig; + IVAS_RTP_REQUEST_VALUE requests[IVAS_REQUEST_MAX]; + uint32_t numPiDataAvailable; /* number of pi data available */ + QUEUE_HANDLE frameQ; /* frame queue */ + QUEUE_HANDLE piDataQ; /* pi data queue */ +}; + +static const uint32_t ivasFrameSizeInBits[] = { + 264, 328, 488, 640, 960, 1280, 1600, 1920, 2560, 3200, 3840, 5120, 7680, 10240, 0, 104 +}; + +static const uint32_t evsFrameSizeInBits[] = { + 56, 144, 160, 192, 264, 328, 488, 640, 960, 1280, 1920, 2560, 48 +}; + +static const uint32_t amrWBIOFrameSizeInBits[] = { + 132, 177, 253, 285, 317, 365, 397, 461, 477, 40 +}; + + +/* Update the RTP Header structure */ +ivas_error IVAS_RTP_PACK_UpdateHeader( + IVAS_RTP_PACK_HANDLE hPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ + uint8_t numCC, /* i : numCC indicates no. of contributing sources */ + uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ + uint16_t extHeaderId, /* i : extension header ID */ + uint16_t numExtensionBytes, /* i : length of the extension data */ + uint8_t *extData /* i : extension data pointer */ ) { - bool isIvasToc = ( toc & 0x30 ) == 0x10; // xx01 xxxx - if ( !isIvasToc ) - { - evsHeaderFullPayload_parseToc( toc, evsIvasModeBit, frameFollowing, bitrateIndex, ivasIndicatorBit, isGoodFrame, bitrate ); - } - else + RTP_HEADER *header = &hPack->header; + + if ( numCC > 15 ) { - *evsIvasModeBit = false; - *ivasIndicatorBit = true; - *isGoodFrame = true; /* assume good frame for IVAS */ - *frameFollowing = ( toc & 0x40 ) != 0; - *bitrateIndex = toc & 0x0f; - *bitrate = IVASmode2rate[*bitrateIndex]; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "CC must be less than 16" ); } -} + header->ssrc = ssrc; + header->CC = numCC; + memcpy( header->CSRC, csrc, numCC & 0xF ); -/*-----------------------------------------------------------------------* - * ivasPayload_parsePItype() - * - * Parse a single PI type and set the PIdataCurrentFrame handle accordingly. - *-----------------------------------------------------------------------*/ - -static void ivasPayload_parsePItype( - char *PIframeDataBeginning, /* i : Pointer to the beginning of the PI data section in the RTP packet */ - uint16_t PIframeSizeBytes, /* i : PI data frame size in bytes */ - uint16_t parsedPIframeSizeBytes, /* i : Size of previously parsed PI frame data section in bytes */ - uint16_t PIframeType, /* i : Type of the PI data frame */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ -) -{ - if ( PIframeType == PI_SCENE_ORIENTATION ) - { - PIdataCurrentFrame->sceneOrientation = PIframeDataBeginning + parsedPIframeSizeBytes; - } - else if ( PIframeType == PI_DEVICE_ORIENTATION_COMPENSATED ) - { - PIdataCurrentFrame->deviceOrientationCompensated = PIframeDataBeginning + parsedPIframeSizeBytes; - } - else if ( PIframeType == PI_DEVICE_ORIENTATION_UNCOMPENSATED ) - { - PIdataCurrentFrame->deviceOrientationUncompensated = PIframeDataBeginning + parsedPIframeSizeBytes; - } - else if ( PIframeType == PI_ACOUSTIC_ENVIRONMENT ) + if ( ( numExtensionBytes > 0 ) && ( extData != NULL ) ) { - if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES ) - { - PIdataCurrentFrame->acousticEnvironmentId = PIframeDataBeginning + parsedPIframeSizeBytes; - } - else if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_ONLY_LATE_REVERB_SIZE_BYTES ) - { - PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = PIframeDataBeginning + parsedPIframeSizeBytes; - } - else if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_LATE_REVERB_AND_EARLY_REFLECTIONS_SIZE_BYTES ) + header->extHeaderId = extHeaderId; + header->extData = realloc( header->extData, numExtensionBytes ); + if ( header->extData == NULL ) { - PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = PIframeDataBeginning + parsedPIframeSizeBytes; + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "failed to allocate extdata" ); } - } - else if ( PIframeType == PI_NO_DATA ) - { - /* no data */ + memcpy( header->extData, extData, numExtensionBytes ); + header->extension = true; + header->numExtUnits = numExtensionBytes / sizeof( uint32_t ); } else { - /* reserved */ + header->numExtUnits = 0; + header->extension = false; } -} - - -/*-----------------------------------------------------------------------* - * ivasPayload_PIaddHeaderBytes() - * - * Add PI header section bytes to the PI data pointers for the current audio frame. - *-----------------------------------------------------------------------*/ -static void ivasPayload_PIaddHeaderBytes( - uint16_t PIheaderSizeBytes, /* i : Size of the PI header section in bytes */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame /* o : PI data handle for the current audio frame */ -) -{ - if ( PIdataCurrentFrame->sceneOrientation != NULL ) - { - PIdataCurrentFrame->sceneOrientation += PIheaderSizeBytes; - } - if ( PIdataCurrentFrame->deviceOrientationCompensated != NULL ) - { - PIdataCurrentFrame->deviceOrientationCompensated += PIheaderSizeBytes; - } - if ( PIdataCurrentFrame->deviceOrientationUncompensated != NULL ) - { - PIdataCurrentFrame->deviceOrientationUncompensated += PIheaderSizeBytes; - } - if ( PIdataCurrentFrame->acousticEnvironmentId != NULL ) - { - PIdataCurrentFrame->acousticEnvironmentId += PIheaderSizeBytes; - } - if ( PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb != NULL ) - { - PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb += PIheaderSizeBytes; - } - if ( PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections != NULL ) - { - PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections += PIheaderSizeBytes; - } + return IVAS_ERR_OK; } - -/*-----------------------------------------------------------------------* - * ivasPayload_convertFromQ15() - * - * Convert a Q15 encoded value to a float value. - *-----------------------------------------------------------------------*/ - -static float ivasPayload_convertFromQ15( uint16_t Q15Value ) +static void InitRtpHeader( + RTP_HEADER *header /* RTP header structure */ +) { - float temp; - temp = (float) ( Q15Value & 0x7FFF ) / 0x7FFF; - return ( Q15Value & 0x8000 ) ? -temp : temp; + time_t t; + memset( header, 0, sizeof( *header ) ); + srand( (uint32_t) time( &t ) ); + header->version = 2; + header->seqNumber = rand() & 0xFFFF; } - -/*-----------------------------------------------------------------------* - * ivasPayload_convertToQ15() - * - * Convert a float value into a Q15 encoded value. - *-----------------------------------------------------------------------*/ - -static uint16_t ivasPayload_convertToQ15( float value ) +static ivas_error PackRtpHeader( + RTP_HEADER *header, + IVAS_DATA_BUFFER *buf ) { - uint16_t temp; - temp = (uint16_t) ( fabsf( value ) * 0x7FFF ); - if ( value >= 0.0f ) + uint32_t nBytes = 0; + uint8_t CC = header->CC & MASK_4BIT; + uint32_t *CSRC = header->CSRC; + bool extension = header->extension && ( NULL != header->extData ); + uint32_t extensionLength = header->extension ? ( 1 + header->numExtUnits ) : 0; /* in u32 words */ + uint32_t packedLength = 12 + ( 4 * CC ) + ( 4 * extensionLength ); + + buf->length = 0; + if ( packedLength > buf->capacity ) { - temp = temp & 0x7FFF; + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient output buffer for RTP header packing" ); } - else + + /* + +---+---+---+---+---+---+---+---+ + | V | V | P | X | CC (4 - bits) | + +---+---+---+---+---+---+---+---+ + */ + buf->buffer[nBytes++] = ( header->version << 6 ) | + ( (uint8_t) header->padding << 5 ) | + ( (uint8_t) extension << 4 ) | + CC; + /* + +---+---+---+---+---+---+---+---+ + | M | PT (7 - bits) | + +---+---+---+---+---+---+---+---+ + */ + buf->buffer[nBytes++] = ( (uint8_t) header->marker << 7 ) | + ( header->payloadType & MASK_7BIT ); + /* + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | SEQUENCE NUMBER (16 bit) | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + buf->buffer[nBytes++] = (uint8_t) ( header->seqNumber >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( header->seqNumber ); + + /* Timestamp (32-bit) */ + buf->buffer[nBytes++] = (uint8_t) ( header->timestamp >> 24 ); + buf->buffer[nBytes++] = (uint8_t) ( header->timestamp >> 16 ); + buf->buffer[nBytes++] = (uint8_t) ( header->timestamp >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( header->timestamp >> 0 ); + + /* SSRC (32-bit) */ + buf->buffer[nBytes++] = (uint8_t) ( header->ssrc >> 24 ); + buf->buffer[nBytes++] = (uint8_t) ( header->ssrc >> 16 ); + buf->buffer[nBytes++] = (uint8_t) ( header->ssrc >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( header->ssrc >> 0 ); + + /* CSRC Identifiers */ + while ( CC ) { - temp = temp | 0x8000; + /* CSRC[n] (32-bit) */ + uint32_t id = *CSRC++; + buf->buffer[nBytes++] = (uint8_t) ( id >> 24 ); + buf->buffer[nBytes++] = (uint8_t) ( id >> 16 ); + buf->buffer[nBytes++] = (uint8_t) ( id >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( id >> 0 ); + CC--; } - return temp; -} + if ( extension ) + { + buf->buffer[nBytes++] = (uint8_t) ( header->extHeaderId >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( header->extHeaderId >> 0 ); + buf->buffer[nBytes++] = (uint8_t) ( header->numExtUnits >> 8 ); + buf->buffer[nBytes++] = (uint8_t) ( header->numExtUnits >> 0 ); -/*-----------------------------------------------------------------------* - * ivasPayload_convertOrientationToQ15() - * - * Convert a float quaternion into a Q15 encoded quaternion. - *-----------------------------------------------------------------------*/ + memcpy( &buf->buffer[nBytes], header->extData, header->numExtUnits * 4 ); + nBytes += header->numExtUnits; + } -static void ivasPayload_convertOrientationToQ15( - IVAS_QUATERNION orientation, /* i : Input orientation in quaternion to be converted to Q15 */ - IVAS_QUATERNION_Q15 *orientationQ15 /* o : Ouput orientation in Q15 quaternion */ -) -{ - orientationQ15->w = ivasPayload_convertToQ15( orientation.w ); - orientationQ15->x = ivasPayload_convertToQ15( orientation.x ); - orientationQ15->y = ivasPayload_convertToQ15( orientation.y ); - orientationQ15->z = ivasPayload_convertToQ15( orientation.z ); + buf->length = nBytes; + return IVAS_ERR_OK; } - -/*-----------------------------------------------------------------------* - * ivasPayload_parseQuaternionValue() - * - * Parse a single Q15 encoded quaternion component from the RTP packet and convert it to float. - *-----------------------------------------------------------------------*/ - -static float ivasPayload_parseQuaternionComponent( - char **PIorientationData /* i/o: Pointer to the PI data frame containing a single quaternion component */ +static void UpdateRtpHeader( + RTP_HEADER *header, /* RTP header structure */ + uint32_t timestampOffset /* Timestamp offset @ 16KHz clock for next frame */ ) { - uint16_t tempQ15value; - tempQ15value = ( uint16_t ) * *PIorientationData; - tempQ15value = (uint16_t) ( ( tempQ15value << 8 ) | ( uint16_t ) * ( *PIorientationData + 1 ) ); - *PIorientationData += 2; - return ivasPayload_convertFromQ15( tempQ15value ); + header->seqNumber++; + header->timestamp += timestampOffset; } - -/*-----------------------------------------------------------------------* - * ivasPayload_parseOrientationPIdata() - * - * Parse a single orientation in quaternions from the PI data. - *-----------------------------------------------------------------------*/ - -static bool ivasPayload_parseOrientationPIdata( - char *PIorientationData, /* i/o: Pointer to the PI data frame containing a single quaternion orientation */ - IVAS_QUATERNION *q /* o : Parsed quaternion value */ +static ivas_error UnpackRtpPacket( + const IVAS_DATA_BUFFER *packet, /* Input buffer with RTP Packet */ + RTP_HEADER *header, /* RTP header structure */ + uint32_t *numHeaderBytes /* No. of rtp header bytes */ ) { - q->w = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->x = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->y = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->z = ivasPayload_parseQuaternionComponent( &PIorientationData ); - - if ( q->w > 1.0f || q->w < -1.0f || - q->x > 1.0f || q->x < -1.0f || - q->y > 1.0f || q->y < -1.0f || - q->z > 1.0f || q->z < -1.0f ) + uint32_t n = 0, nByte = 0, expectedSize = 12; + uint8_t byte; + + if ( packet->length < expectedSize ) { - fprintf( stderr, "Error: invalid orientation PI data\n" ); - return false; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Insufficient input buffer for RTP header unpacking" ); } - return true; -} - -/*---------------------------------------------------------------------* - * ivasPayload_parseSinglePIorientation( ) - * - * Parse a single orientation from PI data. - * Save the parsed orientation value and use in processing in future frames, if no new PI orientation is received. - *---------------------------------------------------------------------*/ - -static bool ivasPayload_parseSinglePIorientation( - char *orientation, /* i : input PI orientation */ - bool *isOrientationSaved, /* o : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* o : saved orientation value for this PI type */ -) -{ - if ( orientation ) + /* + +---+---+---+---+---+---+---+---+ + | V | V | P | X | CC (4 - bits) | + +---+---+---+---+---+---+---+---+ + */ + byte = packet->buffer[nByte++]; + header->version = ( byte >> 6 ) & MASK_2BIT; + header->padding = ( byte >> 5 ) & MASK_1BIT; + header->extension = ( byte >> 4 ) & MASK_1BIT; + header->CC = ( byte & MASK_4BIT ); + + /* + +---+---+---+---+---+---+---+---+ + | M | PT (7 - bits) | + +---+---+---+---+---+---+---+---+ + */ + byte = packet->buffer[nByte++]; + header->marker = ( byte >> 7 ) & MASK_1BIT; + header->payloadType = byte & MASK_7BIT; + + /* + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | SEQUENCE NUMBER (16 bit) | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + header->seqNumber = (uint16_t) packet->buffer[nByte++] << 8; + header->seqNumber |= (uint16_t) packet->buffer[nByte++] << 8; + + /* Timestamp (32-bit) */ + header->timestamp = ( (uint32_t) packet->buffer[nByte++] << 24 ); + header->timestamp |= ( (uint32_t) packet->buffer[nByte++] << 16 ); + header->timestamp |= ( (uint32_t) packet->buffer[nByte++] << 8 ); + header->timestamp |= ( (uint32_t) packet->buffer[nByte++] ); + + /* SSRC (32-bit) */ + header->ssrc = ( (uint32_t) packet->buffer[nByte++] << 24 ); + header->ssrc |= ( (uint32_t) packet->buffer[nByte++] << 16 ); + header->ssrc |= ( (uint32_t) packet->buffer[nByte++] << 8 ); + header->ssrc |= ( (uint32_t) packet->buffer[nByte++] ); + + expectedSize += ( header->CC ) * 4 + ( header->extension ? 4 : 0 ); + if ( packet->length < expectedSize ) { - /* read the orientation PI value */ - if ( !ivasPayload_parseOrientationPIdata( orientation, savedOrientation ) ) - { - fprintf( stderr, "Error: parsing PI orientation failed\n" ); - return false; - } - *isOrientationSaved = true; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "CC indicated but insufficient input buffer" ); } - return true; -} + /* CSRC Identifiers */ + for ( n = 0; n < header->CC; n++ ) + { + /* CSRC[n] (32-bit) */ + header->CSRC[n] = ( (uint32_t) packet->buffer[nByte++] << 24 ); + header->CSRC[n] |= ( (uint32_t) packet->buffer[nByte++] << 16 ); + header->CSRC[n] |= ( (uint32_t) packet->buffer[nByte++] << 8 ); + header->CSRC[n] |= ( (uint32_t) packet->buffer[nByte++] ); + } -/*-----------------------------------------------------------------------* - * ivasPayload_parsePIdata() - * - * Parse PI data for the current audio frame. - *-----------------------------------------------------------------------*/ - -static bool ivasPayload_parsePIdata( - char *PIdataPayload, /* i : Pointer to the beginning of the PI data section in the RTP packet */ - uint16_t PIdataPayloadSizeBytes, /* i : Size of the PI data section in bytes */ - uint16_t frameIndex, /* i : Index to the current audio frame in the RTP packet */ - PI_DATA_CURRENT_FRAME *PIdataCurrentFrame, /* o : PI data handle for the current audio frame */ - PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ -) -{ - // TODO(pajunen): can this accidentally include zero padding bits? - - bool somePIframeFollowing; - char *PIframeDataBeginning; - uint16_t PIframeMarker, PIframeType, tempPIframeSizeBytes, PIframeSizeBytes, totalPIframeSizeBytes, totalPIheaderSizeBytes, count_audio; - PIframeDataBeginning = PIdataPayload; - somePIframeFollowing = true; - totalPIframeSizeBytes = 0; - totalPIheaderSizeBytes = 0; - count_audio = 0; - - while ( somePIframeFollowing ) + if ( header->extension ) { - if ( (int16_t) PIdataPayloadSizeBytes <= 0 ) + header->extHeaderId = ( (uint32_t) packet->buffer[nByte++] << 8 ); + header->extHeaderId |= ( (uint32_t) packet->buffer[nByte++] ); + header->numExtUnits = ( (uint32_t) packet->buffer[nByte++] << 8 ); + header->numExtUnits |= ( (uint32_t) packet->buffer[nByte++] ); + + expectedSize += header->numExtUnits * 4; + if ( packet->length < expectedSize ) { - fprintf( stderr, "Error: PI payload too small\n" ); - return false; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Extension Header indicated but insufficient input buffer" ); } - /* parse PI data header */ - somePIframeFollowing = ( *PIdataPayload & 0x80 ) != 0; - PIframeMarker = ( *PIdataPayload & 0x60 ) >> 5; - PIframeType = ( *PIdataPayload & 0x1F ); - ++PIdataPayload; - ++totalPIheaderSizeBytes; - --PIdataPayloadSizeBytes; - tempPIframeSizeBytes = ( *PIdataPayload & 0xFF ); - PIframeSizeBytes = tempPIframeSizeBytes; - while ( tempPIframeSizeBytes == 255 ) + header->extData = realloc( header->extData, header->numExtUnits * 4 ); + if ( header->extData == NULL ) { - ++PIdataPayload; - ++totalPIheaderSizeBytes; - --PIdataPayloadSizeBytes; - tempPIframeSizeBytes = ( *PIdataPayload & 0xFF ); - PIframeSizeBytes += tempPIframeSizeBytes; + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "failed to allocate extdata" ); } - ++PIdataPayload; - ++totalPIheaderSizeBytes; - --PIdataPayloadSizeBytes; - PIdataPayloadSizeBytes -= PIframeSizeBytes; + memcpy( header->extData, &packet->buffer[nByte], header->numExtUnits * 4 ); + nByte += header->numExtUnits * 4; + } - /* general PI data for all frames */ - if ( PIframeMarker == PM_GENERAL ) + *numHeaderBytes = nByte; + return IVAS_ERR_OK; +} + +static ivas_error getBitrateFromCodecAndFrameSize( + IVAS_RTP_CODEC codecId, + uint32_t frameLengthBytes, + uint32_t *bitrateKbps, + uint32_t *frameLenInBits, + uint8_t *tocByte ) +{ + size_t n; + *bitrateKbps = 0; + *frameLenInBits = 0; + if ( codecId == IVAS_RTP_IVAS ) + { + /* Validate the framelength is supported for ivas frame */ + for ( n = 0; n < sizeof( ivasFrameSizeInBits ) / sizeof( ivasFrameSizeInBits[0] ); n++ ) { - ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + if ( ivasFrameSizeInBits[n] == frameLengthBytes * 8 ) + { + *bitrateKbps = ( frameLengthBytes * IVAS_NUM_FRAMES_PER_SEC * 8 ); /* bitrate in kbps */ + *frameLenInBits = frameLengthBytes * 8; /* frame length in bits */ + *tocByte = TOC_INDICATE_IVAS | ( n & MASK_4BIT ); /* bitrate index */ + return IVAS_ERR_OK; + } } - else + return IVAS_ERR_INVALID_BITRATE; + } + else + { + /* Try if frameLength is a supported EVS frame length */ + for ( n = 0; n < sizeof( evsFrameSizeInBits ) / sizeof( evsFrameSizeInBits[0] ); n++ ) { - /* current frame */ - if ( ( PIframeMarker > 0 ) & ( count_audio == frameIndex ) ) + if ( evsFrameSizeInBits[n] == frameLengthBytes * 8 ) { - ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + *bitrateKbps = ( frameLengthBytes * IVAS_NUM_FRAMES_PER_SEC * 8 ); /* bitrate in kbps */ + *frameLenInBits = frameLengthBytes * 8; /* frame length in bits */ + *tocByte = TOC_INDICATE_EVS | ( n & MASK_4BIT ); /* bitrate index */ + return IVAS_ERR_OK; } - - /* increase audio frame counter */ - if ( PIframeMarker == PM_LAST ) + } + /* Try if frameLength is a supported AMRWB-IO frame length */ + for ( n = 0; n < sizeof( amrWBIOFrameSizeInBits ) / sizeof( amrWBIOFrameSizeInBits[0] ); n++ ) + { + uint32_t lengthInBits = amrWBIOFrameSizeInBits[n]; + if ( ( ( lengthInBits + 7 ) / 8 ) == frameLengthBytes ) { - ++count_audio; + *bitrateKbps = ( lengthInBits * IVAS_NUM_FRAMES_PER_SEC ); /* bitrate in kbps */ + *frameLenInBits = lengthInBits; /* frame length in bits */ + *tocByte = TOC_INDICATE_AMRWB | ( n & MASK_4BIT ); /* bitrate index */ + return IVAS_ERR_OK; } } - totalPIframeSizeBytes += PIframeSizeBytes; + return IVAS_ERR_INVALID_BITRATE; } - ivasPayload_PIaddHeaderBytes( totalPIheaderSizeBytes, PIdataCurrentFrame ); +} + +ivas_error IVAS_RTP_PACK_Open( + IVAS_RTP_PACK_HANDLE *phPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + const IVAS_RTP_PACK_CONFIG *config /* i : pointer to initial config for RTP Packer */ +) +{ + ivas_error error; + IVAS_RTP_PACK_HANDLE hPack; - /* scene orientation */ - if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) + if ( phPack == NULL ) { - fprintf( stderr, "Error: parsing scene orientation PI data failed.\n" ); - return false; + return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - /* device orientation */ - if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) + *phPack = NULL; + if ( ( hPack = (IVAS_RTP_PACK_HANDLE) calloc( 1, sizeof( struct IVAS_RTP_PACK ) ) ) == NULL ) { - fprintf( stderr, "Error: parsing device orientation PI data failed.\n" ); - return false; + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtppack handle" ); } - return true; -} + error = BPOOL_Create( &hPack->nodePool, sizeof( FRAME_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); + ERR_CHECK_RETURN( error ); + error = QUEUE_Create( &hPack->frameQ ); + ERR_CHECK_RETURN( error ); -/*-----------------------------------------------------------------------* - * resetPIdataCurrentFrame() - * - * Reset the PI data handle for the current audio frame. - *-----------------------------------------------------------------------*/ + mtx_init( &hPack->lock, 0 ); + hPack->initConfig = *config; + initRequests( hPack->requests ); + InitRtpHeader( &hPack->header ); + *phPack = hPack; + return IVAS_ERR_OK; +} -static void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) +/* Close and free an existing instance of rtp packer */ +void IVAS_RTP_PACK_Close( + IVAS_RTP_PACK_HANDLE *phPack /* i/o : pointer to an IVAS rtp packer handle to be closed */ +) { - PIdataCurrentFrame->PIdataPresent = false; - PIdataCurrentFrame->sceneOrientation = NULL; - PIdataCurrentFrame->deviceOrientationCompensated = NULL; - PIdataCurrentFrame->deviceOrientationUncompensated = NULL; - PIdataCurrentFrame->acousticEnvironmentId = NULL; - PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = NULL; - PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = NULL; -} + IVAS_RTP_PACK_HANDLE hPack; + + /* Free all memory */ + if ( phPack == NULL || *phPack == NULL ) + { + return; + } + hPack = *phPack; + QUEUE_Destroy( &hPack->frameQ ); + mtx_destroy( &hPack->lock ); + BPOOL_Destroy( &hPack->nodePool ); + free ( hPack->header.extData ); + free( hPack ); + *phPack = NULL; +} -bool ivasPayload_unpackFrame( - char *payload, /* i : RTP payload data */ - uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ - uint16_t frameIndex, /* i : Index for the data frame in the payload */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ +ivas_error IVAS_RTP_PACK_PushRemoteRequest( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE reqType, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE reqValue /* i : value of the requested type */ ) { - bool someEvsIvasModeBit, someFrameFollowing, someIvasIndicatorBit, someGoodFrameIndication; - uint16_t someBitrateIndex, someFrameSizeInBits, fullPayloadSizeBytes, PIdataPayloadSizeBytes; - int32_t bitrate; - uint16_t iFrame; - char *PIdataPayload; - fullPayloadSizeBytes = payloadSizeBytes; - PIdataPayload = payload; - someFrameFollowing = true; - PI_DATA_CURRENT_FRAME PIdataCurrentFrame; - resetPIdataCurrentFrame( &PIdataCurrentFrame ); - if ( payloadSizeBytes < 1 ) + if ( reqType < 0 || reqType >= IVAS_REQUEST_MAX ) { - fprintf( stderr, "Error: payload too small to parse ToC\n" ); - return false; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid request key provided" ); } - /* parse CMR/E-bytes */ - if ( *payload & E_BYTE ) // 1xxx xxxx + + /* Sanity on API */ + switch ( reqType ) { - PIdataCurrentFrame.PIdataPresent = false; - while ( 1 ) + case IVAS_REQUEST_CODEC: { - if ( ( *payload & 0xF0 ) == IVAS_E_CMR ) // 1111 xxxx - { - /* IVAS CMR/E-byte */ - while ( 1 ) - { - /* parse subsequent E-bytes */ - ++payload; - --payloadSizeBytes; - if ( ( *payload & 0xE0 ) == IVAS_E_BW_REQUEST ) // 100x xxxx - { - /* IVAS bandwidth request */ - if ( ( *payload & 0xFF ) == IVAS_BW_REQ_WB ) // 1000 0000 - { - /* Wideband request */ - } - else if ( ( *payload & 0xFF ) == IVAS_BW_REQ_SWB ) // 1000 0001 - { - /* Super-wideband request */ - } - else if ( ( *payload & 0xFF ) == IVAS_BW_REQ_FB ) // 1000 0010 - { - /* Fullband request */ - } - } - else if ( ( *payload & 0xE0 ) == IVAS_E_FMT_REQUEST ) // 101x xxxx - { - /* IVAS coded format request */ - if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_STEREO ) // 1010 0000 - { - /* Stereo request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_SBA ) // 1010 0001 - { - /* SBA request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_MASA ) // 1010 0010 - { - /* MASA request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_ISM ) // 1010 0011 - { - /* ISM request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_MC ) // 1010 0100 - { - /* MC request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_OMASA ) // 1010 0101 - { - /* OMASA request */ - } - else if ( ( *payload & 0xFF ) == IVAS_FMT_REQ_OSBA ) // 1010 0110 - { - /* OSBA request */ - } - } - else if ( ( *payload & 0xE0 ) == IVAS_E_PI_INDICATION ) // 110x xxxx - { - /* IVAS PI presence */ - PIdataCurrentFrame.PIdataPresent = true; - } - else if ( ( *payload & 0xE0 ) == IVAS_E_RESERVED ) // 111x xxxx - { - /* Reserved IVAS subsequent E-byte */ - } - else - { - break; - } - - if ( payloadSizeBytes < 1 ) - { - fprintf( stderr, "Error: payload too small to parse subsequent E-bytes\n" ); - return false; - } - } - break; - } - else + uint32_t codec = reqValue.codec; + if ( codec != IVAS_RTP_IVAS && codec != IVAS_RTP_EVS ) { - /* EVS CMR */ - ++payload; - --payloadSizeBytes; - if ( *payload & E_BYTE ) // 1xxx xxxx - { - continue; - } - else - { - break; - } + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported codec id provided" ); } } - } - - /* parse all ToC entries */ - *frame = (unsigned char *) payload; /* no need to copy frame bytes */ - for ( iFrame = 0; someFrameFollowing; ++iFrame ) - { - if ( (int16_t) payloadSizeBytes <= 0 ) + break; + case IVAS_REQUEST_BITRATE: { - fprintf( stderr, "Error: payload too small\n" ); - return false; + uint32_t bitrate = reqValue.bitrate; + if ( bitrate < 5900 || bitrate > 512000 ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bitrate provided" ); + } } - if ( *payload & E_BYTE ) // 1xxx xxxx + break; + case IVAS_REQUEST_BANDWIDTH: { - fprintf( stderr, "Error: expected ToC, found CMR/E-byte\n" ); - return false; + uint32_t bandwidth = reqValue.bandwidth; + if ( bandwidth > IVAS_BANDWIDTH_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bandwidth provided" ); + } } - ivasPayload_parseToc( *payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &someGoodFrameIndication, &bitrate ); - if ( bitrate < 0 ) + break; + case IVAS_REQUEST_FORMAT: { - fprintf( stderr, "Error: unexpected frameTypeIndex in ToC\n" ); - return false; + uint32_t format = reqValue.formatType; + if ( format > IVAS_FMT_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported format provided" ); + } } - ++payload; - ++*frame; - someFrameSizeInBits = (uint16_t) ( bitrate / 50 ); - /* just keep/copy zero padding bits - * in case of AMRWB_IO_SID the STI bit and CMI bits following the SID_1k75 frame are also kept (A.2.2.1.3 of TS 26.445) */ - payloadSizeBytes -= 1 + ( someFrameSizeInBits + 7 ) / 8; - if ( iFrame < frameIndex ) + break; + case IVAS_REQUEST_CA_MODE: { - *frame += ( someFrameSizeInBits + 7 ) / 8; - if ( !someFrameFollowing ) + uint32_t caMode = reqValue.caMode; + if ( caMode > IVAS_RTP_CA_NO_REQ ) { - fprintf( stderr, "Error: expected ToC with F bit set\n" ); - return false; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported channel aware mode provided" ); } } - else if ( iFrame == frameIndex ) + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_REQUEST_SUBFORMAT: { - *evsIvasModeBit = someEvsIvasModeBit; - *frameFollowing = someFrameFollowing; - *bitrateIndex = someBitrateIndex; - *ivasIndicatorBit = someIvasIndicatorBit; - *isGoodFrame = someGoodFrameIndication; - *frameSizeInBits = someFrameSizeInBits; + uint32_t subFormat = reqValue.subFormatType; + if ( subFormat > IVAS_SUBFMT_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported subformat provided" ); + } } - if ( (int16_t) payloadSizeBytes < 0 ) + break; + case IVAS_REQUEST_SR_CONFIG: { - fprintf( stderr, "Error: payload too small for frame %u data\n", frameIndex ); - return false; + IVAS_RTP_SPLITRENDER srConfig = reqValue.srConfig; + if ( srConfig.reserved != 0 ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported reserved bits in SR config provided" ); + } } + break; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + default: + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported request type" ); } - /* parse PI data */ - if ( PIdataCurrentFrame.PIdataPresent ) - { - PIdataPayloadSizeBytes = payloadSizeBytes; - PIdataPayload += ( fullPayloadSizeBytes - PIdataPayloadSizeBytes ); - if ( !ivasPayload_parsePIdata( PIdataPayload, PIdataPayloadSizeBytes, frameIndex, &PIdataCurrentFrame, PIdataDepackerState ) ) - { - fprintf( stderr, "Error while parsing PI data\n" ); - return false; - } - } + hPack->requests[reqType] = reqValue; - return true; + return IVAS_ERR_OK; } -IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( - IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ - FILE *file /* i : Input file containing the rtpdump */ -) +static bool isAmrWBIOMode( uint32_t bitrate, uint32_t *idx ) { - RTPDUMP_ERROR rtpdumpError; - self->frameFollowing = false; - rtpdumpError = RTPDUMP_OpenWithFileToRead( &self->rtpdump, file ); - if ( rtpdumpError != RTPDUMP_NO_ERROR ) + size_t n; + *idx = 0; + for ( n = 0; n < sizeof( amrWBIOFrameSizeInBits ) / sizeof( amrWBIOFrameSizeInBits[0] ); n++ ) { - return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; + if ( bitrate == ( amrWBIOFrameSizeInBits[n] * IVAS_NUM_FRAMES_PER_SEC ) ) + { + *idx = n; + return true; + } } - self->PIdataDepackerState.sceneOrientationSaved = false; - self->PIdataDepackerState.deviceOrientationSaved = false; - return IVAS_RTPDUMP_DEPACKER_NO_ERROR; + return false; } -IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( - IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ - uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ - uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ - uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeBits /* o : Data frame size in bits */ -) +static uint32_t getBitrateIdx( const uint32_t *table, uint32_t tableLen, uint32_t bitrate ) { - /* read next RTP packet from rtpdump */ - if ( !self->frameFollowing ) + size_t n, idx = 0; + + for ( n = 0; n < tableLen; n++ ) { - RTPDUMP_ERROR rtpdumpError = RTPDUMP_ReadPacket( self->rtpdump, &self->rtpPacket, &self->timeoffset_ms ); - if ( rtpdumpError == RTPDUMP_READ_ENDOFFILE ) + if ( bitrate > ( table[n] * IVAS_NUM_FRAMES_PER_SEC ) ) { - return IVAS_RTPDUMP_DEPACKER_EOF; + idx = n; } - else if ( rtpdumpError != RTPDUMP_NO_ERROR ) + else { - return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; + return idx; } - self->frameIndex = 0; - } - /* unpack next frame from RTP packet */ - if ( !ivasPayload_unpackFrame( self->rtpPacket.data + self->rtpPacket.headerSize, - self->rtpPacket.payloadSize, self->frameIndex, - evsIvasModeBit, &self->frameFollowing, bitrateIndex, ivasIndicatorBit, - isGoodFrame, frame, frameSizeBits, &self->PIdataDepackerState ) ) - { - return IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR; - } - - /* return frame */ - *rtpSequenceNumber = self->rtpPacket.sequenceNumber; - *rtpTimeStamp = self->rtpPacket.timeStamp + self->frameIndex * 16000 / 50; - *rcvTime_ms = self->timeoffset_ms + self->frameIndex * 20; - ++self->frameIndex; - return IVAS_RTPDUMP_DEPACKER_NO_ERROR; -} - -void IVAS_RTPDUMP_DEPACKER_close( - IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ -) -{ - if ( !self ) - { - return; } - RTPDUMP_Close( &self->rtpdump, 0 ); + return idx; } -IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( - IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ - FILE *file /* i : Output file for the rtpdump stream */ +static void packEBytes( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + bool piDataPresent, /* i : Signals if PI data present */ + size_t maxNumEBytes, /* i : max capacity of eByte buffer */ + uint8_t *eByte, /* o : output buffer for E-Bytes */ + size_t *nBytesWritten /* o : max capacity of eByte buffer */ ) { - IVAS_RTPDUMP_PACKER *self; - RTPDUMP_ERROR rtpdumpError; - IVAS_QUATERNION identity; - identity.w = 1.0f; - identity.x = 0.0f; - identity.y = 0.0f; - identity.z = 0.0f; - self = calloc( sizeof( IVAS_RTPDUMP_PACKER ), 1 ); - rtpdumpError = RTPDUMP_OpenWithFileToWrite( &self->rtpdump, file ); - if ( rtpdumpError != RTPDUMP_NO_ERROR ) + /* Initial E-byte structure + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |H|T|T|T| BR | + * +-+-+-+-+-+-+-+-+ + */ + uint32_t nByte = 0; + uint8_t T = EBYTE_CMR_T_IVAS; /* IVAS */ + uint8_t BR = 0; + + IVAS_RTP_CODEC codec = hPack->requests[IVAS_REQUEST_CODEC].codec; + IVAS_RTP_BANDWIDTH bandwidth = hPack->requests[IVAS_REQUEST_BANDWIDTH].bandwidth; + uint32_t bitrate = hPack->requests[IVAS_REQUEST_BITRATE].bitrate; + uint32_t bitrateIdx = 0; + + *nBytesWritten = 0; + + if ( codec == IVAS_RTP_EVS ) { - return IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR; - } - RTPDUMP_SetDefaultRtpPacketHeader( &self->rtpPacket ); - memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); - self->piDataPacker.sceneOrientationQuat = identity; - self->piDataPacker.deviceOrientationQuat = identity; - self->piDataPacker.sceneOrientationPresent = false; - self->piDataPacker.deviceOrientationPresent = false; - self->piDataPresent = false; - *rtpdumpPacker = self; - return IVAS_RTPDUMP_PACKER_NO_ERROR; -} + /* If requesting EVS/AMRWB IO from farend, only Initial E-byte present */ + IVAS_RTP_CA_MODE caMode = hPack->requests[IVAS_REQUEST_CA_MODE].caMode; -void IVAS_RTPDUMP_PACKER_writeEbytes( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). - * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ -) -{ - UWord8 initialEbyte, subsequentEbyte; - bool initialEbyteWritten; - - /* determine E-bytes */ - initialEbyte = 0; - subsequentEbyte = 0; - initialEbyteWritten = false; - if ( requestImmersiveFormatBitrate ) - { - switch ( requestedBitrate ) + if ( caMode != IVAS_RTP_CA_NO_REQ ) { - case IVAS_13k2: - initialEbyte = IVAS_CMR_13k2; - break; - case IVAS_16k4: - initialEbyte = IVAS_CMR_16k4; - break; - case IVAS_24k4: - initialEbyte = IVAS_CMR_24k4; - break; - case IVAS_32k: - initialEbyte = IVAS_CMR_32k; - break; - case IVAS_48k: - initialEbyte = IVAS_CMR_48k; - break; - case IVAS_64k: - initialEbyte = IVAS_CMR_64k; - break; - case IVAS_80k: - initialEbyte = IVAS_CMR_80k; - break; - case IVAS_96k: - initialEbyte = IVAS_CMR_96k; - break; - case IVAS_128k: - initialEbyte = IVAS_CMR_128k; - break; - case IVAS_160k: - initialEbyte = IVAS_CMR_160k; - break; - case IVAS_192k: - initialEbyte = IVAS_CMR_192k; - break; - case IVAS_256k: - initialEbyte = IVAS_CMR_256k; - break; - case IVAS_384k: - initialEbyte = IVAS_CMR_384k; - break; - case IVAS_512k: - initialEbyte = IVAS_CMR_512k; - break; - default: - initialEbyte = IVAS_CMR_NO_REQ; - break; + T = ( bandwidth == IVAS_BANDWIDTH_SWB ) ? EBYTE_CMR_T_EVS_CA_SWB : EBYTE_CMR_T_EVS_CA_WB; + BR = caMode & MASK_3BIT; /* only 8 valid values */ + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = ( T | BR ); + } + } + else if ( bitrate > 0 && isAmrWBIOMode( bitrate, &bitrateIdx ) ) + { + /* AMR WB IO Mode */ + T = EBYTE_CMR_T_EVS_IO; + BR = (uint8_t) bitrateIdx & MASK_4BIT; + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = ( T | BR ); + } + } + else if ( bitrate > 0 ) + { + /* EVS Modes */ + bitrate = ( bitrate > 128000 ) ? 128000 : bitrate; + bitrate = ( bitrate < 5900 ) ? 5900 : bitrate; + bitrateIdx = getBitrateIdx( evsFrameSizeInBits, sizeof( evsFrameSizeInBits ) / sizeof( evsFrameSizeInBits[0] ), bitrate ); + BR = (uint8_t) bitrateIdx & MASK_4BIT; + + /* If a bandwidth choice cannot be signalled for a given bitrate + preference is given to bitrate choice, bandwidth req is modified */ + switch ( bandwidth ) + { + case IVAS_BANDWIDTH_NB: + T = ( bitrate <= 24400u ) ? EBYTE_CMR_T_EVS_NB : EBYTE_CMR_T_EVS_SWB; + break; + case IVAS_BANDWIDTH_WB: + T = EBYTE_CMR_T_EVS_WB; + break; + case IVAS_BANDWIDTH_SWB: + T = ( bitrate >= 9600u ) ? EBYTE_CMR_T_EVS_SWB : EBYTE_CMR_T_EVS_WB; + break; + case IVAS_BANDWIDTH_FB: + T = ( bitrate >= 16400u ) ? EBYTE_CMR_T_EVS_FB : EBYTE_CMR_T_EVS_WB; + break; + default: /*IVAS_BANDWIDTH_NO_REQ */ + T = ( bitrate >= 9600u ) ? EBYTE_CMR_T_EVS_SWB : EBYTE_CMR_T_EVS_WB; + break; + } + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = ( T | BR ); + } + } + else if ( piDataPresent ) + { + /* Send an initial E-byte to allow for subsequent PI indication E-byte */ + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = EBYTE_CMR_T_NO_REQ; + } } - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; } else { - // TODO(pajunen): add EVS CMRs here - /* EVS CMR */ - initialEbyteWritten = false; - } + /* Requesting IVAS from farend, Subsequent E-byte indicate BW, CFR, SR */ + IVAS_RTP_FORMAT format = hPack->requests[IVAS_REQUEST_FORMAT].formatType; + bool isBandwidthProvided = ( bandwidth != IVAS_BANDWIDTH_NO_REQ && bandwidth != IVAS_BANDWIDTH_NB ); + bool isFormatProvided = ( format != IVAS_FMT_NO_REQ ); + bool isSubFormatProvided = false; + bool isSRConfigProvided = false; + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SUBFORMAT subFormat = hPack->requests[IVAS_REQUEST_SUBFORMAT].subFormatType; + IVAS_RTP_SPLITRENDER srConfig = hPack->requests[IVAS_REQUEST_SR_CONFIG].srConfig; + + isSubFormatProvided = ( subFormat != IVAS_SUBFMT_NO_REQ ); + isSRConfigProvided = srConfig.valid; +#endif - if ( ivasBandwidthRequest != NO_BANDWIDTH_REQUEST ) - { - if ( !initialEbyteWritten ) + /* Initial E-Byte only sent if either bitrate requested or subsequent E-byte is requested */ + if ( !( ( bitrate > 0 ) || isBandwidthProvided || isFormatProvided || + isSubFormatProvided || isSRConfigProvided || piDataPresent ) ) { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; + return; } - switch ( ivasBandwidthRequest ) + if ( bitrate > 0 ) { - case WB_REQUEST: - subsequentEbyte = IVAS_BW_REQ_WB; - break; - case SWB_REQUEST: - subsequentEbyte = IVAS_BW_REQ_SWB; - break; - case FB_REQUEST: - subsequentEbyte = IVAS_BW_REQ_FB; - break; - default: - subsequentEbyte = IVAS_BW_NO_REQ; - break; + /* Initial E-Byte */ + bitrate = ( bitrate > 512000 ) ? 512000 : bitrate; + bitrate = ( bitrate < 13200 ) ? 13200 : bitrate; + bitrateIdx = getBitrateIdx( ivasFrameSizeInBits, sizeof( ivasFrameSizeInBits ) / sizeof( ivasFrameSizeInBits[0] ), bitrate ); + BR = (uint8_t) bitrateIdx & MASK_4BIT; + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = ( EBYTE_CMR_T_IVAS | BR ); + } + } + else + { + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = EBYTE_CMR_T_NO_REQ; + } } - writeByteToRtp( self, (char) subsequentEbyte ); - } - if ( ivasFormatRequest != NO_FORMAT_REQUEST ) - { - if ( !initialEbyteWritten ) + /* Subsequent E-bytes - Bandwidth Request */ + if ( isBandwidthProvided && nByte < maxNumEBytes ) { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; + uint8_t bw = ( bandwidth - IVAS_BANDWIDTH_WB ) & MASK_2BIT; + eByte[nByte++] = ( EBYTE_BANDWIDTH_REQUEST | bw ); } - switch ( ivasFormatRequest ) + /* Subsequent E-bytes - Coded Format Request */ + if ( ( isFormatProvided || isSubFormatProvided ) && nByte < maxNumEBytes ) { - case STEREO_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_STEREO; - break; - case SBA_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_SBA; - break; - case MASA_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_MASA; - break; - case ISM_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_ISM; - break; - case MC_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_MC; - break; - case OMASA_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_OMASA; - break; - case OSBA_REQUEST: - subsequentEbyte = IVAS_FMT_REQ_OSBA; - break; - default: - subsequentEbyte = IVAS_FMT_NO_REQ; - break; + uint8_t fmtEByte = ( EBYTE_FORMAT_REQUEST | ( (uint8_t) format & MASK_3BIT ) ); + eByte[nByte++] = isSubFormatProvided ? EBYTE_SUBFORMAT_REQUEST : fmtEByte; + } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( isSubFormatProvided && nByte < maxNumEBytes ) + { + eByte[nByte++] = ( (uint8_t) subFormat & MASK_6BIT ); /* Requested Coded subFormat */ + } + /* Subsequent E-bytes - Split Renderer Configuration Request */ + if ( isSRConfigProvided && nByte < maxNumEBytes ) + { + eByte[nByte++] = EBYTE_SR_REQUEST | + ( (uint8_t) srConfig.diegetic << 3 ) | ( (uint8_t) srConfig.yaw << 2 ) | + ( (uint8_t) srConfig.pitch << 1 ) | ( (uint8_t) srConfig.roll << 0 ); } - writeByteToRtp( self, (char) subsequentEbyte ); +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } - if ( self->piDataPresent ) + /* Final E-byte is the PI Indicator E-Byte */ + if ( piDataPresent && ( nByte < maxNumEBytes ) ) { - if ( !initialEbyteWritten ) - { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; - } - subsequentEbyte = IVAS_PI_INDICATION; - writeByteToRtp( self, (char) subsequentEbyte ); + eByte[nByte++] = EBYTE_PI_INDICATOR; /* PI Indication */ } + + *nBytesWritten = nByte; } -void IVAS_RTPDUMP_PACKER_writeToc( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t frameBitrate, /* i : Bitrate of the written frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). - * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ - bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ + +#ifdef RTP_S4_251135_CR26253_0016_REV1 +static ivas_error getSRToCByte( + IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ + uint32_t bitrateKbps, /* i : Bitrate in kbps */ + uint8_t *tocByte /* o : toc byte 2 */ ) { - UWord8 H_bit, F_bit, EVS_IVAS_mode_bit, Q_bit, toc; - int16_t is_amr_wb; - - is_amr_wb = 0; - H_bit = 0; - F_bit = someFrameFollowing ? 1 : 0; - /* FRAME_NO_DATA frames are written as in TS26.445 */ - if ( isImmersiveFormat && frameBitrate != FRAME_NO_DATA ) + uint8_t bitIdx, codecId, digetic; + if ( bitrateKbps < 256000 || bitrateKbps > 512000 ) { - toc = (UWord8) ( rate2IVASmode( frameBitrate ) ); - EVS_IVAS_mode_bit = 0; - Q_bit = 1; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bitrate for SR" ); } - else - { - toc = (UWord8) ( rate2EVSmode( frameBitrate, &is_amr_wb ) ); - EVS_IVAS_mode_bit = (UWord8) is_amr_wb; - Q_bit = 0; - } - toc = ( H_bit << 7 ) | ( F_bit << 6 ) | ( EVS_IVAS_mode_bit << 5 ) | ( Q_bit << 4 ) | toc; - writeByteToRtp( self, (char) toc ); -} -IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ - int32_t numBits, /* i : Number of bits contained in the data frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ + bitIdx = ( ( bitrateKbps / 128000u ) - 1 ) & MASK_2BIT; + codecId = (uint8_t) srInfo->codec; + digetic = srInfo->diegetic ? 1 : 0; + + *tocByte = ( digetic << 6 ) | ( codecId << 5 ) | ( bitIdx << 3 ); + + return IVAS_ERR_OK; +} +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + +static ivas_error addNewFrameNode( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + FRAME_NODE **newNode ) +{ + FRAME_NODE *node = NULL; + ivas_error error = BPOOL_GetBuffer( hPack->nodePool, (void **) &node ); + ERR_CHECK_RETURN( error ); + + node->auNumBits = 0; + node->tocNumBytes = 0; + node->next = NULL; + + initPiDataFrame( &node->piDataFrame ); + + /* Add to frames FiFo */ + QUEUE_Push( hPack->frameQ, (NODE *) node ); + + *newNode = node; + return IVAS_ERR_OK; +} + +/* Push a single IVAS/EVS frame to rtp packer + * + * If multiple frames per RTP Payload are desired, multiple frames must be explicitly + * pushed before a call to IVAS_RTP_PACK_GetPacket to generate a rtp packet. + * + * It is possible to have variable frames per packet until maxFramesPerPacket frames + * if IVAS_RTP_PACK_GetPacket is invoked asyncronously w.r.t this api. + * + */ +ivas_error IVAS_RTP_PACK_PushFrame( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ +) +{ + ivas_error error = IVAS_ERR_OK; + uint32_t bitrate = 0; + uint32_t frameLengthInBits = 0; + uint8_t tocByte = 0; + FRAME_NODE *node = (FRAME_NODE *) QUEUE_Back( hPack->frameQ ); + + /* Check if a node was added due to Generic PI Data push */ + if ( ( node == NULL ) || ( node->tocNumBytes != 0 ) ) + { + error = addNewFrameNode( hPack, &node ); + ERR_CHECK_RETURN( error ); + } + + if ( frameBuffer == NULL || frameBuffer->length == 0 ) + { + /* Indicates a NO_DATA_FRAME in case of DTX mode */ + node->toc[0] = TOC_INDICATE_NO_DATA; + node->tocNumBytes = 1; + node->auNumBits = 0; + } + else + { + error = getBitrateFromCodecAndFrameSize( codecId, frameBuffer->length, &bitrate, &frameLengthInBits, &tocByte ); + ERR_CHECK_RETURN( error ); + + node->toc[0] = tocByte; + node->tocNumBytes = 1; + node->auNumBits = frameLengthInBits; + memcpy( node->au, frameBuffer->buffer, frameBuffer->length ); + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( srInfo != NULL && srInfo->valid ) + { + node->toc[0] = TOC_INDICATE_SR; + error = getSRToCByte( srInfo, bitrate, &node->toc[1] ); + ERR_CHECK_RETURN( error ); + node->tocNumBytes = 2; + } +#endif + } + + return IVAS_ERR_OK; +} + +uint32_t IVAS_RTP_PACK_GetNumFrames( + IVAS_RTP_PACK_HANDLE hPack /* i/o : IVAS rtp packer handle */ +) +{ + return QUEUE_Size( hPack->frameQ ); +} + +/* Push single PI data to rtp packer + * + * Provide PI data for a current RTP Payload. All PI data is locally cached in the packer + * and set to the rtp packet with policy defined in initial configuration during call to + * IVAS_RTP_PACK_GetPacket. + * + */ +ivas_error IVAS_RTP_PACK_PushPiData( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ +) +{ + IVAS_PI_TYPE piDataType; + ivas_error error = IVAS_ERR_OK; + FRAME_NODE *node = (FRAME_NODE *) QUEUE_Back( hPack->frameQ ); + PIDATA_FRAME *piDataFrm = NULL; + uint8_t PM = 0; + + if ( data == NULL || + data->piDataType >= IVAS_PI_NO_DATA || /* NO_PI_DATA cannot be provided by user, it is generated in packing */ + data->size > sizeof( PIDATA ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Wrong PI Data provided" ); + } + + /* No frame in Queue to associate PI Data, create one */ + if ( node == NULL ) + { + /* PI data received before any frame in queue is generic */ + PM = PI_HEADER_PM_GENERIC; + + /* Add a new node in frame queue, toc is still zero indicating frame is expected */ + error = addNewFrameNode( hPack, &node ); + ERR_CHECK_RETURN( error ); + } + + piDataFrm = &node->piDataFrame; + piDataType = data->piDataType; + + error = PI_PackData( data, &piDataFrm->piData[piDataType], PM ); + ERR_CHECK_RETURN( error ); + + /* Indicate th PI data type presense in bitmap, If the same pi data type is + already pushed for this frame, it is overwritten with the newer data */ + if ( !( piDataFrm->piDataBitmap & ( 1u << piDataType ) ) ) + { + piDataFrm->piDataBitmap |= ( 1u << piDataType ); + piDataFrm->numPiDataAvailable++; + } + + /* Atleast one valid PI data is now pushed for this frame, clear No PI data + for this frame */ + if ( piDataFrm->piDataBitmap & ( 1u << IVAS_PI_NO_DATA ) ) + { + piDataFrm->piDataBitmap &= ~( 1u << IVAS_PI_NO_DATA ); + piDataFrm->numPiDataAvailable--; + } + + return IVAS_ERR_OK; +} + +/* Generate a rtp packet using available pushed frames + * + * Available remote requests, pi data and frames will be packed into a rtp packet. If no + * frame is pushed before call to this api, NO_DATA_FRAME will be generated + * + */ +ivas_error IVAS_RTP_PACK_GetPayload( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ + uint32_t *numFramesInPayload /* o : no. of frames in payload */ ) { + uint32_t n = 0, numFrame = 0; + ivas_error error = IVAS_ERR_OK; + uint32_t numPiDataPresent = 0; + FRAME_NODE *head = (FRAME_NODE *) QUEUE_Front( hPack->frameQ ); + FRAME_NODE *node = NULL; + size_t nBytes = packet->length, numEBytes = 0; + + if ( packet == NULL ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Output data buffer is NULL" ); + } + + /* Calculate number of frames and PI data present */ + node = head; + for ( n = 0; n < hPack->initConfig.maxFramesPerPacket && ( NULL != node ); n++, node = node->next ) + { + numFrame++; + numPiDataPresent += node->piDataFrame.numPiDataAvailable; + } + *numFramesInPayload = numFrame; /* numFrames in Packet */ + + /* IVAS Payload starts with E-Bytes */ + packEBytes( hPack, ( numPiDataPresent > 0 ), packet->capacity, &packet->buffer[packet->length], &numEBytes ); + nBytes += numEBytes; - RTPDUMP_ERROR rtpdumpError; - int32_t i, k, frameBitrate; - char tempData; + /* ToC bytes (atleast 1 byte per frame, 2 if SR )*/ + node = head; + for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + { + uint8_t fBit = ( n != ( numFrame - 1 ) ) ? TOC_HEADER_FOLLOWS : 0; /* Next ToC present */ + if ( nBytes < packet->capacity ) + { + packet->buffer[nBytes++] = ( node->toc[0] | fBit ); + } + else + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + } + + if ( node->tocNumBytes == 2 ) + { + if ( nBytes < packet->capacity ) + { + packet->buffer[nBytes++] = node->toc[1]; + } + else + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + } + } + } + + /* Frame Data */ + node = head; + for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + { + size_t len = ( node->auNumBits + 7 ) >> 3; /* zero padded length in bytes */ + if ( nBytes + len < packet->capacity ) + { + memcpy( &packet->buffer[nBytes], node->au, len ); + } + else + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + } + nBytes += len; + } - self->writingIndex = self->rtpPacket.headerSize; - self->rtpPacket.payloadSize = 0; - memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); + packet->length = nBytes; /* update packet length before PI packing */ - determineBitrate( numBits, &frameBitrate ); + /* PI Data */ + if ( numPiDataPresent > 0 ) + { + bool skipPiData = false; + uint32_t numPiDataWritten = 0; - /* payload header */ - IVAS_RTPDUMP_PACKER_writeEbytes( self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); - IVAS_RTPDUMP_PACKER_writeToc( self, frameBitrate, isImmersiveFormat, false ); + node = head; + for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + { + uint32_t piDataType = 0; + PIDATA_FRAME *piDataFrame = &node->piDataFrame; + uint32_t bitmap = piDataFrame->piDataBitmap; + uint8_t PM = 0, PF = 0; + for ( piDataType = 0; piDataType < 32 && ( bitmap != 0 ); piDataType++ ) + { + uint32_t mask = ( 1u << piDataType ); + if ( bitmap & mask ) + { + bitmap &= ~mask; /* Mask out this pi type to indicate processing */ + /* Check if last PI data this frame */ + PM = piDataFrame->piData[piDataType].data[0] & 0x60; + PM = (uint8_t) ( ( bitmap == 0 && PM != PI_HEADER_PM_GENERIC ) ? PI_HEADER_PM_LAST : PM ); + /* Check if last PI data all frames */ + PF = (uint8_t) ( ( bitmap == 0 && ( numPiDataWritten + 1 == numPiDataPresent ) ) ? PI_HEADER_PF_LAST : PI_HEADER_PF_NOT_LAST ); /* Last PI in Payload */ + /* Update the first byte of PI Header with PF/PM */ + piDataFrame->piData[piDataType].data[0] |= ( PF | PM ); + if ( nBytes + piDataFrame->piData[piDataType].size < packet->capacity ) + { + memcpy( &packet->buffer[nBytes], piDataFrame->piData[piDataType].data, piDataFrame->piData[piDataType].size ); + nBytes += piDataFrame->piData[piDataType].size; + } + else + { + skipPiData = true; /* Not enough bytes in output for PI data */ + } + numPiDataWritten++; + } + } + } + if ( !skipPiData ) + { + packet->length = nBytes; /* update packet length after PI packing */ + } + } - /* frame data */ - for ( i = 0; i < numBits; i += 8 ) + /* Pop frames from Queue */ + for ( n = 0; n < numFrame; n++ ) { - tempData = 0; - for ( k = 0; k < 8; ++k ) + node = (FRAME_NODE *) QUEUE_Pop( hPack->frameQ ); + if ( node == NULL ) { - tempData = (char) ( ( tempData << 1 ) | (char) bitStream[i + k] ); + assert( 0 ); /* catastrophic error, implementation issue */ + break; } - writeByteToRtp( self, tempData ); + + error = BPOOL_FreeBuffer( hPack->nodePool, node ); + assert( error == IVAS_ERR_OK ); /* catastrophic error if this fails */ } - /* PI data */ - IVAS_RTPDUMP_PACKER_writePIdata( self ); + return IVAS_ERR_OK; +} + +ivas_error IVAS_RTP_PACK_GetPacket( + IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ + uint32_t *numFramesInPacket /* o : no. of frames in packet */ +) +{ + ivas_error error = PackRtpHeader( &hPack->header, packet ); + ERR_CHECK_RETURN( error ); - /* write packet */ - rtpdumpError = RTPDUMP_WritePacket( self->rtpdump, &self->rtpPacket, self->timeoffset_ms ); - if ( rtpdumpError != RTPDUMP_NO_ERROR ) + error = IVAS_RTP_PACK_GetPayload( hPack, packet, numFramesInPacket ); + ERR_CHECK_RETURN( error ); + + UpdateRtpHeader( &hPack->header, ( *numFramesInPacket ) * 320 ); + + return IVAS_ERR_OK; +} + +ivas_error IVAS_RTP_UNPACK_Open( + IVAS_RTP_UNPACK_HANDLE *phUnpack, /* i/o : rtp unpacker handle */ + const IVAS_RTP_UNPACK_CONFIG *config /* i : initial configuration for rtp unpacker */ +) +{ + ivas_error error = IVAS_ERR_OK; + IVAS_RTP_UNPACK_HANDLE hUnpack; + if ( phUnpack == NULL || config == NULL ) { - return IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR; + return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - /* adjust packetizer values */ - self->rtpPacket.sequenceNumber += 1; - self->rtpPacket.timeStamp += 320; - self->timeoffset_ms += 20; + *phUnpack = NULL; + if ( ( hUnpack = (IVAS_RTP_UNPACK_HANDLE) calloc( 1, sizeof( struct IVAS_RTP_UNPACK ) ) ) == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtp unpack handle" ); + } + + error = BPOOL_Create( &hUnpack->nodePool, sizeof( UNPACK_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); + ERR_CHECK_RETURN( error ); + + error = BPOOL_Create( &hUnpack->piDataPool, sizeof( PIDATA_NODE ), IVAS_MAX_FRAMES_IN_QUEUE * IVAS_PI_MAX_ID ); + ERR_CHECK_RETURN( error ); + + error = QUEUE_Create( &hUnpack->frameQ ); + ERR_CHECK_RETURN( error ); - return IVAS_RTPDUMP_PACKER_NO_ERROR; + error = QUEUE_Create( &hUnpack->piDataQ ); + ERR_CHECK_RETURN( error ); + + hUnpack->initConfig = *config; + mtx_init( &hUnpack->lock, 0 ); + + initRequests( hUnpack->requests ); + + *phUnpack = hUnpack; + return IVAS_ERR_OK; } -void IVAS_RTPDUMP_PACKER_writePIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +/* Close and free an existing instance of rtp unpacker */ +void IVAS_RTP_UNPACK_Close( + IVAS_RTP_UNPACK_HANDLE *phUnpack /* i/o : IVAS rtp unpacker handle */ ) { - if ( !self->piDataPresent ) + IVAS_RTP_UNPACK_HANDLE hUnpack; + + /* Free all memory */ + if ( phUnpack == NULL || *phUnpack == NULL ) { return; } - UWord8 PIheaderByte; - IVAS_QUATERNION_Q15 sceneOrientationQ15, deviceOrientationQ15; - bool somePIfollowing = false; + hUnpack = *phUnpack; + mtx_destroy( &hUnpack->lock ); + QUEUE_Destroy( &hUnpack->frameQ ); + QUEUE_Destroy( &hUnpack->piDataQ ); + BPOOL_Destroy( &hUnpack->piDataPool ); + BPOOL_Destroy( &hUnpack->nodePool ); + free ( hUnpack->header.extData ); + free( hUnpack ); + *phUnpack = NULL; +} + +static void setEVSRequests( IVAS_RTP_BANDWIDTH bandwidth, uint32_t bitrate, IVAS_RTP_CA_MODE caMode, IVAS_RTP_REQUEST_VALUE *requests ) +{ + requests[IVAS_REQUEST_CODEC].codec = IVAS_RTP_EVS; + requests[IVAS_REQUEST_CA_MODE].caMode = caMode; + requests[IVAS_REQUEST_BITRATE].bitrate = bitrate; + requests[IVAS_REQUEST_BANDWIDTH].bandwidth = bandwidth; +} + +static uint32_t parseInitialEByte( const IVAS_DATA_BUFFER *payload, uint32_t nBytes, IVAS_RTP_REQUEST_VALUE *requests ) +{ + if ( nBytes < payload->length ) + { + uint8_t byte = payload->buffer[nBytes]; + uint8_t EvsIvasIndicator = ( byte & ( ~MASK_4BIT ) ); + uint8_t BR = ( byte & MASK_4BIT ); + + if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) + { + return nBytes; + } + + nBytes++; /* Consume this e-byte */ + + switch ( EvsIvasIndicator ) + { + case EBYTE_CMR_T_EVS_NB: + if ( BR < 7 ) + { + uint32_t bitrate = evsFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + setEVSRequests( IVAS_BANDWIDTH_NB, bitrate, IVAS_RTP_CA_NO_REQ, requests ); + } + break; + case EBYTE_CMR_T_EVS_IO: /* AMRWB-IO */ + if ( BR < 9 ) + { + uint32_t bitrate = amrWBIOFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + setEVSRequests( IVAS_BANDWIDTH_NO_REQ, bitrate, IVAS_RTP_CA_NO_REQ, requests ); + } + break; + case EBYTE_CMR_T_EVS_CA_WB: + if ( BR < 8 ) + { + uint32_t bitrate = 13200; /* Fixed in CA Mode */ + setEVSRequests( IVAS_BANDWIDTH_WB, bitrate, BR, requests ); + } + break; + case EBYTE_CMR_T_EVS_CA_SWB: + if ( BR < 8 ) + { + uint32_t bitrate = 13200; /* Fixed in CA Mode */ + setEVSRequests( IVAS_BANDWIDTH_SWB, bitrate, BR, requests ); + } + break; + case EBYTE_CMR_T_EVS_WB: + if ( BR < 12 ) + { + uint32_t bitrate = evsFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + setEVSRequests( IVAS_BANDWIDTH_WB, bitrate, IVAS_RTP_CA_NO_REQ, requests ); + } + break; + case EBYTE_CMR_T_EVS_SWB: /* Intentional fall through */ + if ( BR < 12 && BR > 2 ) + { + uint32_t bitrate = evsFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + setEVSRequests( IVAS_BANDWIDTH_SWB, bitrate, IVAS_RTP_CA_NO_REQ, requests ); + } + break; + case EBYTE_CMR_T_EVS_FB: + if ( BR < 12 && BR > 4 ) + { + uint32_t bitrate = evsFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + setEVSRequests( IVAS_BANDWIDTH_FB, bitrate, IVAS_RTP_CA_NO_REQ, requests ); + } + break; + case EBYTE_CMR_T_IVAS: /* IVAS */ + if ( BR != 14 ) + { + requests[IVAS_REQUEST_CODEC].codec = IVAS_RTP_IVAS; + requests[IVAS_REQUEST_BITRATE].bitrate = ( BR == 0xF ) ? 0 : ivasFrameSizeInBits[BR] * IVAS_NUM_FRAMES_PER_SEC; + requests[IVAS_REQUEST_CA_MODE].caMode = IVAS_RTP_CA_NO_REQ; + } + break; + } + } + + return nBytes; +} + +static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t nBytes, IVAS_RTP_REQUEST_VALUE *requests, bool *piDataIndicated ) +{ + while ( nBytes < payload->length ) + { + uint8_t byte = payload->buffer[nBytes]; + uint8_t ET = ( byte & ( ~MASK_4BIT ) ); + + if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) + { + return nBytes; + } + + nBytes++; /* Consume this e-byte */ + + switch ( ET ) + { + case EBYTE_BANDWIDTH_REQUEST: /* Bandwidth Request */ + { + requests[IVAS_REQUEST_BANDWIDTH].bandwidth = IVAS_BANDWIDTH_WB + ( byte & MASK_2BIT ); + } + break; + case EBYTE_FORMAT_REQUEST: /* Format Request */ + { +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool S = ( byte >> 3 ) & MASK_1BIT; + if ( S ) + { + /* Use the next byte to extract SubFormat */ + if ( nBytes < payload->length ) + { + byte = payload->buffer[nBytes++]; + requests[IVAS_REQUEST_SUBFORMAT].subFormatType = byte & MASK_6BIT; + } + } +#endif + requests[IVAS_REQUEST_FORMAT].formatType = byte & MASK_3BIT; + } + break; + case EBYTE_PI_INDICATOR: /* PI Indication */ + *piDataIndicated = true; + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case EBYTE_SR_REQUEST: /* Split Rendering Request */ + { + IVAS_RTP_SPLITRENDER *srConfig = &requests[IVAS_REQUEST_SR_CONFIG].srConfig; + srConfig->diegetic = ( byte >> 3 ) & MASK_1BIT; + srConfig->yaw = ( byte >> 2 ) & MASK_1BIT; + srConfig->pitch = ( byte >> 1 ) & MASK_1BIT; + srConfig->roll = byte & MASK_1BIT; + srConfig->valid = true; + } + break; +#endif + default: /* Reserved for future use - unhandled atm */ + assert( 0 ); + } + } + + return nBytes; +} + +static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numFrames, TOC_INFO *toc ) +{ + bool headerFollows = true; + uint32_t nBytes = *numBytes; + + *numFrames = 0; + while ( nBytes < payload->length && headerFollows ) + { + uint8_t byte = payload->buffer[nBytes]; + uint8_t BR = byte & MASK_4BIT; + uint8_t FT = byte & ( ( ~MASK_4BIT ) & MASK_6BIT ); + + headerFollows = ( byte & ( ~MASK_6BIT ) ) == TOC_HEADER_FOLLOWS; + + if ( ( byte & EBYTE_TOC_HEADER_BIT ) != 0 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Expected ToC byte missing" ); + } + + nBytes++; /* Consume this e-byte */ + + *numFrames += 1; + memset( toc, 0, sizeof( *toc ) ); + if ( *numFrames == MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET ) + { + return IVAS_ERROR( IVAS_ERR_INTERNAL, "No of frames in packet exceed max defined" ); + } + + if ( FT == TOC_INDICATE_ARMWB_Q || FT == TOC_INDICATE_AMRWB ) + { + toc->codecId = IVAS_RTP_EVS; + toc->speechLostIndicated = ( FT == TOC_INDICATE_ARMWB_Q ) ? true : false; /* Q bit = 0 for AMRWB, BR is valid */ + if ( BR <= 9 ) + { + toc->auNumBits = amrWBIOFrameSizeInBits[BR]; + } + else if ( BR < 14 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Reserved bitrate provided in AMRWB ToC" ); + } + else + { + toc->speechLostIndicated = ( BR == 14 ); /* SPEECH_LOST */ + toc->auNumBits = 0; + } + } + else if ( FT == TOC_INDICATE_IVAS ) + { + toc->codecId = IVAS_RTP_IVAS; + if ( BR == 14 ) + { +#ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Read Unconditional SR-ToC byte */ + if ( nBytes < payload->length ) + { + uint8_t SR_BR; + byte = payload->buffer[nBytes++]; + SR_BR = ( byte >> 3 ) & MASK_2BIT; + if ( SR_BR == 0 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Reserved bitrate provided in SR ToC" ); + } + toc->srInfo.valid = true; + toc->srInfo.diegetic = ( byte >> 6 ) & MASK_1BIT; + toc->srInfo.codec = IVAS_SR_TRANSPORT_LCLD + ( ( byte >> 5 ) & MASK_1BIT ); + toc->auNumBits = ( SR_BR + 1 ) * 128000u / IVAS_NUM_FRAMES_PER_SEC; + } + else + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during ToC SR byte" ); + } +#else + /* Reserved bit not expected */ + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Reserved BR idx reported in ToC" ); +#endif + } + else + { + toc->auNumBits = ivasFrameSizeInBits[BR]; + } + } + else /* EVS */ + { + toc->codecId = IVAS_RTP_EVS; + toc->speechLostIndicated = ( FT == TOC_INDICATE_ARMWB_Q ) ? true : false; /* Q bit = 0 for AMRWB, BR is valid */ + if ( BR < 13 ) + { + toc->auNumBits = evsFrameSizeInBits[BR]; + } + else if ( BR == 13 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Reserved bitrate provided in EVS ToC" ); + } + else + { + toc->speechLostIndicated = ( BR == 14 ); /* SPEECH_LOST */ + toc->auNumBits = 0; + } + } + + toc++; + + /* Handle any frame specific E-Bytes here currently there are none, so we skip any E-Bytes here after */ + if ( headerFollows ) + { + while ( nBytes < payload->length ) + { + byte = payload->buffer[nBytes]; + if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) + { + break; + } + nBytes++; + } + } + } + + *numBytes = nBytes; + return IVAS_ERR_OK; +} - /* PI header section */ +static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimestamp, const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numPiDataInPacket ) +{ + bool PF = true; + uint32_t nBytes = *numBytes; - /* scene orientation */ - if ( self->piDataPacker.sceneOrientationPresent ) + while ( PF ) { - if ( self->piDataPacker.deviceOrientationPresent ) + uint8_t piHeader0, PM, piDataType, byte = 0; + uint32_t piSize = 0; + + if ( nBytes + 1 >= payload->length ) + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during expected PI Header read" ); + } + + piHeader0 = payload->buffer[nBytes++]; + + PF = ( piHeader0 >> 7 ) & MASK_1BIT; /* New PI header follows this PI Data Frame */ + PM = ( piHeader0 & ( ~MASK_5BIT ) ) & MASK_7BIT; /* PI Marker Bits */ + piDataType = ( piHeader0 & MASK_5BIT ); + + do + { + byte = payload->buffer[nBytes++]; + piSize += byte; + if ( nBytes >= payload->length ) + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during reading piSize" ); + } + } while ( byte == 255 ); + + if ( nBytes + piSize <= payload->length ) { - somePIfollowing = true; + PIDATA_NODE *node = NULL; + ivas_error error = BPOOL_GetBuffer( hUnpack->piDataPool, (void **) &node ); + ERR_CHECK_RETURN( error ); + + node->next = NULL; + + error = PI_UnPackData( piDataType, piSize, &payload->buffer[nBytes], (IVAS_PIDATA_GENERIC *) &node->data ); + ERR_CHECK_RETURN( error ); + + node->timestamp = rtpTimestamp; + + nBytes += piSize; + *numPiDataInPacket += 1; + + QUEUE_Push( hUnpack->piDataQ, (NODE *) node ); } - if ( somePIfollowing ) + else { - PIheaderByte = ( PF1 << 7 ) | ( PM_NOT_LAST << 5 ) | PI_SCENE_ORIENTATION; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during reading pi data" ); + } + + if ( PM == PI_HEADER_PM_LAST ) + { + rtpTimestamp += 16000 / IVAS_NUM_FRAMES_PER_SEC; + } + } + + *numBytes = nBytes; + return IVAS_ERR_OK; +} + +ivas_error IVAS_RTP_UNPACK_PushPayload( + IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ + uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ + uint16_t sequenceNumber, /* i : sequence number from rtp header */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ +) +{ + ivas_error error = IVAS_ERR_OK; + uint32_t nBytes = 0, numFrames = 0, numPiData = 0, n; + bool piDataIndicated = false; + TOC_INFO toc[MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET]; + + IVAS_RTP_REQUEST_VALUE oldRequests[IVAS_REQUEST_MAX]; + + if ( hUnpack == NULL || payload == NULL ) + { + return IVAS_ERR_UNEXPECTED_NULL_POINTER; + } + + if ( remoteRequestBitmap != NULL ) + { + *remoteRequestBitmap = 0; + } + + if ( numFramesInPacket != NULL ) + { + *numFramesInPacket = 0; + } + + if ( numPiDataInPacket != NULL ) + { + *numPiDataInPacket = 0; + } + + memcpy( oldRequests, hUnpack->requests, sizeof( oldRequests ) ); + + /* Unpack IVAS Payload, starting with the E-Bytes */ + nBytes = parseInitialEByte( payload, nBytes, hUnpack->requests ); + + /* Unpack any subsequent E-bytes */ + nBytes = parseSubsequentEByte( payload, nBytes, hUnpack->requests, &piDataIndicated ); + + /* Unpack the ToC Bytes => Extract number of frames in packet */ + error = parseToCByte( payload, &nBytes, &numFrames, toc ); + ERR_CHECK_RETURN( error ); + + /* Read frame bits */ + for ( n = 0; n < numFrames; n++ ) + { + uint32_t frameSizeBytes; + UNPACK_NODE *node = NULL; + + /* Get a new node */ + error = BPOOL_GetBuffer( hUnpack->nodePool, (void **) &node ); + ERR_CHECK_RETURN( error ); + + node->next = NULL; + node->timestamp = timestamp + ( n * 320 ); + node->seqNumber = sequenceNumber; + node->toc = toc[n]; + + frameSizeBytes = ( node->toc.auNumBits + 7 ) / 8; + if ( nBytes + frameSizeBytes < payload->length ) + { + memcpy( node->au, &payload->buffer[nBytes], frameSizeBytes ); + nBytes += frameSizeBytes; } else { - PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_SCENE_ORIENTATION; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during expected frame bits" ); } - writeByteToRtp( self, (char) PIheaderByte ); - writeByteToRtp( self, PI_SCENE_ORIENTATION_SIZE_BYTES ); + + /* Add to frames FiFo */ + QUEUE_Push( hUnpack->frameQ, (NODE *) node ); } - /* device orientation */ - if ( self->piDataPacker.deviceOrientationPresent ) + if ( piDataIndicated ) { - PIheaderByte = ( PF0 << 7 ) | ( PM_LAST << 5 ) | PI_DEVICE_ORIENTATION_UNCOMPENSATED; - writeByteToRtp( self, (char) PIheaderByte ); - writeByteToRtp( self, PI_DEVICE_ORIENTATION_UNCOMPENSATED_SIZE_BYTES ); + error = parsePIData( hUnpack, timestamp, payload, &nBytes, &numPiData ); + if ( error != IVAS_ERR_OK ) + { + /* PI Parsing errors are not fatal => indicate no PI data in packet as workaround */ + numPiData = 0; + } } - /* PI frame data section */ + if ( numFramesInPacket != NULL ) + { + *numFramesInPacket = numFrames; + } - if ( self->piDataPacker.sceneOrientationPresent ) + if ( numPiDataInPacket ) { - ivasPayload_convertOrientationToQ15( self->piDataPacker.sceneOrientationQuat, &sceneOrientationQ15 ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.w >> 8 ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.w & 0x00FF ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.x >> 8 ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.x & 0x00FF ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.y >> 8 ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.y & 0x00FF ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.z >> 8 ) ); - writeByteToRtp( self, (char) ( sceneOrientationQ15.z & 0x00FF ) ); + *numPiDataInPacket = numPiData; } - if ( self->piDataPacker.deviceOrientationPresent ) + if ( remoteRequestBitmap ) { - ivasPayload_convertOrientationToQ15( self->piDataPacker.deviceOrientationQuat, &deviceOrientationQ15 ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.w >> 8 ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.w & 0x00FF ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.x >> 8 ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.x & 0x00FF ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.y >> 8 ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.y & 0x00FF ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.z >> 8 ) ); - writeByteToRtp( self, (char) ( deviceOrientationQ15.z & 0x00FF ) ); + for ( n = 0; n < IVAS_REQUEST_MAX; n++ ) + { + bool changed = ( memcmp( &hUnpack->requests[n], &oldRequests[n], sizeof( IVAS_RTP_REQUEST_VALUE ) ) != 0 ); + *remoteRequestBitmap |= changed ? ( 1u << n ) : 0; + } } + + return IVAS_ERR_OK; } -void IVAS_RTPDUMP_PACKER_determinePIpresence( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +ivas_error IVAS_RTP_UNPACK_PushPacket( + IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *packet, /* i : received rtp Packet */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ ) { - self->piDataPresent = false; - if ( self->piDataPacker.sceneOrientationPresent || - self->piDataPacker.deviceOrientationPresent ) + ivas_error error = IVAS_ERR_OK; + uint32_t numHeaderBytes = 0; + IVAS_DATA_BUFFER payload; + + error = UnpackRtpPacket( packet, &hUnpack->header, &numHeaderBytes ); + ERR_CHECK_RETURN( error ); + + /* Offset to RTP Payload */ + payload.capacity = packet->capacity; + payload.buffer = packet->buffer + numHeaderBytes; + payload.length = packet->length - numHeaderBytes; + + return IVAS_RTP_UNPACK_PushPayload( + hUnpack, + &payload, + hUnpack->header.timestamp, + hUnpack->header.seqNumber, + numFramesInPacket, + numPiDataInPacket, + remoteRequestBitmap ); +} + +ivas_error IVAS_RTP_UNPACK_GetRequest( + IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE type, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE *value /* o : pointer of the requested type */ +) +{ + if ( type < 0 || type >= IVAS_REQUEST_MAX ) { - self->piDataPresent = true; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Invalid request key provided" ); } + *value = hUnpack->requests[type]; + return IVAS_ERR_OK; } -void IVAS_RTPDUMP_PACKER_resetPIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +ivas_error IVAS_RTP_UNPACK_PullFrame( + IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ + uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ + uint16_t *sequenceNumber, /* o : sequence number from rtp header */ + bool *speechLostIndicated /* o : Is current frame indicated as Lost */ ) { - IVAS_QUATERNION identity; - identity.w = 1.0f; - identity.x = 0.0f; - identity.y = 0.0f; - identity.z = 0.0f; - - self->piDataPresent = false; - self->piDataPacker.sceneOrientationQuat = identity; - self->piDataPacker.deviceOrientationQuat = identity; - self->piDataPacker.sceneOrientationPresent = false; - self->piDataPacker.deviceOrientationPresent = false; + size_t length = 0; + UNPACK_NODE *node = (UNPACK_NODE *) QUEUE_Pop( hUnpack->frameQ ); + + /* Check if a node is available in FiFo */ + if ( node == NULL ) + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "No more frames in unpack fifo" ); + } + + length = ( node->toc.auNumBits + 7 ) / 8; + if ( frameBuffer != NULL && ( length <= frameBuffer->capacity ) ) + { + memcpy( frameBuffer->buffer, node->au, length ); + frameBuffer->length = length; + } + + if ( receivedCodecId != NULL ) + { + *receivedCodecId = node->toc.codecId; + } + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( srInfo != NULL ) + { + *srInfo = node->toc.srInfo; + } +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + + if ( timestamp != NULL ) + { + *timestamp = node->timestamp; + } + + if ( sequenceNumber != NULL ) + { + *sequenceNumber = node->seqNumber; + } + + if ( speechLostIndicated != NULL ) + { + *speechLostIndicated = node->toc.speechLostIndicated; + } + + return BPOOL_FreeBuffer( hUnpack->nodePool, node ); } -void IVAS_RTPDUMP_PACKER_close( - IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ +ivas_error IVAS_RTP_UNPACK_PullNextPiData( + IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ + size_t capacity, /* i : capacity of pi data buffer in bytes */ + uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ ) { - if ( !self || !( *self ) ) + IVAS_PIDATA_GENERIC *pi = NULL; + PIDATA_NODE *node = (PIDATA_NODE *) QUEUE_Pop( hUnpack->piDataQ ); + + /* Check if a node is available in FiFo */ + if ( node == NULL ) { - return; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "No more pi data in unpack fifo" ); } - RTPDUMP_Close( &( *self )->rtpdump, 0 ); - free( *self ); - *self = NULL; + + pi = (IVAS_PIDATA_GENERIC *) &node->data; + + if ( data != NULL && ( pi->size <= capacity ) ) + { + *data = *pi; + } + + if ( timestamp != NULL ) + { + *timestamp = node->timestamp; + } + + return BPOOL_FreeBuffer( hUnpack->piDataPool, node ); } -#endif + +#endif /* IVAS_RTPDUMP */ diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c new file mode 100644 index 0000000000..ba33abe59e --- /dev/null +++ b/lib_util/ivas_rtp_pi_data.c @@ -0,0 +1,601 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include "ivas_rtp_pi_data.h" +#include "ivas_error_utils.h" +#include "ivas_rtp_internal.h" + +#ifdef IVAS_RTPDUMP + +/* Generic PI data packing/unpacking functions */ +typedef ivas_error ( *PACK_PI_FN )( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ); +typedef ivas_error ( *UNPACK_PI_FN )( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ); + +static uint32_t writeInt16( uint8_t *buffer, uint32_t idx, int16_t val ) +{ + buffer[idx++] = (uint8_t) ( val >> 8 ); + buffer[idx++] = (uint8_t) ( val & 0x00FF ); + return idx; +} + +static uint16_t readInt16( const uint8_t *buffer ) +{ + return ( (uint16_t) buffer[0] << 8 ) | ( (uint16_t) buffer[1] ); +} + +/*-----------------------------------------------------------------------* + * ivasPayload_convertToQ15() + * + * Convert a float value into a Q15 encoded value. + *-----------------------------------------------------------------------*/ +static int16_t ivasPayload_convertToQ15( float value ) +{ + value = ( value * 32768.0 ); + value = value > +32767.0 ? +32767.0 : value; + value = value < -32768.0 ? -32768.0 : value; + return (int16_t) ( value ); +} + +static ivas_error packUnsupportedData( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + (void) piData; + (void) buffer; + (void) maxDataBytes; + /* Skip packing */ + *nBytesWritten = 0; + return IVAS_ERR_OK; +} + +static ivas_error unpackUnsupportedData( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + (void) piData; + (void) buffer; + (void) numDataBytes; + /* Skip unpacking */ + return IVAS_ERR_OK; +} + +static ivas_error packNoPiData( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + (void) piData; + + *nBytesWritten = 0; + + /* NO_PI_DATA is just PI header with no data */ + if ( maxDataBytes < 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in PI data buffer for NO_PI_DATA" ); + } + + buffer[nBytes++] = ( IVAS_PI_NO_DATA ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 0; /* NO_PI_DATA is 0 bytes */ + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackNoPiData( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + (void) buffer; + + if ( numDataBytes != 0 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "NO_PI_DATA must be 0 byte" ); + } + + piData->size = sizeof( IVAS_PIDATA_NO_DATA ); + return IVAS_ERR_OK; +} + +static ivas_error packOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + const IVAS_PIDATA_ORIENTATION *orientation = (const IVAS_PIDATA_ORIENTATION *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_ORIENTATION ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in Orientation PI data" ); + } + + if ( ( piData->piDataType != IVAS_PI_SCENE_ORIENTATION ) && ( piData->piDataType != IVAS_PI_DEVICE_ORIENTATION_COMPENSATED ) && ( piData->piDataType != IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED ) +#ifdef RTP_S4_251135_CR26253_0016_REV1 + && ( piData->piDataType != IVAS_PI_PLAYBACK_DEVICE_ORIENTATION ) && ( piData->piDataType != IVAS_PI_HEAD_ORIENTATION ) && ( piData->piDataType != IVAS_PI_AUDIO_FOCUS_DIRECTION ) +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in Orientation PI data" ); + } + + /* Orientation data is 8 bytes, header is 2 bytes */ + if ( maxDataBytes < 8 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); + } + + buffer[nBytes++] = ( orientation->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 8; + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.w ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.x ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.y ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( orientation->orientation.z ) ); + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_ORIENTATION *orientation = (IVAS_PIDATA_ORIENTATION *) piData; + + /* Orientation data is 8 bytes */ + if ( numDataBytes != 8 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + } + + piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); + orientation->orientation.w = ((int16_t) readInt16( &buffer[0] )) / 32768.0f; + orientation->orientation.x = ((int16_t) readInt16( &buffer[2] )) / 32768.0f; + orientation->orientation.y = ((int16_t) readInt16( &buffer[4] )) / 32768.0f; + orientation->orientation.z = ((int16_t) readInt16( &buffer[6] )) / 32768.0f; + + return IVAS_ERR_OK; +} + +static uint32_t getIndexTable( const float *table, uint32_t tableLength, float value ) +{ + uint32_t idx = 0; + if ( value <= table[0] ) + { + return 0; + } + + for ( idx = 1; idx < tableLength; idx++ ) + { + if ( value < table[idx] ) + { + break; + } + } + return idx - 1; +} + +#define GET_IDX( table, nBits, value ) getIndexTable( table, ( 1u << ( nBits ) ), ( value ) ) + +static ivas_error packAcousticEnvironment( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + uint32_t packedSize = 1; + const IVAS_PIDATA_ACOUSTIC_ENV *aeEnv = (const IVAS_PIDATA_ACOUSTIC_ENV *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_ACOUSTIC_ENV ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in PI data of type Acoustic Environment" ); + } + + if ( aeEnv->availEarlyReflections ) + { + packedSize = 8; + } + else if ( aeEnv->availLateReverb ) + { + packedSize = 5; + } + + /* Acoustic Env data is packedSize bytes, header is 2 bytes */ + if ( maxDataBytes < packedSize + 2 ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Insufficient space to pack Orientation PI data" ); + } + + buffer[nBytes++] = ( IVAS_PI_ACOUSTIC_ENVIRONMENT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = packedSize; + + if ( packedSize == 1 ) + { + buffer[nBytes++] = aeEnv->aeid & 0x7F; + } + else + { + uint64_t dWord = (uint64_t) aeEnv->aeid << 57; + + dWord |= (uint64_t) GET_IDX( mapRT60, NBITS_RT60, aeEnv->rt60[IVAS_PI_AE_LOW] ) << 52; + dWord |= (uint64_t) GET_IDX( mapDSR, NBITS_DSR, aeEnv->dsr[IVAS_PI_AE_LOW] ) << 46; + dWord |= (uint64_t) GET_IDX( mapRT60, NBITS_RT60, aeEnv->rt60[IVAS_PI_AE_MID] ) << 41; + dWord |= (uint64_t) GET_IDX( mapDSR, NBITS_DSR, aeEnv->dsr[IVAS_PI_AE_MID] ) << 35; + dWord |= (uint64_t) GET_IDX( mapRT60, NBITS_RT60, aeEnv->rt60[IVAS_PI_AE_HIGH] ) << 30; + dWord |= (uint64_t) GET_IDX( mapDSR, NBITS_DSR, aeEnv->dsr[IVAS_PI_AE_HIGH] ) << 24; + + buffer[nBytes++] = (uint8_t) ( dWord >> 56 ); + buffer[nBytes++] = (uint8_t) ( dWord >> 48 ); + buffer[nBytes++] = (uint8_t) ( dWord >> 40 ); + buffer[nBytes++] = (uint8_t) ( dWord >> 32 ); + buffer[nBytes++] = (uint8_t) ( dWord >> 24 ); + + if ( packedSize > 5 ) + { + dWord |= (uint64_t) GET_IDX( mapRoomDims, NBITS_DIM, aeEnv->roomDimensions.x ) << 20; + dWord |= (uint64_t) GET_IDX( mapRoomDims, NBITS_DIM, aeEnv->roomDimensions.y ) << 16; + dWord |= (uint64_t) GET_IDX( mapRoomDims, NBITS_DIM, aeEnv->roomDimensions.z ) << 12; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_FRONT] ) << 10; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_BACK] ) << 8; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_LEFT] ) << 6; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_RIGHT] ) << 4; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_CEILING] ) << 2; + dWord |= (uint64_t) GET_IDX( mapAbsorbtion, NBITS_ABS, aeEnv->absorbCoeffs[IVAS_PI_AE_FLOOR] ); + + buffer[nBytes++] = (uint8_t) ( dWord >> 16 ); + buffer[nBytes++] = (uint8_t) ( dWord >> 8 ); + buffer[nBytes++] = (uint8_t) ( dWord ); + } + } + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackAcousticEnvironment( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_ACOUSTIC_ENV *aeEnv = (IVAS_PIDATA_ACOUSTIC_ENV *) piData; + + /* Acooustic Env data is either 1, 5 or 8 bytes */ + if ( numDataBytes != 1 && numDataBytes != 5 && numDataBytes != 8 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack PI data of type Acoustic Environment" ); + } + + piData->size = sizeof( IVAS_PIDATA_ACOUSTIC_ENV ); + aeEnv->availLateReverb = ( numDataBytes >= 5 ); + aeEnv->availEarlyReflections = ( numDataBytes == 8 ); + + if ( numDataBytes == 1 ) + { + aeEnv->aeid = buffer[0]; + } + else + { + uint64_t dWord = 0ull; + uint32_t n; + for ( n = 0; n < numDataBytes; n++ ) + { + dWord <<= 8; + dWord |= buffer[n]; + } + dWord <<= ( 8 - numDataBytes ); + + aeEnv->aeid = (uint8_t) ( ( dWord >> 57 ) & MASK_AEID ); + aeEnv->rt60[IVAS_PI_AE_LOW] = mapRT60[( dWord >> 52 ) & MASK_RT60]; + aeEnv->dsr[IVAS_PI_AE_LOW] = mapDSR[( dWord >> 46 ) & MASK_DSR]; + aeEnv->rt60[IVAS_PI_AE_MID] = mapRT60[( dWord >> 41 ) & MASK_RT60]; + aeEnv->dsr[IVAS_PI_AE_MID] = mapDSR[( dWord >> 35 ) & MASK_DSR]; + aeEnv->rt60[IVAS_PI_AE_HIGH] = mapRT60[( dWord >> 30 ) & MASK_RT60]; + aeEnv->dsr[IVAS_PI_AE_HIGH] = mapDSR[( dWord >> 24 ) & MASK_DSR]; + + aeEnv->roomDimensions.x = mapRoomDims[( dWord >> 20 ) & MASK_DIM]; + aeEnv->roomDimensions.y = mapRoomDims[( dWord >> 16 ) & MASK_DIM]; + aeEnv->roomDimensions.z = mapRoomDims[( dWord >> 12 ) & MASK_DIM]; + + aeEnv->absorbCoeffs[IVAS_PI_AE_FRONT] = mapAbsorbtion[( dWord >> 10 ) & MASK_ABS]; + aeEnv->absorbCoeffs[IVAS_PI_AE_BACK] = mapAbsorbtion[( dWord >> 8 ) & MASK_ABS]; + aeEnv->absorbCoeffs[IVAS_PI_AE_LEFT] = mapAbsorbtion[( dWord >> 6 ) & MASK_ABS]; + aeEnv->absorbCoeffs[IVAS_PI_AE_RIGHT] = mapAbsorbtion[( dWord >> 4 ) & MASK_ABS]; + aeEnv->absorbCoeffs[IVAS_PI_AE_CEILING] = mapAbsorbtion[( dWord >> 2 ) & MASK_ABS]; + aeEnv->absorbCoeffs[IVAS_PI_AE_FLOOR] = mapAbsorbtion[( dWord >> 0 ) & MASK_ABS]; + } + + return IVAS_ERR_OK; +} + +#ifdef RTP_S4_251135_CR26253_0016_REV1 +static ivas_error packAudioDescription( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t n; + uint32_t nBytes = 0; + const IVAS_PIDATA_AUDIO_DESC *audioDesc = (const IVAS_PIDATA_AUDIO_DESC *) piData; + uint32_t packedSize = audioDesc->nValidEntries; /* Each Entry is 1 byte */ + + *nBytesWritten = 0; + + /* Audio Description data is max 5 bytes, 2 bytes header */ + if ( maxDataBytes < packedSize + 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in Audio Description PI data buffer" ); + } + + buffer[nBytes++] = ( IVAS_PI_AUDIO_DESCRIPTION ); /* PF/PM populated during final packing */ + buffer[nBytes++] = (uint8_t) packedSize; + + for ( n = 0; n < audioDesc->nValidEntries; n++ ) + { + buffer[nBytes++] = ( audioDesc->audioId[n].speech ? PI_AD_SPEECH_INDICATED : 0 ) | + ( audioDesc->audioId[n].music ? PI_AD_MUSIC_INDICATED : 0 ) | + ( audioDesc->audioId[n].ambiance ? PI_AD_AMBIANCE_INDICATED : 0 ) | + ( audioDesc->audioId[n].editable ? PI_AD_EDITABLE_INDICATED : 0 ) | + ( audioDesc->audioId[n].binaural ? PI_AD_BINAURAL_INDICATED : 0 ); + } + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackAudioDescription( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + uint32_t n; + IVAS_PIDATA_AUDIO_DESC *audioDesc = (IVAS_PIDATA_AUDIO_DESC *) piData; + + /* Audio Description data is max 5 bytes */ + if ( numDataBytes > 5 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + } + + audioDesc->size = sizeof( IVAS_PIDATA_AUDIO_DESC ); + audioDesc->nValidEntries = numDataBytes; + audioDesc->piDataType = IVAS_PI_AUDIO_DESCRIPTION; + + for ( n = 0; n < audioDesc->nValidEntries; n++ ) + { + audioDesc->audioId[n].speech = ( buffer[n] & PI_AD_SPEECH_INDICATED ) != 0; + audioDesc->audioId[n].music = ( buffer[n] & PI_AD_MUSIC_INDICATED ) != 0; + audioDesc->audioId[n].ambiance = ( buffer[n] & PI_AD_AMBIANCE_INDICATED ) != 0; + audioDesc->audioId[n].editable = ( buffer[n] & PI_AD_EDITABLE_INDICATED ) != 0; + audioDesc->audioId[n].binaural = ( buffer[n] & PI_AD_BINAURAL_INDICATED ) != 0; + } + + return IVAS_ERR_OK; +} + +static ivas_error packDynamicSuppression( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = (const IVAS_PIDATA_DYNAMIC_SUPPRESSION *) piData; + + *nBytesWritten = 0; + + /* Dynamic Audio Suppression data is 2 bytes, 2 bytes header */ + if ( maxDataBytes < 2 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in DAS PI data buffer" ); + } + + buffer[nBytes++] = ( IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 2u; + + buffer[nBytes++] = ( das->speech ? PI_AD_SPEECH_INDICATED : 0 ) | + ( das->music ? PI_AD_MUSIC_INDICATED : 0 ) | + ( das->ambiance ? PI_AD_AMBIANCE_INDICATED : 0 ); + buffer[nBytes++] = ( (uint8_t) das->sli & MASK_4BIT ) << 4; + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackDynamicSuppression( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = (IVAS_PIDATA_DYNAMIC_SUPPRESSION *) piData; + + /* Dynamic Suppression data is 2 bytes */ + if ( numDataBytes != 2 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack DAS PI data" ); + } + + das->size = sizeof( IVAS_PIDATA_AUDIO_DESC ); + das->piDataType = IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION; + das->speech = ( buffer[0] & PI_AD_SPEECH_INDICATED ) != 0; + das->music = ( buffer[0] & PI_AD_MUSIC_INDICATED ) != 0; + das->ambiance = ( buffer[0] & PI_AD_AMBIANCE_INDICATED ) != 0; + das->sli = ( buffer[1] >> 4 ); + + return IVAS_ERR_OK; +} + +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + +static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { + packOrientation, /* SCENE_ORIENTATION */ + packOrientation, /* DEVICE_ORIENTATION_COMPENSATED */ + packOrientation, /* DEVICE_ORIENTATION_UNCOMPENSATED */ + packAcousticEnvironment, /* ACOUSTIC_ENVIRONMENT */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + packAudioDescription, /* AUDIO_DESCRIPTION */ +#else + packUnsupportedData, /* AUDIO_DESCRIPTION */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + packUnsupportedData, /* ISM_NUM */ + packUnsupportedData, /* ISM_ID */ + packUnsupportedData, /* ISM_GAIN */ + packUnsupportedData, /* ISM_ORIENTATION */ + packUnsupportedData, /* ISM_POSITION */ + packUnsupportedData, /* ISM_DISTANCE_ATTENUATION */ + packUnsupportedData, /* ISM_DIRECTIVITY */ + packUnsupportedData, /* DIEGETIC_TYPE */ + packUnsupportedData, /* RESERVED13 */ + packUnsupportedData, /* RESERVED14 */ + packUnsupportedData, /* RESERVED15 */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ + packOrientation, /* HEAD_ORIENTATION */ +#else + packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ + packUnsupportedData, /* HEAD_ORIENTATION */ +#endif + packUnsupportedData, /* LISTENER_POSITION */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ + packAudioDescription, /* AUDIO_FOCUS_DIRECTION */ +#else + packUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ + packUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ +#endif + packUnsupportedData, /* PI_LATENCY */ + packUnsupportedData, /* R_ISM_ID */ + packUnsupportedData, /* R_ISM_GAIN */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + packOrientation, /* R_ISM_ORIENTATION */ +#else + packUnsupportedData, /* R_ISM_ORIENTATION */ +#endif + packUnsupportedData, /* R_ISM_POSITION */ + packUnsupportedData, /* R_ISM_DIRECTION */ + packUnsupportedData, /* RESERVED27 */ + packUnsupportedData, /* RESERVED28 */ + packUnsupportedData, /* RESERVED29 */ + packUnsupportedData, /* RESERVED30 */ + packNoPiData /* NO_DATA */ +}; + +static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { + unpackOrientation, /* SCENE_ORIENTATION */ + unpackOrientation, /* DEVICE_ORIENTATION_COMPENSATED */ + unpackOrientation, /* DEVICE_ORIENTATION_UNCOMPENSATED */ + unpackAcousticEnvironment, /* ACOUSTIC_ENVIRONMENT */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + unpackAudioDescription, /* AUDIO_DESCRIPTION */ +#else + unpackUnsupportedData, /* AUDIO_DESCRIPTION */ +#endif + unpackUnsupportedData, /* ISM_NUM */ + unpackUnsupportedData, /* ISM_ID */ + unpackUnsupportedData, /* ISM_GAIN */ + unpackUnsupportedData, /* ISM_ORIENTATION */ + unpackUnsupportedData, /* ISM_POSITION */ + unpackUnsupportedData, /* ISM_DISTANCE_ATTENUATION */ + unpackUnsupportedData, /* ISM_DIRECTIVITY */ + unpackUnsupportedData, /* DIEGETIC_TYPE */ + unpackUnsupportedData, /* RESERVED13 */ + unpackUnsupportedData, /* RESERVED14 */ + unpackUnsupportedData, /* RESERVED15 */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ + unpackOrientation, /* HEAD_ORIENTATION */ +#else + unpackUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ + unpackUnsupportedData, /* HEAD_ORIENTATION */ +#endif + unpackUnsupportedData, /* LISTENER_POSITION */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ + unpackOrientation, /* AUDIO_FOCUS_DIRECTION */ +#else + unpackUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ + unpackUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ +#endif + unpackUnsupportedData, /* PI_LATENCY */ + unpackUnsupportedData, /* R_ISM_ID */ + unpackUnsupportedData, /* R_ISM_GAIN */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + unpackOrientation, /* R_ISM_ORIENTATION */ +#else + unpackUnsupportedData, /* R_ISM_ORIENTATION */ +#endif + unpackUnsupportedData, /* R_ISM_POSITION */ + unpackUnsupportedData, /* R_ISM_DIRECTION */ + unpackUnsupportedData, /* RESERVED27 */ + unpackUnsupportedData, /* RESERVED28 */ + unpackUnsupportedData, /* RESERVED29 */ + unpackUnsupportedData, /* RESERVED30 */ + unpackNoPiData /* NO_DATA */ +}; + +static const uint32_t maxPiDataSize[IVAS_PI_MAX_ID] = { + 8, /* IVAS_PI_SCENE_ORIENTATION */ + 8, /* IVAS_PI_DEVICE_ORIENTATION_COMPENSATED */ + 8, /* IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED */ + 8, /* IVAS_PI_ACOUSTIC_ENVIRONMENT */ + 5, /* IVAS_PI_AUDIO_DESCRIPTION */ + 1, /* IVAS_PI_ISM_NUM */ + 4, /* IVAS_PI_ISM_ID */ + 4, /* IVAS_PI_ISM_GAIN */ + 32, /* IVAS_PI_ISM_ORIENTATION */ + 24, /* IVAS_PI_ISM_POSITION */ + 12, /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ + 8, /* IVAS_PI_ISM_DIRECTIVITY */ + 1, /* IVAS_PI_DIEGETIC_TYPE */ + 0, /* IVAS_PI_RESERVED13 */ + 0, /* IVAS_PI_RESERVED14 */ + 0, /* IVAS_PI_RESERVED15 */ + 8, /* IVAS_PI_PLAYBACK_DEVICE_ORIENTATION */ + 8, /* IVAS_PI_HEAD_ORIENTATION */ + 6, /* IVAS_PI_LISTENER_POSITION */ + 2, /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ + 8, /* IVAS_PI_AUDIO_FOCUS_DIRECTION */ + 4, /* IVAS_PI_PI_LATENCY */ + 1, /* IVAS_PI_R_ISM_ID */ + 1, /* IVAS_PI_R_ISM_GAIN */ + 8, /* IVAS_PI_R_ISM_ORIENTATION */ + 6, /* IVAS_PI_R_ISM_POSITION */ + 2, /* IVAS_PI_R_ISM_DIRECTION */ + 0, /* IVAS_PI_RESERVED27 */ + 0, /* IVAS_PI_RESERVED28 */ + 0, /* IVAS_PI_RESERVED29 */ + 0, /* IVAS_PI_RESERVED30 */ + 0, /* IVAS_PI_NO_DATA = 31 */ +}; + +ivas_error PI_PackData( const IVAS_PIDATA_GENERIC *piData, PIDATA_PACKED *packed, uint8_t pmBits ) +{ + uint32_t type = (IVAS_PI_TYPE) ( piData->piDataType & MASK_5BIT ); + ivas_error error = packPiDataFuntions[type]( piData, packed->data, sizeof( packed->data ), &packed->size ); + if ( error == IVAS_ERR_OK ) + { + packed->data[0] |= pmBits; /* Update the PM bits */ + } + assert( packed->size != 0 ); + return error; +} + +ivas_error PI_UnPackData( uint8_t piDataType, uint32_t piSize, const uint8_t *piDataBuffer, IVAS_PIDATA_GENERIC *piData ) +{ + ivas_error error; + + /* Sanitize maximum sizes for each PI Type */ + if ( piSize > maxPiDataSize[piDataType] ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Max size for PI Data type exceeded" ); + } + + error = unpackPiDataFuntions[piDataType]( piDataBuffer, piSize, piData ); + ERR_CHECK_RETURN( error ); + + /* since some pi data share piData structure, pi id are re-filled after unpacking */ + piData->piDataType = piDataType; + + return IVAS_ERR_OK; +} + +#endif /* IVAS_RTPDUMP */ diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h new file mode 100644 index 0000000000..5780f32d8f --- /dev/null +++ b/lib_util/ivas_rtp_pi_data.h @@ -0,0 +1,441 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_RTP_PI_DATA_H +#define IVAS_RTP_PI_DATA_H + +#include "common_api_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef IVAS_RTPDUMP + +#define IVAS_PI_MAX_DATA_SIZE ( 32 + 2 ) /* max packed PI data bytes + pi header bytes */ +#define IVAS_PI_MAX_OBJECTS ( 4 ) /* max ISM objects in PI data */ + + /* IVAS PI Data Types */ + typedef enum + { + /* Forward direction PI types */ + IVAS_PI_SCENE_ORIENTATION, /* orientation of audio scene in unit quaternions */ + IVAS_PI_DEVICE_ORIENTATION_COMPENSATED, /* orientation of device in unit quaternions (compensated) */ + IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED, /* orientation of device in unit quaternions (un-compensated) */ + IVAS_PI_ACOUSTIC_ENVIRONMENT, /* describe the acoustic environment */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ + IVAS_PI_ISM_NUM, /* Number of objects */ + IVAS_PI_ISM_ID, /* id of each object */ + IVAS_PI_ISM_GAIN, /* gain of each object */ + IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ + IVAS_PI_ISM_POSITION, /* position of each object */ + IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ + IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ + IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ + IVAS_PI_RESERVED13, /* reserved */ + IVAS_PI_RESERVED14, /* reserved */ + IVAS_PI_RESERVED15, /* reserved */ + + /* Reverse direction PI types */ + IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ + IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ + IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION, /* receiver’s preference with respect to audio suppression */ + IVAS_PI_AUDIO_FOCUS_DIRECTION, /* direction of interest for the listener in Quaternions */ + IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ + IVAS_PI_R_ISM_ID, /* id of an object for editing */ + IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ + IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ + IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ + IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ + IVAS_PI_RESERVED27, /* reserved */ + IVAS_PI_RESERVED28, /* reserved */ + IVAS_PI_RESERVED29, /* reserved */ + IVAS_PI_RESERVED30, /* reserved */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ + IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ + } IVAS_PI_TYPE; + + /* cartesian coordinates (X,Y,Z) in 3D space */ + typedef struct + { + float x, y, z; + } IVAS_COORDINATE; + + /* orientation data corresponding to any of the following pi data types :- + * - IVAS_PI_SCENE_ORIENTATION + * - IVAS_PI_DEVICE_ORIENTATION_COMPENSATED + * - IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED + * - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION + * - IVAS_PI_HEAD_ORIENTATION + * - IVAS_PI_AUDIO_FOCUS_DIRECTION + * - IVAS_PI_R_ISM_ORIENTATION + * + * piDataType is used to identify the correct pi data type contained here + */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_SCENE_ORIENTATION) */ + uint32_t piDataType; /* one of supported orientation data types */ + IVAS_QUATERNION orientation; /* orientation data expressed as quartenions */ + } IVAS_PIDATA_ORIENTATION; + + /* Acoustic environment corresponding to IVAS_PI_ACOUSTIC_ENVIRONMENT + * + * acoustic environment ID + * late reverb parameters + * - RT60 – indicating the time that it takes for the reflections to reduce 60 dB in energy level, per frequency band + * - DSR – diffuse to source signal energy ratio, per frequency band + * - Pre-delay – delay at which the computation of DSR values was performed + * early reflections + * - 3D rectangular virtual room dimensions + * - Broadband energy absorption coefficient per wall surface + */ + typedef enum + { + IVAS_PI_AE_LOW, /* center frequency 25 Hz */ + IVAS_PI_AE_MID, /* center frequency 250 Hz */ + IVAS_PI_AE_HIGH, /* center frequency 2.5 kHz */ + IVAS_PI_AE_NUM_BANDS /* number of ae bands */ + } IVAS_PI_AE_BANDS; + + typedef enum + { + IVAS_PI_AE_FRONT, + IVAS_PI_AE_BACK, + IVAS_PI_AE_LEFT, + IVAS_PI_AE_RIGHT, + IVAS_PI_AE_CEILING, + IVAS_PI_AE_FLOOR, + IVAS_PI_AE_NUM_SURFACE + } IVAS_PI_AE_SURFACE; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ACOUSTIC_ENV) */ + uint32_t piDataType; /* IVAS_PI_ACOUSTIC_ENVIRONMENT */ + bool availLateReverb; /* AE contains only late reverb parameters */ + bool availEarlyReflections; /* AE containing late reverb and simplified early reflections */ + uint8_t aeid; /* seven-bit acoustic environment identifier */ + + /* only valid if availLateReverb==true or availEarlyReflections==true */ + float rt60[IVAS_PI_AE_NUM_BANDS]; /* time for the reflections to reduce 60 dB per band in seconds */ + float dsr[IVAS_PI_AE_NUM_BANDS]; /* diffuse to source signal energy ratio per band in dB */ + + /* only valid if availEarlyReflections==true */ + IVAS_COORDINATE roomDimensions; /* room dimensions in meters length (x), width (y), height (z) */ + float absorbCoeffs[IVAS_PI_AE_NUM_SURFACE]; /* absorption coefficients for all surfaces */ + } IVAS_PIDATA_ACOUSTIC_ENV; + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Audio Description corresponding to IVAS_PI_AUDIO_DESCRIPTION + * Describe the following audio decriptors per object/type :- + * - audio content type is speech/music/ambiance + * - if audio rendering is editable + * - if stereo audio is binaural + * + * number of valid entries decide on basis of audio format:- + * - Stereo/SBA/MASA = 1 entry + * - MultiChannel = 2 entries (1 for center channel + 1 for all other channels) + * - ISM = Number of Object entries ( 1 per object ) + * - OMASA/OSBA = 1 + Num Discrete Coded Objects + * + */ + typedef struct + { + bool speech; /* audio has voice/speech */ + bool music; /* audio has music */ + bool ambiance; /* audio has background ambiance */ + bool editable; /* rendering audio metadata is editable */ + bool binaural; /* stereo stream is binaural */ + } IVAS_AUDIO_ID; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_DESC) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_DESCRIPTION */ + uint32_t nValidEntries; /* Number of valid audio IDs */ + IVAS_AUDIO_ID audioId[1 + IVAS_PI_MAX_OBJECTS]; /* audio id as per format */ + } IVAS_PIDATA_AUDIO_DESC; + + /* ISM specific PI data related to PI types : - + * + * - IVAS_PI_ISM_NUM + * - IVAS_PI_ISM_ID + * - IVAS_PI_ISM_GAIN + * - IVAS_PI_ISM_ORIENTATION + * - IVAS_PI_ISM_POSITION + * - IVAS_PI_ISM_DISTANCE_ATTENUATION + * - IVAS_PI_ISM_DIRECTIVITY + */ + + /* Number of ISMs */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_NUM) */ + uint32_t piDataType; /* IVAS_PI_ISM_NUM */ + uint32_t numObjects; /* Number of ISM */ + } IVAS_PIDATA_ISM_NUM; + + /* ISM ID */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_ID) */ + uint32_t piDataType; /* IVAS_PI_ISM_ID */ + uint8_t id[IVAS_PI_MAX_OBJECTS]; /* 8-bit ISM id of object */ + } IVAS_PIDATA_ISM_ID; + + /* ISM gain */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_GAIN) */ + uint32_t piDataType; /* IVAS_PI_ISM_GAIN */ + int8_t dB[IVAS_PI_MAX_OBJECTS]; /* ISM gain in dB per object [-96, +3] */ + } IVAS_PIDATA_ISM_GAIN; + + /* ISM orientation */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_ORIENTATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_ORIENTATION */ + IVAS_QUATERNION orientation[IVAS_PI_MAX_OBJECTS]; /* Orientation of audio objects in ISM(s) */ + } IVAS_PIDATA_ISM_ORIENTATION; + + /* ISM position */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_POSITION) */ + uint32_t piDataType; /* IVAS_PI_ISM_POSITION */ + IVAS_COORDINATE position[IVAS_PI_MAX_OBJECTS]; /* Position of audio objects in ISM(s) */ + } IVAS_PIDATA_ISM_POSITION; + + /* ISM distance attenuation comprising of following gains per ISM + * - reference distance + * - maximum distance + * - roll-off factor + */ + typedef struct + { + float ref_dist; /* reference distance in meters */ + float max_dist; /* maximum distance in meters */ + float roll; /* roll-off factor values */ + } IVAS_DIST_ATTEN; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_ATTENUATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ + IVAS_DIST_ATTEN distAtten[IVAS_PI_MAX_OBJECTS]; /* Distance attenuation of audio objects */ + } IVAS_PIDATA_ISM_ATTENUATION; + + /* ISM Directivity comprising of following per ISM :- + * - inner cone angle determines the size of the main cone directed to the front of the object + * - outer cone angle determines the size of the outer (back) cone + * - outer attenuation gain determines the attenuation outside the outer cone + */ + typedef struct + { + uint16_t innerConeAngle; /* inner cone angle in degrees (0 - 360) */ + uint16_t outerConeAngle; /* outer cone angle in degrees (0 - 360) */ + float outerAttenuationdB; /* attenuation outside the outer cone in dB */ + } IVAS_ISM_DIRECTIVITY; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_DIRECTIVITY) */ + uint32_t piDataType; /* IVAS_PI_ISM_DIRECTIVITY */ + IVAS_ISM_DIRECTIVITY directivity[IVAS_PI_MAX_OBJECTS]; /* Directivity of audio objects */ + } IVAS_PIDATA_ISM_DIRECTIVITY; + + /* Diegetic and non-diegetic indication flag as per audio format + * + * number of valid entries decided on basis of audio format:- + * - Stereo/SBA/MASA/MultiChannel = 1 entry + * - ISM = Number of Object entries ( 1 per object ) + * - OMASA/OSBA = 1 (last) + Num Discrete Coded Objects + */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_DIEGETIC) */ + uint32_t piDataType; /* IVAS_PI_DIEGETIC_TYPE */ + bool isDiegetic[1 + IVAS_PI_MAX_OBJECTS]; /* diegetic indication as per audio format */ + } IVAS_PIDATA_DIEGETIC; + + /* Listener position */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_LISTENER_POSITION) */ + uint32_t piDataType; /* IVAS_PI_LISTENER_POSITION */ + IVAS_COORDINATE position[IVAS_PI_MAX_OBJECTS]; /* Position of audio objects in ISM(s) */ + } IVAS_PIDATA_LISTENER_POSITION; + + + /* Dynamic Audio Suppression describes receiver’s preference with respect to the + * type of audio content that should be enhanced and the amount of suppression to + * be applied to the background noise + */ + typedef enum + { + IVAS_SLI_NO_SUPPRESSION = 0, /* Apply no suppression */ + IVAS_SLI_SUPPRESSION_LEVEL_1, /* Suppression level 1 */ + IVAS_SLI_SUPPRESSION_LEVEL_2, /* Suppression level 2 */ + IVAS_SLI_SUPPRESSION_LEVEL_3, /* Suppression level 3 */ + IVAS_SLI_SUPPRESSION_LEVEL_4, /* Suppression level 4 */ + IVAS_SLI_SUPPRESSION_LEVEL_5, /* Suppression level 5 */ + IVAS_SLI_SUPPRESSION_LEVEL_6, /* Suppression level 6 */ + IVAS_SLI_SUPPRESSION_LEVEL_7, /* Suppression level 7 */ + IVAS_SLI_SUPPRESSION_LEVEL_8, /* Suppression level 8 */ + IVAS_SLI_SUPPRESSION_LEVEL_9, /* Suppression level 9 */ + IVAS_SLI_SUPPRESSION_LEVEL_10, /* Suppression level 10 */ + IVAS_SLI_SUPPRESSION_LEVEL_11, /* Suppression level 11 */ + IVAS_SLI_SUPPRESSION_LEVEL_12, /* Suppression level 12 */ + IVAS_SLI_SUPPRESSION_LEVEL_13, /* Suppression level 13 */ + IVAS_SLI_SUPPRESSION_LEVEL_14, /* Suppression level 14 */ + IVAS_SLI_MAX_SUPPRESSION, /* Apply max suppression */ + } IVAS_SLI; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_DYNAMIC_SUPPRESSION) */ + uint32_t piDataType; /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ + bool speech; /* receiver's preference is voice/speech */ + bool music; /* receiver's preference is music */ + bool ambiance; /* receiver's preference is background ambiance */ + IVAS_SLI sli; /* suppression level indicator [0, 15] */ + } IVAS_PIDATA_DYNAMIC_SUPPRESSION; + + /* Reverse PI latency calculated as the elapsed time between the sent reverse PI data + * and received forward PI data. It is based on the receiving device experiencing the + * result of its sent data by receiving the corresponding data in forward direction as + * forward PI data + */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_REVERSE_PI_LATENCY) */ + uint32_t piDataType; /* IVAS_PI_PI_LATENCY */ + IVAS_PI_TYPE type; /* Reverse PI used for computation of Latency */ + int32_t latency; /* Latency as 27-bit int on RTP Clock @ 16KHz */ + } IVAS_PIDATA_REVERSE_PI_LATENCY; + + /* ISM specific PI data editing requests */ + + /* ISM ID in editing requests */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ID) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_ID */ + uint8_t id; /* 8-bit ISM id of object to edit */ + } IVAS_PIDATA_ISM_EDIT_ID; + + /* Editing request for ISM gain */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_GAIN) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_GAIN */ + int8_t dB; /* Preferred ISM gain in dB [-96, +3] */ + } IVAS_PIDATA_ISM_EDIT_GAIN; + + /* Editing request for ISM orientation */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ORIENTATION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_ORIENTATION */ + IVAS_QUATERNION orientation; /* orientation editing request for received ISM */ + } IVAS_PIDATA_ISM_EDIT_ORIENTATION; + + /* Editing request for ISM position */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_POSITION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_POSITION */ + IVAS_COORDINATE position; /* Positional editing request for received ISM */ + } IVAS_PIDATA_ISM_EDIT_POSITION; + + /* Editing request for ISM direction */ + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_DIRECTION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_DIRECTION */ + float azimuth; /* azimuth angle in degrees [-180, 180] */ + float elevation; /* elevation angle in degrees [-90°, 90°] */ + } IVAS_PIDATA_ISM_EDIT_DIRECTION; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_NO_DATA) */ + uint32_t piDataType; /* IVAS_PI_NO_DATA */ + } IVAS_PIDATA_NO_DATA; + + + typedef union + { + IVAS_PIDATA_ORIENTATION scene; + IVAS_PIDATA_ORIENTATION deviceCompensated; + IVAS_PIDATA_ORIENTATION deviceUnCompensated; + IVAS_PIDATA_ACOUSTIC_ENV acousticEnv; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_PIDATA_AUDIO_DESC audioDesc; + IVAS_PIDATA_ISM_NUM ismNum; + IVAS_PIDATA_ISM_ID ismId; + IVAS_PIDATA_ISM_GAIN ismGain; + IVAS_PIDATA_ISM_ORIENTATION ismOrientation; + IVAS_PIDATA_ISM_POSITION ismPosition; + IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; + IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; + IVAS_PIDATA_DIEGETIC digeticIndicator; + + IVAS_PIDATA_ORIENTATION playbackOrientation; + IVAS_PIDATA_ORIENTATION headOrientation; + IVAS_PIDATA_LISTENER_POSITION listnerPosition; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppression; + IVAS_PIDATA_ORIENTATION focusDirection; + IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; + IVAS_PIDATA_ISM_EDIT_ID ismEditId; + IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; + IVAS_PIDATA_ISM_EDIT_ORIENTATION ismEditOrientation; + IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; + IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_PIDATA_NO_DATA noPiData; + } PIDATA; + +#endif /* IVAS_RTPDUMP */ + +#ifdef __cplusplus +} +#endif + +#endif /* IVAS_RTP_PI_DATA_H */ diff --git a/lib_util/ivas_rtp_rom.c b/lib_util/ivas_rtp_rom.c new file mode 100644 index 0000000000..4b8539fb30 --- /dev/null +++ b/lib_util/ivas_rtp_rom.c @@ -0,0 +1,64 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include "ivas_rtp_internal.h" + +#ifdef IVAS_RTPDUMP + +const float mapRT60[1u << NBITS_RT60] = { + 0.01f, 0.0126f, 0.0159f, 0.02f, 0.0252f, 0.0317f, 0.04f, 0.0504f, + 0.0635f, 0.08f, 0.1008f, 0.1270f, 0.16f, 0.2016f, 0.2540f, 0.32f, + 0.4032f, 0.5080f, 0.64f, 0.8063f, 1.0159f, 1.28f, 1.6127f, 2.0319f, + 2.56f, 3.2254f, 4.0637f, 5.12f, 6.4508f, 8.1275f, 10.24f, 12.9016f +}; + +const float mapDSR[1u << NBITS_DSR] = { + -20.f, -21.f, -22.f, -23.f, -24.f, -25.f, -26.f, -27.f, + -28.f, -29.f, -30.f, -31.f, -32.f, -33.f, -34.f, -35.f, + -36.f, -37.f, -38.f, -39.f, -40.f, -41.f, -42.f, -43.f, + -44.f, -45.f, -46.f, -47.f, -48.f, -49.f, -50.f, -51.f, + -52.f, -53.f, -54.f, -55.f, -56.f, -57.f, -58.f, -59.f, + -60.f, -61.f, -62.f, -63.f, -64.f, -65.f, -66.f, -67.f, + -68.f, -69.f, -70.f, -71.f, -72.f, -73.f, -74.f, -75.f, + -76.f, -77.f, -78.f, -79.f, -80.f, -81.f, -82.f, -83.f +}; + +const float mapRoomDims[1u << NBITS_DIM] = { + 0.5f, 0.707f, 1.f, 1.4141f, 2, 2.8282f, 4.f, 5.6568f, + 8.f, 11.314f, 16.f, 22.627f, 32.f, 45.255f, 64.f, 90.51f +}; + +const float mapAbsorbtion[1u << NBITS_ABS] = { + 0.0800f, 0.1656f, 0.3430f, 0.7101f +}; + +#endif /* IVAS_RTPDUMP */ diff --git a/lib_util/mutex.h b/lib_util/mutex.h new file mode 100644 index 0000000000..f3fe493dc6 --- /dev/null +++ b/lib_util/mutex.h @@ -0,0 +1,104 @@ +/****************************************************************************************************** + + (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +/*==================================================================================== + EVS Codec 3GPP TS26.443 Nov 04, 2021. Version 12.14.0 / 13.10.0 / 14.6.0 / 15.4.0 / 16.3.0 + ====================================================================================*/ + +#ifndef _MUTEX_H +#define _MUTEX_H + +#if defined( __unix__ ) || defined( __linux__ ) || ( defined( __MACH__ ) && defined( __APPLE__ )) +#include +typedef pthread_mutex_t mtx_t; +#define _USE_POSIX ( 1 ) +#elif defined( _WIN32 ) || defined( _WIN64 ) +#include +typedef CRITICAL_SECTION mtx_t; +#define _USE_WIN ( 1 ) +#else +typedef int mtx_t; +#warning Mutex implementation to be defined for this platform here. +#endif + +static __inline int mtx_init( mtx_t *mutex, int type ) +{ + int err = 0; + (void) type; +#ifdef _USE_POSIX + err = pthread_mutex_init( mutex, NULL ); +#elif defined( _USE_WIN ) + InitializeCriticalSection( mutex ); +#else + (void) mutex; +#endif + return err; +} + +static __inline void mtx_destroy( mtx_t *mutex ) +{ +#if _USE_POSIX + pthread_mutex_destroy( mutex ); +#elif defined( _USE_WIN ) + DeleteCriticalSection( mutex ); +#else + (void) mutex; +#endif +} + +static __inline int mtx_lock( mtx_t *mutex ) +{ + int err = 0; +#if _USE_POSIX + err = pthread_mutex_lock( mutex ); +#elif defined( _USE_WIN ) + EnterCriticalSection( mutex ); +#else + (void) mutex; +#endif + return err; +} + +static __inline int mtx_unlock( mtx_t *mutex ) +{ + int err = 0; +#if _USE_POSIX + err = pthread_mutex_unlock( mutex ); +#elif defined( _USE_WIN ) + LeaveCriticalSection( mutex ); +#else + (void) mutex; +#endif + return err; +} + +#endif /* _MUTEX_H */ -- GitLab From d76762fdab6169deda87e038aad73c6a9255a6dd Mon Sep 17 00:00:00 2001 From: vaclav Date: Tue, 5 Aug 2025 17:22:08 +0200 Subject: [PATCH 066/340] address comments --- apps/decoder.c | 4 +-- lib_dec/ivas_cldfb_ring_buffer.c | 8 ++++-- lib_dec/lib_dec.c | 26 ++++++++++++++++---- lib_dec/lib_dec.h | 4 +-- lib_rend/ivas_dirac_dec_binaural_functions.c | 13 ++-------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 4fde8aa1c5..21d9c66873 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3379,7 +3379,7 @@ static ivas_error decodeVoIP( if ( isSplitRend ) { #ifdef SUPPORT_JBM_TRACEFILE - if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, nOutSamples, (void *) pcmBuf, splitRendBits, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else if ( ( error = IVAS_DEC_VoIP_GetSplitBinauralBitstream( hIvasDec, (void *) pcmBuf, splitRendBits, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #endif @@ -3392,7 +3392,7 @@ static ivas_error decodeVoIP( { #endif #ifdef SUPPORT_JBM_TRACEFILE - if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, &bitstreamReadDone, &nSamplesRendered, ¶meterAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #endif diff --git a/lib_dec/ivas_cldfb_ring_buffer.c b/lib_dec/ivas_cldfb_ring_buffer.c index 01ce9cd7c0..82f79687ff 100644 --- a/lib_dec/ivas_cldfb_ring_buffer.c +++ b/lib_dec/ivas_cldfb_ring_buffer.c @@ -42,9 +42,12 @@ #include "wmc_auto.h" #ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * CLDFB ring-buffer functions needed in split-rendering outputs + *---------------------------------------------------------------------*/ /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsEmpty() + * ivas_CLDFB_RINGBUF_IsEmpty() * * Returns 1 if the ring buffer is empty, or 0 otherwise. *---------------------------------------------------------------------*/ @@ -55,8 +58,9 @@ static int16_t ivas_CLDFB_RINGBUF_IsEmpty( return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); } + /*---------------------------------------------------------------------* - * CLDFB_RINGBUF_IsFull() + * ivas_CLDFB_RINGBUF_IsFull() * * Returns 1 if the ring buffer is full, or 0 otherwise. *---------------------------------------------------------------------*/ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0160b48549..25865123b0 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -4009,9 +4009,10 @@ ivas_error IVAS_DEC_VoIP_GetSamples *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_VoIP_GetSamples( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ - void *pcmBuf, /* o : output synthesis signal */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ + void *pcmBuf, /* o : output synthesis signal */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, void *jbmWriter, @@ -4025,7 +4026,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples( return ivas_dec_voip_get_samples_common( hIvasDec, nSamplesPerChannel, - IVAS_DEC_PCM_INT16, + pcmType, pcmBuf, NULL, NULL, @@ -4039,10 +4040,17 @@ ivas_error IVAS_DEC_VoIP_GetSamples( systemTimestamp_ms ); } + +/*---------------------------------------------------------------------* + * IVAS_DEC_VoIP_GetSplitBinauralBitstream( ) + * + * Main function to decode one split-rendering frame in VoIP + *---------------------------------------------------------------------*/ + /*! r: error code */ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ void *pcmBuf, /* o : output synthesis signal */ ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #ifdef SUPPORT_JBM_TRACEFILE @@ -4058,6 +4066,14 @@ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( int16_t i; float head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES][L_FRAME48k]; float *pp_head_pose_buf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES]; + ivas_error error = IVAS_ERR_UNKNOWN; + int16_t nSamplesPerChannel = 0; + + if ( ( error = IVAS_DEC_GetRenderFramesizeSamples( hIvasDec, &nSamplesPerChannel ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError getting render frame size in samples\n" ); + return error; + } /* Set pointers to beginning of head pose buffers */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 181417c70a..18298954c4 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -311,9 +311,7 @@ ivas_error IVAS_DEC_TSM_SetQuality( ivas_error IVAS_DEC_VoIP_GetSamples( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ -#ifndef FIX_1119_SPLIT_RENDERING_VOIP const IVAS_DEC_PCM_TYPE pcmType, /* i : type for the decoded PCM resolution */ -#endif void *pcmBuf, /* o : output synthesis signal */ #ifdef SUPPORT_JBM_TRACEFILE JbmTraceFileWriterFn jbmWriterFn, @@ -329,7 +327,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples( /*! r: error code */ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - uint16_t nSamplesPerChannel, /* i : number of samples per channel requested to be written to output buffer */ + /* const IVAS_DEC_PCM_TYPE pcmType, */ /* i : type for the decoded PCM resolution */ void *pcmBuf, /* o : output synthesis signal */ ISAR_SPLIT_REND_BITS_DATA *splitRendBits, /* o : output split rendering bits */ #ifdef SUPPORT_JBM_TRACEFILE diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 98f05e7b8e..0e314632d3 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -796,11 +796,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ivas_CLDFB_RINGBUF_Push( - st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], - tmp_Cldfb_out_re[ch][i], - tmp_Cldfb_out_im[ch][i], - CLDFB_NO_CHANNELS_MAX ); + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], CLDFB_NO_CHANNELS_MAX ); #else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); @@ -861,7 +857,6 @@ static void ivas_dirac_dec_binaural_internal( hCombinedOrientationData && hCombinedOrientationData->enableCombinedOrientation[subframe] > 0, nchanSeparateChannels, st_ivas->hMasaIsmData ); - /* re-use reverb and decorr from main direction for the sides */ ivas_dirac_dec_binaural_process_output( hDiracDecBin, hSpatParamRendCom, st_ivas->cldfbSynDec, output_f, Cldfb_RealBuffer_in, Cldfb_ImagBuffer_in, max_band_decorr, numInChannels, config_data.processReverb, subframe, tmp_Cldfb_out_re, tmp_Cldfb_out_im, @@ -873,11 +868,7 @@ static void ivas_dirac_dec_binaural_internal( for ( i = 0; i < hSpatParamRendCom->subframe_nbslots[subframe]; i++ ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ivas_CLDFB_RINGBUF_Push( - st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], - tmp_Cldfb_out_re[ch][i], - tmp_Cldfb_out_im[ch][i], - CLDFB_NO_CHANNELS_MAX ); + ivas_CLDFB_RINGBUF_Push( st_ivas->hSplitBinRend->hMultiBinCldfbData[pos_idx * BINAURAL_CHANNELS + ch], tmp_Cldfb_out_re[ch][i], tmp_Cldfb_out_im[ch][i], CLDFB_NO_CHANNELS_MAX ); #else mvr2r( tmp_Cldfb_out_re[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); mvr2r( tmp_Cldfb_out_im[ch][i], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[pos_idx * BINAURAL_CHANNELS + ch][hSpatParamRendCom->slots_rendered + i], CLDFB_NO_CHANNELS_MAX ); -- GitLab From a145854081c295b007196b42add0503b06cbf3ce Mon Sep 17 00:00:00 2001 From: "Singh, Ripinder" Date: Wed, 30 Jul 2025 13:25:21 +1000 Subject: [PATCH 067/340] BugFixes, Race Condition fixes, decoder restart for codec switch Signed-off-by: Singh, Ripinder --- Workspace_msvc/lib_util.vcxproj | 4 + Workspace_msvc/lib_util.vcxproj.filters | 12 + apps/decoder.c | 217 +++++++++++-- apps/encoder.c | 47 ++- lib_dec/lib_dec.c | 3 +- lib_util/ivas_bpool.c | 8 +- lib_util/ivas_bpool.h | 8 +- lib_util/ivas_queue.c | 26 +- lib_util/ivas_rtp_api.h | 46 +-- lib_util/ivas_rtp_internal.h | 1 + lib_util/ivas_rtp_payload.c | 385 +++++++++++++----------- lib_util/ivas_rtp_pi_data.c | 18 +- lib_util/mutex.h | 2 +- 13 files changed, 522 insertions(+), 255 deletions(-) diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 94561b175a..84ff59beae 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -111,6 +111,10 @@ + + + + diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index afaedfb5da..776a38e3d3 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -85,6 +85,18 @@ util_c + + util_c + + + util_c + + + util_c + + + util_c + diff --git a/apps/decoder.c b/apps/decoder.c index 39c4f19824..c9eb700df3 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -168,6 +168,12 @@ typedef struct } IVAS_DEC_HRTF_BINARY_WRAPPER; #ifdef IVAS_RTPDUMP +#define JITTER_DURATION_MS ( 60 ) +#ifdef DEBUGGING +#define DEBUG_PRINT fprintf +#else +#define DEBUG_PRINT( ... ) +#endif typedef struct { @@ -177,8 +183,8 @@ typedef struct typedef struct { - uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * MAX_FRAMES_PER_RTP_PACKET]; - PIDATA_TS piData[IVAS_PI_MAX_ID * MAX_FRAMES_PER_RTP_PACKET]; + uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; FILE *f_rtpstream; IVAS_RTP_CODEC codecId; @@ -187,11 +193,18 @@ typedef struct uint32_t numFramesInPacket; uint32_t numPiDataInPacket; uint32_t remoteRequestBitmap; + bool speechLostIndicated; + bool restartNeeded; + bool isAMRWB_IOmode; + size_t numFramesProduced; IVAS_DATA_BUFFER rtpPacket; IVAS_RTP_UNPACK_HANDLE hUnpack; IVAS_RTP_UNPACK_CONFIG unpackCfg; PI_DATA_DEPACKER_STATE piDataDepackerState; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO srInfo; +#endif } IVAS_RTP; static void IVAS_RTP_Term( IVAS_RTP *rtp ) @@ -210,6 +223,8 @@ static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, FILE *f_rtpstream ) memset( rtp, 0, sizeof( IVAS_RTP ) ); + rtp->unpackCfg.jitterMarginMs = JITTER_DURATION_MS; + error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); rtp->f_rtpstream = f_rtpstream; @@ -223,11 +238,9 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ { ivas_error error = IVAS_ERR_OK; IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; - uint32_t packetLen = 0u; - bool speechLostIndicated = false; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO srInfo; -#endif + size_t packetLen = 0u; + IVAS_RTP_CODEC codecId = IVAS_RTP_IVAS; + bool isAMRWB_IOmode = false; packedFrame.buffer = au; packedFrame.capacity = ( IVAS_MAX_BITS_PER_FRAME / 8 ); @@ -245,7 +258,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ } nread = fread( rtp->packet, sizeof( uint8_t ), packetLen, rtp->f_rtpstream ); /* Read Packet */ - if ( nread == 0 ) + if ( nread < packetLen ) { return IVAS_ERR_END_OF_FILE; } @@ -257,7 +270,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ &rtp->numFramesInPacket, &rtp->numPiDataInPacket, &rtp->remoteRequestBitmap ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "failed to unpack RTP packet\n" ); + fprintf( stderr, "failed to unpack RTP packet error = %s\n", ivas_error_to_string( error ) ); return error; } @@ -279,9 +292,9 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ } #ifdef RTP_S4_251135_CR26253_0016_REV1 - error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &rtp->codecId, &srInfo, &packedFrame, rtpTimeStamp, rtpSequenceNumber, &speechLostIndicated ); + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &rtp->srInfo, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); #else - error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &rtp->codecId, &packedFrame, rtpTimeStamp, rtpSequenceNumber, &speechLostIndicated ); + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); #endif if ( error != IVAS_ERR_OK ) { @@ -289,9 +302,31 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ return error; } - *auSizeBits = (int16_t) ( packedFrame.length * 8 ); - *qBit = !speechLostIndicated; + rtp->restartNeeded = false; + if ( *auSizeBits == 0 ) + { + /* NO_DATA_FRAME/SPEECH_LOST for IVAS and EVS is indicated by same bits + Do not restart decoder on codec/amrwb mode change in this case */ + } + else + { + rtp->restartNeeded = ( rtp->codecId != codecId ) || + ( codecId == IVAS_RTP_EVS && ( rtp->isAMRWB_IOmode != isAMRWB_IOmode ) ); + + if ( rtp->restartNeeded ) + { + fprintf( stdout, "\nRTP packet codec changed %s -> %s\n", + ( rtp->codecId == IVAS_RTP_EVS ) ? ( rtp->isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS", + ( codecId == IVAS_RTP_EVS ) ? ( isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS" ); + } + + rtp->codecId = codecId; + rtp->isAMRWB_IOmode = isAMRWB_IOmode; + } + + *qBit = !rtp->speechLostIndicated; rtp->numFramesInPacket--; + rtp->numFramesProduced++; *nextPacketRcvTime_ms += 20; while ( rtp->nProcPiData < rtp->nReadPiData && @@ -305,13 +340,17 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ { case IVAS_PI_SCENE_ORIENTATION: { - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, &piData->data.scene.orientation ); + IVAS_QUATERNION *quat = &piData->data.scene.orientation; + DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: { - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, &piData->data.deviceCompensated.orientation ); + IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; + DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; @@ -339,7 +378,6 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ static bool parseCmdlIVAS_dec( int16_t argc, char **argv, DecArguments *arg ); static void usage_dec( void ); static ivas_error decodeG192( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); -static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary, IVAS_DEC_HANDLE hIvasDec, const IVAS_AUDIO_CONFIG OutputConfig, const int32_t output_Fs ); #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); @@ -348,6 +386,117 @@ static IVAS_DEC_FORCED_REND_MODE parseForcedRendModeDec( char *forcedRendModeCha #endif static void do_object_editing( IVAS_EDITABLE_PARAMETERS *editableParameters, ObjectEditFileReader *objectEditFileReader ); +#ifdef IVAS_RTPDUMP +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE *phIvasDec, int16_t *pcmBuf ); + +static int restartDecoder( + IVAS_DEC_HANDLE *phIvasDec, + IVAS_DEC_MODE codec, + DecArguments *arg, + IVAS_RENDER_CONFIG_DATA *renderConfig, + IVAS_CUSTOM_LS_DATA *hLsCustomData ) +{ + ivas_error error = IVAS_ERR_OK; + IVAS_DEC_HANDLE hIvasDec; + + if ( phIvasDec == NULL ) + { + return -1; + } + + if ( NULL != *phIvasDec ) + { + IVAS_DEC_Close( phIvasDec ); + } + + if ( ( error = IVAS_DEC_Open( phIvasDec, codec ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "Open failed: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + arg->decMode = codec; + + hIvasDec = *phIvasDec; + + uint16_t aeID = arg->aeSequence.count > 0 ? arg->aeSequence.pID[0] : 65535; + + IVAS_AUDIO_CONFIG outputConfig = ( codec == IVAS_DEC_MODE_IVAS ) ? arg->outputConfig : IVAS_AUDIO_CONFIG_MONO; + if ( ( error = IVAS_DEC_Configure( hIvasDec, arg->output_Fs, outputConfig, arg->renderFramesize, arg->customLsOutputEnabled, arg->hrtfReaderEnabled, + arg->enableHeadRotation, arg->enableExternalOrientation, arg->orientation_tracking, arg->renderConfigEnabled, arg->non_diegetic_pan_enabled, + arg->non_diegetic_pan_gain, arg->dpidEnabled, aeID, arg->objEditEnabled, arg->delayCompensationEnabled ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + if ( ( error = IVAS_DEC_GetRenderFramesize( hIvasDec, &arg->renderFramesize ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + if ( arg->outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || arg->outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + { + if ( ( error = IVAS_DEC_EnableSplitRendering( hIvasDec ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nSplit rendering configure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + if ( ( error = IVAS_DEC_GetRenderFramesize( hIvasDec, &arg->renderFramesize ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + arg->enableHeadRotation = true; + } + + if ( arg->voipMode ) + { + if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, JITTER_DURATION_MS, arg->inputFormat ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nCould not enable VOIP: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + } + + if ( ( error = IVAS_DEC_PrintConfig( hIvasDec, 1, arg->voipMode ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nIVAS_DEC_PrintConfig failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + if ( arg->renderConfigEnabled && renderConfig != NULL ) + { + if ( ( error = IVAS_DEC_FeedRenderConfig( hIvasDec, *renderConfig ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nIVAS_DEC_FeedRenderConfig failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + } + + if ( arg->customLsOutputEnabled && hLsCustomData != NULL ) + { + if ( ( error = IVAS_DEC_FeedCustomLsData( hIvasDec, *hLsCustomData ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nIVAS_DEC_FeedCustomLsData failed: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + } + + return 0; + +cleanup: + IVAS_DEC_Close( phIvasDec ); + + return -1; +} +#else +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); +#endif + /*------------------------------------------------------------------------------------------* * main() * @@ -648,7 +797,11 @@ int main( if ( arg.voipMode ) { +#ifdef IVAS_RTPDUMP + if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, JITTER_DURATION_MS, arg.inputFormat ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, 60, arg.inputFormat ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "\nCould not enable VOIP: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; @@ -920,7 +1073,11 @@ int main( if ( arg.voipMode ) { +#ifdef IVAS_RTPDUMP + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, &hIvasDec, pcmBuf ); +#else error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, hIvasDec, pcmBuf ); +#endif } else { @@ -3178,7 +3335,11 @@ static ivas_error decodeVoIP( RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, +#ifdef IVAS_RTPDUMP + IVAS_DEC_HANDLE *phIvasDec, +#else IVAS_DEC_HANDLE hIvasDec, +#endif int16_t *pcmBuf ) { bool decodingFailed = true; /* Assume failure until cleanup is reached without errors */ @@ -3212,7 +3373,8 @@ static ivas_error decodeVoIP( int16_t i; FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP - IVAS_RTP ivasRtp; + IVAS_RTP ivasRtp = {0}; + IVAS_DEC_HANDLE hIvasDec = *phIvasDec; #else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; @@ -3375,6 +3537,27 @@ static ivas_error decodeVoIP( { nSamplesRendered = 0; +#ifdef IVAS_RTPDUMP + if ( ivasRtp.restartNeeded ) + { + IVAS_DEC_MODE newCodecInPacket = ( ivasRtp.codecId == IVAS_RTP_EVS ) ? IVAS_DEC_MODE_EVS : IVAS_DEC_MODE_IVAS; + int err = restartDecoder( + &hIvasDec, + newCodecInPacket, + &arg, + NULL, /* ToDo : Provide rendererConfig */ + NULL /* ToDo : Provide LS Custom Data */ + ); + if ( err < 0 ) + { + fprintf( stderr, "\nFailed to restart decoder from %d to %d\n", arg.decMode, newCodecInPacket ); + goto cleanup; + } + *phIvasDec = hIvasDec; /* Update for main()' s free */ + ivasRtp.restartNeeded = false; + } +#endif + /* reference vector */ if ( arg.enableReferenceVectorTracking && vec_pos_update == 0 ) { diff --git a/apps/encoder.c b/apps/encoder.c index 861250a5a0..14db97bc1a 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -223,6 +223,17 @@ int main( #ifdef IVAS_RTPDUMP FILE *f_rtpstream = NULL; IVAS_RTP_PACK_HANDLE hPack = NULL; + + uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; + uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; + IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; + uint32_t numFramesInPayload = 0, packetLength = 0; + + packedFrame.capacity = sizeof( au ); + packedFrame.buffer = au; + rtpPacket.capacity = sizeof( packet ); + rtpPacket.buffer = packet; #endif /*------------------------------------------------------------------------------------------* @@ -729,11 +740,7 @@ int main( goto cleanup; } -#ifdef IVAS_RTPDUMP - if ( ( numSamplesRead == 0 ) && ( IVAS_RTP_PACK_GetNumFrames( hPack ) == 0 ) ) -#else if ( numSamplesRead == 0 ) -#endif { /* end of input data */ break; @@ -863,19 +870,12 @@ int main( #ifdef IVAS_RTPDUMP if ( hPack ) { - uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; - uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * MAX_FRAMES_PER_RTP_PACKET]; - uint32_t n = numBits / 8, x = 0, numFramesInPayload = 0, packetLength = 0; + uint32_t n = numBits / 8, x = 0; uint16_t *bitstrm = bitStream; - IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; - IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; - - packedFrame.capacity = sizeof( au ); - packedFrame.buffer = au; - rtpPacket.capacity = sizeof( packet ); - rtpPacket.buffer = packet; /* Pack Encoded Stream */ + packedFrame.length = 0; + rtpPacket.length = 0; while ( n ) { x = ( ( bitstrm[0] << 7 ) | ( bitstrm[1] << 6 ) | ( bitstrm[2] << 5 ) | ( bitstrm[3] << 4 ) | @@ -1059,6 +1059,21 @@ cleanup: #ifdef IVAS_RTPDUMP if ( hPack ) { + /* Complete the last packet */ + if ( IVAS_RTP_PACK_GetNumFrames( hPack ) != 0 ) + { + if ( ( error = IVAS_RTP_PACK_GetPacket( hPack, &rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while packing RTP Packet\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } + + /* File Format = Packet Length (uint32_t) + Packet bytes */ + packetLength = (uint32_t) rtpPacket.length; + fwrite( &packetLength, sizeof( packetLength ), 1, f_rtpstream ); + fwrite( rtpPacket.buffer, sizeof( uint8_t ), packetLength, f_rtpstream ); + } + IVAS_RTP_PACK_Close( &hPack ); } @@ -1965,9 +1980,9 @@ static bool parseCmdlIVAS_enc( else { arg->numFramesPerPacket = atoi( argv[i++] ); - if ( arg->numFramesPerPacket > MAX_FRAMES_PER_RTP_PACKET ) + if ( arg->numFramesPerPacket > IVAS_MAX_FRAMES_PER_RTP_PACKET ) { - fprintf( stderr, "numFramesPerPacket(%d) exceeds max frames per packet (%d) \n", arg->numFramesPerPacket, MAX_FRAMES_PER_RTP_PACKET ); + fprintf( stderr, "numFramesPerPacket(%d) exceeds max frames per packet (%d) \n", arg->numFramesPerPacket, IVAS_MAX_FRAMES_PER_RTP_PACKET ); arg->numFramesPerPacket = 1; } } diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index f9c9646c9f..11e208218a 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -339,10 +339,9 @@ void IVAS_DEC_Close( } /* destroy Split binaural renderer (ISAR) handle */ - ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); - if ( ( *phIvasDec )->st_ivas ) { + ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); ivas_destroy_dec( ( *phIvasDec )->st_ivas ); ( *phIvasDec )->st_ivas = NULL; } diff --git a/lib_util/ivas_bpool.c b/lib_util/ivas_bpool.c index cea33509ff..23754a0c11 100644 --- a/lib_util/ivas_bpool.c +++ b/lib_util/ivas_bpool.c @@ -89,7 +89,7 @@ ivas_error BPOOL_Create( BPOOL_HANDLE *pHandle, size_t bufferSize, uint32_t numB void BPOOL_Destroy( BPOOL_HANDLE *pHandle ) { - if ( pHandle != NULL ) + if ( ( pHandle != NULL ) && ( *pHandle != NULL ) ) { mtx_destroy( &( *pHandle )->lock ); free( *pHandle ); @@ -146,9 +146,9 @@ ivas_error BPOOL_FreeBuffer( BPOOL_HANDLE handle, void *dataPtr ) /* return the number of free buffers available atm in the pool */ uint32_t BPOOL_AvailableBuffers( BPOOL_HANDLE handle ) { - uint32_t idx; + uint32_t numFreeBuffers; mtx_lock( &handle->lock ); - idx = handle->numFreeBuffers; + numFreeBuffers = handle->numFreeBuffers; mtx_unlock( &handle->lock ); - return idx; + return numFreeBuffers; } diff --git a/lib_util/ivas_bpool.h b/lib_util/ivas_bpool.h index c4cd45a736..d929ae7415 100644 --- a/lib_util/ivas_bpool.h +++ b/lib_util/ivas_bpool.h @@ -41,18 +41,18 @@ typedef struct BPOOL *BPOOL_HANDLE; /* Create a buffer pool with given element size and max number of buffers */ -ivas_error BPOOL_Create(BPOOL_HANDLE *pHandle, size_t bufferSize, uint32_t numBuffers); +ivas_error BPOOL_Create( BPOOL_HANDLE *pHandle, size_t bufferSize, uint32_t numBuffers ); /* Destroy the buffer pool and all free-up all allocated memory */ void BPOOL_Destroy( BPOOL_HANDLE *pHandle ); /* request a buffer from the pool */ -ivas_error BPOOL_GetBuffer(BPOOL_HANDLE handle, void **dataPtr ); +ivas_error BPOOL_GetBuffer( BPOOL_HANDLE handle, void **dataPtr ); /* return the buffer back to pool */ -ivas_error BPOOL_FreeBuffer(BPOOL_HANDLE handle, void *dataPtr); +ivas_error BPOOL_FreeBuffer( BPOOL_HANDLE handle, void *dataPtr ); /* return the number of free buffers available atm in the pool */ -uint32_t BPOOL_AvailableBuffers(BPOOL_HANDLE handle); +uint32_t BPOOL_AvailableBuffers( BPOOL_HANDLE handle ); #endif /* IVAS_BPOOL_H */ diff --git a/lib_util/ivas_queue.c b/lib_util/ivas_queue.c index db038938a5..b72fcee9e4 100644 --- a/lib_util/ivas_queue.c +++ b/lib_util/ivas_queue.c @@ -64,10 +64,10 @@ ivas_error QUEUE_Create( QUEUE_HANDLE *pHandle ) return IVAS_ERR_OK; } -/* Destroy the queue and all free-up all allocated memory */ +/* Destroy the queue and free-up all allocated memory */ void QUEUE_Destroy( QUEUE_HANDLE *pHandle ) { - if ( pHandle != NULL ) + if ( ( pHandle != NULL ) && ( *pHandle != NULL ) ) { mtx_destroy( &( *pHandle )->lock ); free( *pHandle ); @@ -92,9 +92,9 @@ void QUEUE_Push( QUEUE_HANDLE handle, NODE *node ) } /* return the buffer back to pool */ -NODE * QUEUE_Pop( QUEUE_HANDLE handle ) +NODE *QUEUE_Pop( QUEUE_HANDLE handle ) { - NODE * node; + NODE *node; mtx_lock( &handle->lock ); node = handle->front; handle->front = handle->front->next; @@ -110,17 +110,29 @@ NODE * QUEUE_Pop( QUEUE_HANDLE handle ) /* returns the first element in the queue */ NODE *QUEUE_Front( QUEUE_HANDLE handle ) { - return handle->front; + NODE *node; + mtx_lock( &handle->lock ); + node = handle->front; + mtx_unlock( &handle->lock ); + return node; } /* returns the last element in the queue */ NODE *QUEUE_Back( QUEUE_HANDLE handle ) { - return handle->back; + NODE *node; + mtx_lock( &handle->lock ); + node = handle->back; + mtx_unlock( &handle->lock ); + return node; } /* return the number of elements in the queue */ size_t QUEUE_Size( QUEUE_HANDLE handle ) { - return handle->size; + uint32_t numNodes; + mtx_lock( &handle->lock ); + numNodes = handle->size; + mtx_unlock( &handle->lock ); + return numNodes; } diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 6b8ebb470a..597258f9f1 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -75,7 +75,7 @@ extern "C" * */ -#define MAX_FRAMES_PER_RTP_PACKET ( 8 ) /* Max supported frames per RTP packet */ +#define IVAS_MAX_FRAMES_PER_RTP_PACKET ( 8 ) /* Max supported frames per RTP packet */ /* It is difficult to decide the RTP Payload buffer's capacity intrinsically however computing * using the maximum frame size and all currently supported PI data present gives a crude @@ -301,17 +301,6 @@ extern "C" uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ } IVAS_RTP_PACK_CONFIG; - /* Update the RTP Header structure */ - ivas_error IVAS_RTP_PACK_UpdateHeader( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ - uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ - uint8_t numCC, /* i : numCC indicates no. of contributing sources */ - uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ - uint16_t extHeaderId, /* i : extension header ID */ - uint16_t numExtensionBytes, /* i : length of the extension data */ - uint8_t *extData /* i : extension data pointer */ - ); - /* Open an instance of the RTP packer and return a handle to rtp packer on success * error code is retured on failure and handle is set to NULL */ @@ -325,6 +314,17 @@ extern "C" IVAS_RTP_PACK_HANDLE *phIvasPack /* i/o : pointer to an IVAS rtp packer handle to be closed */ ); + /* Update the RTP Header structure */ + ivas_error IVAS_RTP_PACK_UpdateHeader( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ + uint8_t numCC, /* i : numCC indicates no. of contributing sources */ + uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ + uint16_t extHeaderId, /* i : extension header ID */ + uint16_t numExtensionBytes, /* i : length of the extension data */ + uint8_t *extData /* i : extension data pointer */ + ); + /* Add requests for remote sender using a key value pair api * each key must be provided with a corresponding value type * @@ -408,7 +408,7 @@ extern "C" /* Initial configuration for rtp unpacker */ typedef struct { - uint32_t maxNumberOfPayloadsInFifo; /* input payload fifo 's max length */ + uint32_t jitterMarginMs; /* expected max jitter duration in milliseconds */ } IVAS_RTP_UNPACK_CONFIG; /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success @@ -483,8 +483,7 @@ extern "C" ); /* Push a received rtp Ivas Packet to unpacker to extract number of frames, pi data and - * any remote request present in the Packet. Caller must extract RTP header and header - * extension and feed Ivas Packet alongwith RTP Timestamp and sequence number. + * any remote request present in the Packet. * * In case of DTX transmission modes, the number of frames in packet will be reduced by * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall @@ -492,8 +491,7 @@ extern "C" * * Example usage : - * ================== - * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, rtpTs, seqNum, - * &nFrames, &nPiData, &reqBitmap); + * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, &nFrames, &nPiData, &reqBitmap); * if (err != IVAS_ERR_OK) { return err; } * * // Read the frames in packet and feed to decoder @@ -560,12 +558,14 @@ extern "C" IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ - uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ - uint16_t *sequenceNumber, /* o : sequence number from rtp header */ - bool *speechLostIndicated /* o : Is current frame indicated as Lost */ + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ + int16_t *frameSizeInBits, /* o : exact frame size in bits (AMRWB IO) */ + uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ + uint16_t *sequenceNumber, /* o : sequence number from rtp header */ + bool *speechLostIndicated, /* o : Is current frame indicated as Lost */ + bool *isAMRWB_IOmode /* o : Is AMRWB_IO mode EVS frame */ ); /* Pull a single PI data from rtp unpacker instance for current packet diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h index b74bdbe8d5..0acfcd2161 100644 --- a/lib_util/ivas_rtp_internal.h +++ b/lib_util/ivas_rtp_internal.h @@ -75,6 +75,7 @@ enum IVAS_RTP_HEADER_BITS EBYTE_TOC_HEADER_BIT = 0x80, /* Ebyte/ToC indicator H bit, 1 = EByte, 0 = ToC */ TOC_HEADER_FOLLOWS = 0x40, /* ToC header byte is followed by another header byte */ + TOC_INDICATE_NO_DATA_AMRWB = 0x3F, /* ToC byte indicates NO_DATA in DTX mode for AMRWB */ TOC_INDICATE_NO_DATA = 0x0F, /* ToC header byte indicates NO_DATA in DTX mode */ TOC_INDICATE_IVAS = 0x10, /* ToC header byte indicates IVAS data */ TOC_INDICATE_EVS = 0x00, /* ToC header byte indicates EVS data */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 7bbc86171d..367aea06c8 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -112,14 +112,24 @@ typedef struct FRAME_NODE uint32_t tocNumBytes; /* valid ToC bytes (1 or 2) */ } FRAME_NODE; +static void initFrameNode( FRAME_NODE *node ) +{ + node->next = NULL; + initPiDataFrame( &node->piDataFrame ); + node->auNumBits = 0; + node->tocNumBytes = 0; +} + struct IVAS_RTP_PACK { - mtx_t lock; /* Lock to handle concurrent API invocation */ - RTP_HEADER header; - BPOOL_HANDLE nodePool; - IVAS_RTP_PACK_CONFIG initConfig; - IVAS_RTP_REQUEST_VALUE requests[IVAS_REQUEST_MAX]; - QUEUE_HANDLE frameQ; /* frames queue */ + mtx_t apilock; /* Lock to handle concurrent API invocation */ + bool amrwbIOMode; /* If EVS is an AMRWB-IO mode frame */ + RTP_HEADER header; /* RTP Header Params */ + BPOOL_HANDLE packNodePool; /* Buffer pool to allocate FRAME_NODEs */ + PIDATA_FRAME piDataCache; /* temporary cache for PI data pushed before any frame is available in Queue */ + IVAS_RTP_PACK_CONFIG initConfig; /* Initial Configuration for Unpacker */ + IVAS_RTP_REQUEST_VALUE requests[IVAS_REQUEST_MAX]; /* Requests Storage */ + QUEUE_HANDLE frameQ; /* frames queue for FRAME_NODEs */ }; typedef struct @@ -127,9 +137,10 @@ typedef struct IVAS_RTP_CODEC codecId; /* Coded frame type (IVAS/EVS) */ uint32_t auNumBits; /* Actual frame size in bits */ bool speechLostIndicated; /* Speech Lost indicated */ + bool isAMRWB_IOmode; /* EVS frame is AMRWB_IO */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO srInfo; /* Split Rendering Info */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_RTP_SR_INFO srInfo; /* Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } TOC_INFO; typedef struct PIDATA_NODE @@ -150,9 +161,11 @@ typedef struct UNPACK_NODE struct IVAS_RTP_UNPACK { - mtx_t lock; /* Lock to handle concurrent API invocation */ + mtx_t apilock; /* Lock to handle concurrent API invocation */ + size_t maxNumberOfFrames; + size_t maxNumberOfPiData; RTP_HEADER header; - BPOOL_HANDLE nodePool; + BPOOL_HANDLE unpackNodePool; BPOOL_HANDLE piDataPool; IVAS_RTP_UNPACK_CONFIG initConfig; IVAS_RTP_REQUEST_VALUE requests[IVAS_REQUEST_MAX]; @@ -170,7 +183,7 @@ static const uint32_t evsFrameSizeInBits[] = { }; static const uint32_t amrWBIOFrameSizeInBits[] = { - 132, 177, 253, 285, 317, 365, 397, 461, 477, 40 + 132, 177, 253, 285, 317, 365, 397, 461, 477, 35 }; @@ -400,7 +413,7 @@ static ivas_error UnpackRtpPacket( return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Extension Header indicated but insufficient input buffer" ); } - header->extData = realloc( header->extData, header->numExtUnits * 4 ); + header->extData = realloc( header->extData, header->numExtUnits * 4 ); if ( header->extData == NULL ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "failed to allocate extdata" ); @@ -418,7 +431,8 @@ static ivas_error getBitrateFromCodecAndFrameSize( uint32_t frameLengthBytes, uint32_t *bitrateKbps, uint32_t *frameLenInBits, - uint8_t *tocByte ) + uint8_t *tocByte, + bool *isAmrwbIOMode ) { size_t n; *bitrateKbps = 0; @@ -457,6 +471,7 @@ static ivas_error getBitrateFromCodecAndFrameSize( uint32_t lengthInBits = amrWBIOFrameSizeInBits[n]; if ( ( ( lengthInBits + 7 ) / 8 ) == frameLengthBytes ) { + *isAmrwbIOMode = true; *bitrateKbps = ( lengthInBits * IVAS_NUM_FRAMES_PER_SEC ); /* bitrate in kbps */ *frameLenInBits = lengthInBits; /* frame length in bits */ *tocByte = TOC_INDICATE_AMRWB | ( n & MASK_4BIT ); /* bitrate index */ @@ -486,16 +501,17 @@ ivas_error IVAS_RTP_PACK_Open( return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtppack handle" ); } - error = BPOOL_Create( &hPack->nodePool, sizeof( FRAME_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); + error = BPOOL_Create( &hPack->packNodePool, sizeof( FRAME_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); ERR_CHECK_RETURN( error ); error = QUEUE_Create( &hPack->frameQ ); ERR_CHECK_RETURN( error ); - mtx_init( &hPack->lock, 0 ); + mtx_init( &hPack->apilock, 0 ); hPack->initConfig = *config; initRequests( hPack->requests ); InitRtpHeader( &hPack->header ); + initPiDataFrame( &hPack->piDataCache ); *phPack = hPack; return IVAS_ERR_OK; } @@ -515,9 +531,9 @@ void IVAS_RTP_PACK_Close( hPack = *phPack; QUEUE_Destroy( &hPack->frameQ ); - mtx_destroy( &hPack->lock ); - BPOOL_Destroy( &hPack->nodePool ); - free ( hPack->header.extData ); + mtx_destroy( &hPack->apilock ); + BPOOL_Destroy( &hPack->packNodePool ); + free( hPack->header.extData ); free( hPack ); *phPack = NULL; } @@ -839,36 +855,28 @@ static ivas_error getSRToCByte( } #endif /* RTP_S4_251135_CR26253_0016_REV1 */ -static ivas_error addNewFrameNode( - IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ - FRAME_NODE **newNode ) +static void addPackedPiDataToFrame( PIDATA_FRAME *piDataFrm, const PIDATA_PACKED *packedPiData, uint32_t piDataType ) { - FRAME_NODE *node = NULL; - ivas_error error = BPOOL_GetBuffer( hPack->nodePool, (void **) &node ); - ERR_CHECK_RETURN( error ); - - node->auNumBits = 0; - node->tocNumBytes = 0; - node->next = NULL; - - initPiDataFrame( &node->piDataFrame ); + piDataFrm->piData[piDataType].size = packedPiData->size; + memcpy( piDataFrm->piData[piDataType].data, packedPiData->data, packedPiData->size ); - /* Add to frames FiFo */ - QUEUE_Push( hPack->frameQ, (NODE *) node ); + /* Indicate th PI data type presense in bitmap, If the same pi data type is + already pushed for this frame, it is overwritten with the newer data */ + if ( !( piDataFrm->piDataBitmap & ( 1u << piDataType ) ) ) + { + piDataFrm->piDataBitmap |= ( 1u << piDataType ); + piDataFrm->numPiDataAvailable++; + } - *newNode = node; - return IVAS_ERR_OK; + /* Atleast one valid PI data is now pushed for this frame, clear No PI data + for this frame */ + if ( piDataFrm->piDataBitmap & ( 1u << IVAS_PI_NO_DATA ) ) + { + piDataFrm->piDataBitmap &= ~( 1u << IVAS_PI_NO_DATA ); + piDataFrm->numPiDataAvailable--; + } } -/* Push a single IVAS/EVS frame to rtp packer - * - * If multiple frames per RTP Payload are desired, multiple frames must be explicitly - * pushed before a call to IVAS_RTP_PACK_GetPacket to generate a rtp packet. - * - * It is possible to have variable frames per packet until maxFramesPerPacket frames - * if IVAS_RTP_PACK_GetPacket is invoked asyncronously w.r.t this api. - * - */ ivas_error IVAS_RTP_PACK_PushFrame( IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ @@ -882,42 +890,73 @@ ivas_error IVAS_RTP_PACK_PushFrame( uint32_t bitrate = 0; uint32_t frameLengthInBits = 0; uint8_t tocByte = 0; - FRAME_NODE *node = (FRAME_NODE *) QUEUE_Back( hPack->frameQ ); - - /* Check if a node was added due to Generic PI Data push */ - if ( ( node == NULL ) || ( node->tocNumBytes != 0 ) ) - { - error = addNewFrameNode( hPack, &node ); - ERR_CHECK_RETURN( error ); - } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + uint8_t tocByte1 = 0; +#endif + FRAME_NODE *node = NULL; + PIDATA_FRAME *piDataFrame = &hPack->piDataCache; + uint32_t piDataType = 0; if ( frameBuffer == NULL || frameBuffer->length == 0 ) { /* Indicates a NO_DATA_FRAME in case of DTX mode */ - node->toc[0] = TOC_INDICATE_NO_DATA; - node->tocNumBytes = 1; - node->auNumBits = 0; + tocByte = ( hPack->amrwbIOMode ) ? TOC_INDICATE_NO_DATA_AMRWB : TOC_INDICATE_NO_DATA; + } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + else if ( srInfo != NULL && srInfo->valid ) + { + tocByte = TOC_INDICATE_SR; + error = getSRToCByte( srInfo, bitrate, &tocByte1 ); + ERR_CHECK_RETURN( error ); } +#endif else { - error = getBitrateFromCodecAndFrameSize( codecId, frameBuffer->length, &bitrate, &frameLengthInBits, &tocByte ); + error = getBitrateFromCodecAndFrameSize( codecId, frameBuffer->length, &bitrate, &frameLengthInBits, &tocByte, &hPack->amrwbIOMode ); ERR_CHECK_RETURN( error ); + } - node->toc[0] = tocByte; - node->tocNumBytes = 1; - node->auNumBits = frameLengthInBits; - memcpy( node->au, frameBuffer->buffer, frameBuffer->length ); + /* Allocate a new frame node for this frame */ + error = BPOOL_GetBuffer( hPack->packNodePool, (void **) &node ); + ERR_CHECK_RETURN( error ); + initFrameNode( node ); + + /* Set ToC byte & frame */ + node->toc[node->tocNumBytes++] = tocByte; #ifdef RTP_S4_251135_CR26253_0016_REV1 - if ( srInfo != NULL && srInfo->valid ) + if ( srInfo != NULL && srInfo->valid ) + { + node->toc[node->tocNumBytes++] = tocByte1; + } +#endif + + node->auNumBits = frameLengthInBits; + if ( frameBuffer != NULL ) + { + memcpy( node->au, frameBuffer->buffer, frameBuffer->length ); + } + + /* If some Pi data is is Cache add to Frame Node's associated Pi Data */ + mtx_lock( &hPack->apilock ); /* Lock to prevent access to shared cache */ + if ( piDataFrame->numPiDataAvailable ) + { + uint32_t bitmap = piDataFrame->piDataBitmap; + for ( piDataType = 0; piDataType < 32 && ( bitmap != 0 ); piDataType++ ) { - node->toc[0] = TOC_INDICATE_SR; - error = getSRToCByte( srInfo, bitrate, &node->toc[1] ); - ERR_CHECK_RETURN( error ); - node->tocNumBytes = 2; + uint32_t mask = ( 1u << piDataType ); + if ( bitmap & mask ) + { + bitmap &= ~mask; /* Mask out this pi type to indicate processed */ + addPackedPiDataToFrame( &node->piDataFrame, &piDataFrame->piData[piDataType], piDataType ); + } } -#endif + initPiDataFrame( piDataFrame ); /* Reset Cache */ } + mtx_unlock( &hPack->apilock ); + + /* Add to frames FiFo */ + QUEUE_Push( hPack->frameQ, (NODE *) node ); return IVAS_ERR_OK; } @@ -926,7 +965,12 @@ uint32_t IVAS_RTP_PACK_GetNumFrames( IVAS_RTP_PACK_HANDLE hPack /* i/o : IVAS rtp packer handle */ ) { - return QUEUE_Size( hPack->frameQ ); + uint32_t nFrames = 0; + if ( hPack ) + { + nFrames = QUEUE_Size( hPack->frameQ ); + } + return nFrames; } /* Push single PI data to rtp packer @@ -941,11 +985,8 @@ ivas_error IVAS_RTP_PACK_PushPiData( const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ ) { - IVAS_PI_TYPE piDataType; ivas_error error = IVAS_ERR_OK; - FRAME_NODE *node = (FRAME_NODE *) QUEUE_Back( hPack->frameQ ); - PIDATA_FRAME *piDataFrm = NULL; - uint8_t PM = 0; + PIDATA_PACKED packedPiData; if ( data == NULL || data->piDataType >= IVAS_PI_NO_DATA || /* NO_PI_DATA cannot be provided by user, it is generated in packing */ @@ -954,137 +995,120 @@ ivas_error IVAS_RTP_PACK_PushPiData( return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Wrong PI Data provided" ); } - /* No frame in Queue to associate PI Data, create one */ - if ( node == NULL ) - { - /* PI data received before any frame in queue is generic */ - PM = PI_HEADER_PM_GENERIC; - - /* Add a new node in frame queue, toc is still zero indicating frame is expected */ - error = addNewFrameNode( hPack, &node ); - ERR_CHECK_RETURN( error ); - } - - piDataFrm = &node->piDataFrame; - piDataType = data->piDataType; - - error = PI_PackData( data, &piDataFrm->piData[piDataType], PM ); + error = PI_PackData( data, &packedPiData, 0 ); ERR_CHECK_RETURN( error ); - /* Indicate th PI data type presense in bitmap, If the same pi data type is - already pushed for this frame, it is overwritten with the newer data */ - if ( !( piDataFrm->piDataBitmap & ( 1u << piDataType ) ) ) - { - piDataFrm->piDataBitmap |= ( 1u << piDataType ); - piDataFrm->numPiDataAvailable++; - } - - /* Atleast one valid PI data is now pushed for this frame, clear No PI data - for this frame */ - if ( piDataFrm->piDataBitmap & ( 1u << IVAS_PI_NO_DATA ) ) + mtx_lock( &hPack->apilock ); /* Lock to prevent access to shared cache */ { - piDataFrm->piDataBitmap &= ~( 1u << IVAS_PI_NO_DATA ); - piDataFrm->numPiDataAvailable--; + FRAME_NODE *node = (FRAME_NODE *) QUEUE_Back( hPack->frameQ ); + /* use cache if no frame in Queue to associate PI data */ + PIDATA_FRAME *piDataFrm = ( node != NULL ) ? &node->piDataFrame : &hPack->piDataCache; + addPackedPiDataToFrame( piDataFrm, &packedPiData, data->piDataType ); } + mtx_unlock( &hPack->apilock ); return IVAS_ERR_OK; } -/* Generate a rtp packet using available pushed frames - * - * Available remote requests, pi data and frames will be packed into a rtp packet. If no - * frame is pushed before call to this api, NO_DATA_FRAME will be generated - * - */ +#define WRITE_BYTE_PAYLOAD_OR_EXIT( payload, byte ) \ + if ( payload->length < payload->capacity ) \ + { \ + uint8_t _byte = ( byte ); \ + payload->buffer[payload->length++] = _byte; \ + } \ + else \ + { \ + error = IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); \ + goto err_exit; \ + } + ivas_error IVAS_RTP_PACK_GetPayload( IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ - IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ + IVAS_DATA_BUFFER *payload, /* o : encapsulated rtp payload */ uint32_t *numFramesInPayload /* o : no. of frames in payload */ ) { uint32_t n = 0, numFrame = 0; ivas_error error = IVAS_ERR_OK; uint32_t numPiDataPresent = 0; - FRAME_NODE *head = (FRAME_NODE *) QUEUE_Front( hPack->frameQ ); - FRAME_NODE *node = NULL; - size_t nBytes = packet->length, numEBytes = 0; + FRAME_NODE *availableFrameNodes[IVAS_MAX_FRAMES_PER_RTP_PACKET]; + size_t numEBytes = 0; - if ( packet == NULL ) + if ( payload == NULL ) { return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Output data buffer is NULL" ); } - /* Calculate number of frames and PI data present */ - node = head; - for ( n = 0; n < hPack->initConfig.maxFramesPerPacket && ( NULL != node ); n++, node = node->next ) + /* Calculate number of frames to pack in this payload */ + numFrame = QUEUE_Size( hPack->frameQ ); + numFrame = ( numFrame > hPack->initConfig.maxFramesPerPacket ) ? hPack->initConfig.maxFramesPerPacket : numFrame; + *numFramesInPayload = numFrame; /* numFrames in Packet */ + + /* Collect all the frame nodes from FiFo */ + for ( n = 0; n < numFrame; n++ ) { - numFrame++; + FRAME_NODE *node = (FRAME_NODE *) QUEUE_Pop( hPack->frameQ ); + if ( node == NULL ) + { + assert( 0 ); /* catastrophic error, implementation issue somewhere */ + return IVAS_ERROR( IVAS_ERR_INTERNAL_FATAL, "NULL found in frame nodes" ); + } + /* Calculate number of PI data present in total */ numPiDataPresent += node->piDataFrame.numPiDataAvailable; + availableFrameNodes[n] = node; } - *numFramesInPayload = numFrame; /* numFrames in Packet */ /* IVAS Payload starts with E-Bytes */ - packEBytes( hPack, ( numPiDataPresent > 0 ), packet->capacity, &packet->buffer[packet->length], &numEBytes ); - nBytes += numEBytes; + packEBytes( hPack, ( numPiDataPresent > 0 ), payload->capacity, &payload->buffer[payload->length], &numEBytes ); + payload->length += numEBytes; /* ToC bytes (atleast 1 byte per frame, 2 if SR )*/ - node = head; - for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + for ( n = 0; n < numFrame; n++ ) { + FRAME_NODE *node = availableFrameNodes[n]; uint8_t fBit = ( n != ( numFrame - 1 ) ) ? TOC_HEADER_FOLLOWS : 0; /* Next ToC present */ - if ( nBytes < packet->capacity ) - { - packet->buffer[nBytes++] = ( node->toc[0] | fBit ); - } - else - { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); - } + WRITE_BYTE_PAYLOAD_OR_EXIT( payload, ( node->toc[0] | fBit ) ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 if ( node->tocNumBytes == 2 ) { - if ( nBytes < packet->capacity ) - { - packet->buffer[nBytes++] = node->toc[1]; - } - else - { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); - } + WRITE_BYTE_PAYLOAD_OR_EXIT( payload, node->toc[1] ); } +#endif } /* Frame Data */ - node = head; - for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + for ( n = 0; n < numFrame; n++ ) { - size_t len = ( node->auNumBits + 7 ) >> 3; /* zero padded length in bytes */ - if ( nBytes + len < packet->capacity ) + FRAME_NODE *node = availableFrameNodes[n]; + size_t frameLength = ( node->auNumBits + 7 ) >> 3; /* zero padded length in bytes */ + if ( payload->length + frameLength <= payload->capacity ) { - memcpy( &packet->buffer[nBytes], node->au, len ); + memcpy( &payload->buffer[payload->length], node->au, frameLength ); } else { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + error = IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + goto err_exit; } - nBytes += len; + payload->length += frameLength; } - packet->length = nBytes; /* update packet length before PI packing */ - /* PI Data */ if ( numPiDataPresent > 0 ) { bool skipPiData = false; + size_t nBytes = payload->length; uint32_t numPiDataWritten = 0; - node = head; - for ( n = 0; n < numFrame && ( NULL != node ); n++, node = node->next ) + for ( n = 0; n < numFrame; n++ ) { uint32_t piDataType = 0; + FRAME_NODE *node = availableFrameNodes[n]; PIDATA_FRAME *piDataFrame = &node->piDataFrame; uint32_t bitmap = piDataFrame->piDataBitmap; uint8_t PM = 0, PF = 0; + for ( piDataType = 0; piDataType < 32 && ( bitmap != 0 ); piDataType++ ) { uint32_t mask = ( 1u << piDataType ); @@ -1098,9 +1122,9 @@ ivas_error IVAS_RTP_PACK_GetPayload( PF = (uint8_t) ( ( bitmap == 0 && ( numPiDataWritten + 1 == numPiDataPresent ) ) ? PI_HEADER_PF_LAST : PI_HEADER_PF_NOT_LAST ); /* Last PI in Payload */ /* Update the first byte of PI Header with PF/PM */ piDataFrame->piData[piDataType].data[0] |= ( PF | PM ); - if ( nBytes + piDataFrame->piData[piDataType].size < packet->capacity ) + if ( nBytes + piDataFrame->piData[piDataType].size < payload->capacity ) { - memcpy( &packet->buffer[nBytes], piDataFrame->piData[piDataType].data, piDataFrame->piData[piDataType].size ); + memcpy( &payload->buffer[nBytes], piDataFrame->piData[piDataType].data, piDataFrame->piData[piDataType].size ); nBytes += piDataFrame->piData[piDataType].size; } else @@ -1113,25 +1137,23 @@ ivas_error IVAS_RTP_PACK_GetPayload( } if ( !skipPiData ) { - packet->length = nBytes; /* update packet length after PI packing */ + payload->length = nBytes; /* update payload length after PI packing */ } } +err_exit: /* Pop frames from Queue */ for ( n = 0; n < numFrame; n++ ) { - node = (FRAME_NODE *) QUEUE_Pop( hPack->frameQ ); - if ( node == NULL ) + if ( BPOOL_FreeBuffer( hPack->packNodePool, availableFrameNodes[n] ) != IVAS_ERR_OK ) { - assert( 0 ); /* catastrophic error, implementation issue */ - break; + assert( 0 ); /* catastrophic error if this fails */ + return IVAS_ERROR( IVAS_ERR_INTERNAL_FATAL, "frame node could not be freed" ); } - - error = BPOOL_FreeBuffer( hPack->nodePool, node ); - assert( error == IVAS_ERR_OK ); /* catastrophic error if this fails */ + availableFrameNodes[n] = NULL; } - return IVAS_ERR_OK; + return error; } ivas_error IVAS_RTP_PACK_GetPacket( @@ -1158,6 +1180,7 @@ ivas_error IVAS_RTP_UNPACK_Open( { ivas_error error = IVAS_ERR_OK; IVAS_RTP_UNPACK_HANDLE hUnpack; + if ( phUnpack == NULL || config == NULL ) { return IVAS_ERR_UNEXPECTED_NULL_POINTER; @@ -1169,10 +1192,15 @@ ivas_error IVAS_RTP_UNPACK_Open( return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtp unpack handle" ); } - error = BPOOL_Create( &hUnpack->nodePool, sizeof( UNPACK_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); + /* maxNumberOfFrames = Ping Pong RTP Packet with max frames per packet + any jitter adjustment frames */ + hUnpack->maxNumberOfFrames += IVAS_MAX_FRAMES_PER_RTP_PACKET * 2; + hUnpack->maxNumberOfFrames += ( ( config->jitterMarginMs * IVAS_NUM_FRAMES_PER_SEC + 1000 - 1 ) / 1000 ); /* Jitter in Packets + Ping/Pong */ + hUnpack->maxNumberOfPiData = hUnpack->maxNumberOfFrames * IVAS_PI_MAX_ID; + + error = BPOOL_Create( &hUnpack->unpackNodePool, sizeof( UNPACK_NODE ), hUnpack->maxNumberOfFrames ); ERR_CHECK_RETURN( error ); - error = BPOOL_Create( &hUnpack->piDataPool, sizeof( PIDATA_NODE ), IVAS_MAX_FRAMES_IN_QUEUE * IVAS_PI_MAX_ID ); + error = BPOOL_Create( &hUnpack->piDataPool, sizeof( PIDATA_NODE ), hUnpack->maxNumberOfPiData ); ERR_CHECK_RETURN( error ); error = QUEUE_Create( &hUnpack->frameQ ); @@ -1182,7 +1210,7 @@ ivas_error IVAS_RTP_UNPACK_Open( ERR_CHECK_RETURN( error ); hUnpack->initConfig = *config; - mtx_init( &hUnpack->lock, 0 ); + mtx_init( &hUnpack->apilock, 0 ); initRequests( hUnpack->requests ); @@ -1204,12 +1232,12 @@ void IVAS_RTP_UNPACK_Close( } hUnpack = *phUnpack; - mtx_destroy( &hUnpack->lock ); + mtx_destroy( &hUnpack->apilock ); QUEUE_Destroy( &hUnpack->frameQ ); QUEUE_Destroy( &hUnpack->piDataQ ); BPOOL_Destroy( &hUnpack->piDataPool ); - BPOOL_Destroy( &hUnpack->nodePool ); - free ( hUnpack->header.extData ); + BPOOL_Destroy( &hUnpack->unpackNodePool ); + free( hUnpack->header.extData ); free( hUnpack ); *phUnpack = NULL; } @@ -1394,6 +1422,7 @@ static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBy if ( FT == TOC_INDICATE_ARMWB_Q || FT == TOC_INDICATE_AMRWB ) { toc->codecId = IVAS_RTP_EVS; + toc->isAMRWB_IOmode = true; toc->speechLostIndicated = ( FT == TOC_INDICATE_ARMWB_Q ) ? true : false; /* Q bit = 0 for AMRWB, BR is valid */ if ( BR <= 9 ) { @@ -1604,7 +1633,7 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( UNPACK_NODE *node = NULL; /* Get a new node */ - error = BPOOL_GetBuffer( hUnpack->nodePool, (void **) &node ); + error = BPOOL_GetBuffer( hUnpack->unpackNodePool, (void **) &node ); ERR_CHECK_RETURN( error ); node->next = NULL; @@ -1613,7 +1642,7 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( node->toc = toc[n]; frameSizeBytes = ( node->toc.auNumBits + 7 ) / 8; - if ( nBytes + frameSizeBytes < payload->length ) + if ( nBytes + frameSizeBytes <= payload->length ) { memcpy( node->au, &payload->buffer[nBytes], frameSizeBytes ); nBytes += frameSizeBytes; @@ -1707,12 +1736,14 @@ ivas_error IVAS_RTP_UNPACK_PullFrame( IVAS_RTP_UNPACK_HANDLE hUnpack, /* i/o : IVAS rtp unpacker handle */ IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ - uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ - uint16_t *sequenceNumber, /* o : sequence number from rtp header */ - bool *speechLostIndicated /* o : Is current frame indicated as Lost */ + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ + int16_t *frameSizeInBits, /* o : exact frame size in bits (AMRWB IO) */ + uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ + uint16_t *sequenceNumber, /* o : sequence number from rtp header */ + bool *speechLostIndicated, /* o : Is current frame indicated as Lost */ + bool *isAMRWB_IOmode /* o : Is AMRWB_IO mode EVS frame */ ) { size_t length = 0; @@ -1731,6 +1762,11 @@ ivas_error IVAS_RTP_UNPACK_PullFrame( frameBuffer->length = length; } + if ( frameSizeInBits != NULL ) + { + *frameSizeInBits = (int16_t) node->toc.auNumBits; + } + if ( receivedCodecId != NULL ) { *receivedCodecId = node->toc.codecId; @@ -1758,7 +1794,12 @@ ivas_error IVAS_RTP_UNPACK_PullFrame( *speechLostIndicated = node->toc.speechLostIndicated; } - return BPOOL_FreeBuffer( hUnpack->nodePool, node ); + if ( isAMRWB_IOmode != NULL ) + { + *isAMRWB_IOmode = node->toc.isAMRWB_IOmode; + } + + return BPOOL_FreeBuffer( hUnpack->unpackNodePool, node ); } ivas_error IVAS_RTP_UNPACK_PullNextPiData( @@ -1781,7 +1822,7 @@ ivas_error IVAS_RTP_UNPACK_PullNextPiData( if ( data != NULL && ( pi->size <= capacity ) ) { - *data = *pi; + memcpy( data, pi, pi->size ); } if ( timestamp != NULL ) diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index ba33abe59e..8e918d76a2 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -59,9 +59,9 @@ static uint16_t readInt16( const uint8_t *buffer ) *-----------------------------------------------------------------------*/ static int16_t ivasPayload_convertToQ15( float value ) { - value = ( value * 32768.0 ); - value = value > +32767.0 ? +32767.0 : value; - value = value < -32768.0 ? -32768.0 : value; + value = ( value * 32768.0f ); + value = value > +32767.0f ? +32767.0f : value; + value = value < -32768.0f ? -32768.0f : value; return (int16_t) ( value ); } @@ -166,10 +166,10 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); - orientation->orientation.w = ((int16_t) readInt16( &buffer[0] )) / 32768.0f; - orientation->orientation.x = ((int16_t) readInt16( &buffer[2] )) / 32768.0f; - orientation->orientation.y = ((int16_t) readInt16( &buffer[4] )) / 32768.0f; - orientation->orientation.z = ((int16_t) readInt16( &buffer[6] )) / 32768.0f; + orientation->orientation.w = ( (int16_t) readInt16( &buffer[0] ) ) / 32768.0f; + orientation->orientation.x = ( (int16_t) readInt16( &buffer[2] ) ) / 32768.0f; + orientation->orientation.y = ( (int16_t) readInt16( &buffer[4] ) ) / 32768.0f; + orientation->orientation.z = ( (int16_t) readInt16( &buffer[6] ) ) / 32768.0f; return IVAS_ERR_OK; } @@ -197,7 +197,7 @@ static uint32_t getIndexTable( const float *table, uint32_t tableLength, float v static ivas_error packAcousticEnvironment( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) { uint32_t nBytes = 0; - uint32_t packedSize = 1; + uint8_t packedSize = 1; const IVAS_PIDATA_ACOUSTIC_ENV *aeEnv = (const IVAS_PIDATA_ACOUSTIC_ENV *) piData; *nBytesWritten = 0; @@ -217,7 +217,7 @@ static ivas_error packAcousticEnvironment( const IVAS_PIDATA_GENERIC *piData, ui } /* Acoustic Env data is packedSize bytes, header is 2 bytes */ - if ( maxDataBytes < packedSize + 2 ) + if ( maxDataBytes < (uint32_t) packedSize + 2 ) { return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Insufficient space to pack Orientation PI data" ); } diff --git a/lib_util/mutex.h b/lib_util/mutex.h index f3fe493dc6..caf0145e2e 100644 --- a/lib_util/mutex.h +++ b/lib_util/mutex.h @@ -37,7 +37,7 @@ #ifndef _MUTEX_H #define _MUTEX_H -#if defined( __unix__ ) || defined( __linux__ ) || ( defined( __MACH__ ) && defined( __APPLE__ )) +#if defined( __unix__ ) || defined( __linux__ ) || ( defined( __MACH__ ) && defined( __APPLE__ ) ) #include typedef pthread_mutex_t mtx_t; #define _USE_POSIX ( 1 ) -- GitLab From 6dca357c0be08ef737338361cf0cec4b14a51ee5 Mon Sep 17 00:00:00 2001 From: "Singh, Ripinder" Date: Wed, 6 Aug 2025 15:51:05 +1000 Subject: [PATCH 068/340] Add pytest for PACK API - Add python version of Unpack - Check RTP Header for Timestamp, Sequenc Number, Header, SSRC - Check Payload for Codec, Bitrate and AU bytes - Check PI data for Type, Size and Pi data bytes Signed-off-by: Singh, Ripinder --- tests/rtp/ivasrtp.py | 446 ++++++++++++++++++++++++++++++++++++++++++ tests/rtp/test_rtp.py | 355 +++++++++++++++++++++++++++++++++ 2 files changed, 801 insertions(+) create mode 100644 tests/rtp/ivasrtp.py create mode 100644 tests/rtp/test_rtp.py diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py new file mode 100644 index 0000000000..ecb3924866 --- /dev/null +++ b/tests/rtp/ivasrtp.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 + +__copyright__ = """ +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +__doc__ = """ +To configure test modules. +""" + +import struct +from enum import Enum +from dataclasses import dataclass, field, asdict +from bitstring import ConstBitStream, ReadError +import json +import base64 +import argparse + +class CODECS(str, Enum): + AMRWB = "amrwb_io" + EVS = "evs" + IVAS = "ivas" + +class SRCODEC(str, Enum): + LCLD = "lcld" + LC3PLUS = "lc3+" + NA = "NONE" + +class BANDWIDTH(str, Enum): + NB = "narrowband" + WB = "wideband" + SWB = "super wideband" + FB = "fullband" + NREQ = "NO_REQ" + +class REQUESTS(str, Enum): + CODEC = "codec", + BR = "bitrate" + BW = "bandwidth" + CA = "ca-mode" + FMT = "format" + SUBFMT = "sub-format" + SRCFG = "sr-config" + +@dataclass +class RTPHDR: + version: int = 2 + padding: bool = False + extension: bool = False + csrcCount: int = 0 + marker: bool = False + payloadType: int = 0 + sequenceNum: int = 0 + timestamp: int = 0 + ssrc: int = 0 + extensionType: int = 0 + extensionLength: int = 0 + csrcList: list = field(default_factory=list) + extensionBytes: list = field(default_factory=list) + + def __init__(self, bitstrm: ConstBitStream): + self.version = bitstrm.read(2).uint + self.padding = bitstrm.read(1).bool + self.extension = bitstrm.read(1).bool + self.csrcCount = bitstrm.read(4).int + self.marker = bitstrm.read(1).bool + self.payloadType = bitstrm.read(7).int + self.sequenceNum = bitstrm.read(16).uintbe + self.timestamp = bitstrm.read(32).uintbe + self.ssrc = bitstrm.read(32).uintbe + self.csrcList = [ bitstrm.read(32).uintbe for _ in range(self.csrcCount) ] + if self.extension: + self.extensionType = bitstrm.read(16).uintbe + self.extensionLength = bitstrm.read(16).uintbe + self.extensionBytes = [ bitstrm.read(32).uintbe for _ in range(self.extensionLength) ] + else: + self.extensionType = 0 + self.extensionLength = 0 + self.extensionBytes = list() + +@dataclass +class SRCONFIG: + enabled: bool = False + diegetic: bool = False + yaw: bool = False + pitch: bool = False + roll: bool = False + +@dataclass +class CMR: + bandwidth: BANDWIDTH + codec: CODECS = CODECS.IVAS + startIdx: int = 0 + endIdx: int = 0 + bitrates: list = field(default_factory=list) + +@dataclass +class SRINFO: + bitrate: int = 0 + diegetic: bool = False + transportCodec: SRCODEC = SRCODEC.NA + +@dataclass +class FRAME: + codec: CODECS = CODECS.IVAS + frmSizeBits: int = 0 + bitrate: int = 0 + speechLost: bool = False + srInfo: SRINFO = None + timestamp: int = 0 + au: bytes = field(default_factory=bytes) + +@dataclass +class ORIENTATION: + w: float = 0.0 + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + +@dataclass +class PIDATA: + timestamp: int = 0 + type: str = "NO_PI_DATA" + data: any = None + +ivasBitrates = [13200, 16400, 24400, 32000, 48000, 64000, 80000, 96000, 128000, 160000, 192000, 256000, 384000, 512000, -1, 5200] +evsBitrates = [5900, 7200, 8000, 9600, 13200, 16400, 24400, 32000, 48000, 64000, 96000, 128000, 2400, -1, -1, -1] +amrwbBitrates = [6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850, 1750, -1, -1, -1, -1, -1, -1] +cmrLookup = [ + CMR(bandwidth=BANDWIDTH.NB, codec=CODECS.EVS, startIdx=0, endIdx=7, bitrates = evsBitrates), #000 = NB-EVS + CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.AMRWB, startIdx=0, endIdx=9, bitrates = amrwbBitrates), #001 = AMRWB IO + CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=12, bitrates = evsBitrates), #010 = WB-EVS + CMR(bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=3, endIdx=12, bitrates = evsBitrates), #011 = SWB-EVS + CMR(bandwidth=BANDWIDTH.FB, codec=CODECS.EVS, startIdx=5, endIdx=12, bitrates = evsBitrates), #100 = FB-EVS + CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #101 = WB-CA + CMR(bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #110 = SWB-CA + CMR(bandwidth=BANDWIDTH.NREQ, codec=CODECS.IVAS, startIdx=0, endIdx=14, bitrates = ivasBitrates), #111 = IVAS +] + +codedFormats = ["Stereo", "SBA", "MASA", "ISM", "MC", "OMASA", "OSBA", "NO_REQ"] +codedSubFormats = [ + "FOA planar", "HOA2 planar", "HOA3 planar", "FOA", "HOA2", "HOA3", "MASA1", "MASA2", "ISM1", + "ISM2", "ISM3", "ISM4", "ISM1 extended metadata", "ISM2 extended metadata", + "ISM3 extended metadata", "ISM4 extended metadata", "MC 5.1", "MC 7.1", "MC 5.1.2", "MC 5.1.4", + "MC 7.1.4", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", + "Reserved", "Reserved", "Reserved", "Reserved", "OMASA ISM1 1TC", "OMASA ISM2 1TC", + "OMASA ISM3 1TC", "OMASA ISM4 1TC", "OMASA ISM1 2TC", "OMASA ISM2 2TC", "OMASA ISM3 2TC", + "OMASA ISM4 2TC", "OSBA ISM1 FOA planar", "OSBA ISM2 FOA planar", "OSBA ISM3 FOA planar", + "OSBA ISM4 FOA planar", "OSBA ISM1 FOA", "OSBA ISM2 FOA", "OSBA ISM3 FOA", "OSBA ISM4 FOA", + "OSBA ISM1 HOA2 planar", "OSBA ISM2 HOA2 planar", "OSBA ISM3 HOA2 planar", "OSBA ISM4 HOA2 planar", + "OSBA ISM1 HOA2", "OSBA ISM2 HOA2", "OSBA ISM3 HOA2", "OSBA ISM4 HOA2", "OSBA ISM1 HOA3 planar", + "OSBA ISM2 HOA3 planar", "OSBA ISM3 HOA3 planar", "OSBA ISM4 HOA3 planar", "OSBA ISM1 HOA3", + "OSBA ISM2 HOA3", "OSBA ISM3 HOA3", "OSBA ISM4 HOA3"] +PiTypeNames = [ + "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", + "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", + "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", "RESERVED14", + "RESERVED15","PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", "DYNAMIC_AUDIO_SUPPRESSION", + "AUDIO_FOCUS_DIRECTION", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", + "RESERVED27", "RESERVED28", "RESERVED29", "RESERVED30", "NO_PI_DATA" +] + +def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: + #assert False, "Unsupported PI Data" + return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode('utf-8') + +def unpackNoPiData(bitstrm: ConstBitStream, piSize: int) -> None: + assert piSize == 0, "NO_PI_DATA should be 0 size" + +def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: + assert piSize == 8, "Incorrect PI Data Size for Orientation" + w = bitstrm.read(16).int / 32768.0 + x = bitstrm.read(16).int / 32768.0 + y = bitstrm.read(16).int / 32768.0 + z = bitstrm.read(16).int / 32768.0 + return ORIENTATION(w, x, y, z) + +PIDataUnpacker = [ + unpackOrientation, # SCENE_ORIENTATION, + unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, + unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + unpackUnsupported, # ACOUSTIC_ENVIRONMENT + unpackUnsupported, # AUDIO_DESCRIPTION + unpackUnsupported, # ISM_NUM + unpackUnsupported, # ISM_ID + unpackUnsupported, # ISM_GAIN + unpackUnsupported, # ISM_ORIENTATION + unpackUnsupported, # ISM_POSITION + unpackUnsupported, # ISM_DISTANCE_ATTENUATION + unpackUnsupported, # ISM_DIRECTIVITY + unpackUnsupported, # DIEGETIC_TYPE + unpackUnsupported, # RESERVED13 + unpackUnsupported, # RESERVED14 + unpackUnsupported, # RESERVED15 + unpackUnsupported, # PLAYBACK_DEVICE_ORIENTATION + unpackUnsupported, # HEAD_ORIENTATION + unpackUnsupported, # LISTENER_POSITION + unpackUnsupported, # DYNAMIC_AUDIO_SUPPRESSION + unpackUnsupported, # AUDIO_FOCUS_DIRECTION + unpackUnsupported, # PI_LATENCY + unpackUnsupported, # R_ISM_ID + unpackUnsupported, # R_ISM_GAIN + unpackUnsupported, # R_ISM_ORIENTATION + unpackUnsupported, # R_ISM_POSITION + unpackUnsupported, # R_ISM_DIRECTION + unpackUnsupported, # RESERVED27 + unpackUnsupported, # RESERVED28 + unpackUnsupported, # RESERVED29 + unpackUnsupported, # RESERVED30 + unpackNoPiData # NO_DATA +] + +@dataclass +class IvasPayload: + lastCodec: CODECS = CODECS.IVAS # Track last frame's codec + frameList: list[FRAME] = field(default_factory=list) + piDataList: list[PIDATA] = field(default_factory=list) + requests: dict [str, any] = field(default_factory=dict) + + def __init__(self, bitstrm: ConstBitStream, rtpTimestamp, lastCodec: CODECS): + self.lastCodec = lastCodec + self.frameList = list[FRAME]() + self.piDataList = list[PIDATA]() + self.requests = dict() + + try: + piIndicated = False + if bitstrm.read(1).bool: + T = bitstrm.read(3).uint + BR = bitstrm.read(4).uint + + if T in [5, 6]:#CA MODES + if BR < 8: + self.requests[REQUESTS.CODEC] = cmrLookup[T].codec + self.requests[REQUESTS.BR] = 13200 + self.requests[REQUESTS.CA] = BR + self.requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception("Unsupported BR bits in CA Mode") + elif T == 7: #IVAS + if BR < 14 : + self.requests[REQUESTS.CODEC] = cmrLookup[T].codec + self.requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + self.requests[REQUESTS.CA] = -1 + elif BR == 14 : + raise Exception("Reserved BR idx in IVAS EByte") + else: + if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: + self.requests[REQUESTS.CODEC] = cmrLookup[T].codec + self.requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + self.requests[REQUESTS.CA] = -1 + self.requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception("Reserved BR idx in {} EByte".format(cmrLookup[T].codec)) + + #Try to get all subsequent E-bytes + while bitstrm.read(1).bool: + ET = bitstrm.read(3).uint + if ET == 0 : + supportedBW = [BANDWIDTH.WB, BANDWIDTH.SWB, BANDWIDTH.FB, BANDWIDTH.NREQ] + reserved = bitstrm.read(2) + BW = bitstrm.read(2).uint + self.requests[REQUESTS.BW] = supportedBW[BW] + elif ET == 1 : + S = bitstrm.read(1).bool + FMT = bitstrm.read(3).uint + if not S: + self.requests[REQUESTS.FMT] = codedFormats[FMT] + self.requests[REQUESTS.SUBFMT] = "NO_REQ" + else: + reserved = bitstrm.read(2) + subFMT = bitstrm.read(6).uint + self.requests[REQUESTS.FMT] = "NO_REQ" + self.requests[REQUESTS.SUBFMT] = codedSubFormats[subFMT] + elif ET == 2: + reserved = bitstrm.read(4) + piIndicated = True + elif ET == 3: + D = bitstrm.read(1).bool + Y = bitstrm.read(1).bool + P = bitstrm.read(1).bool + R = bitstrm.read(1).bool + self.requests[REQUESTS.SRCFG] = SRCONFIG(enabled=True, diegetic=D, yaw=Y, pitch=P, roll=R) + else: + reserved = bitstrm.read(4) + raise Exception("Unsupported subsequent EByte with ET={}".format(ET)) + + # ToC Byte parsing starts with 'F' bit as H bit is already read above + frameTimeStamps = rtpTimestamp + F = True + frmList = list() + while F: + F = bitstrm.read(1).bool + FT = bitstrm.read(2).uint + BR = bitstrm.read(4).uint + + frm = FRAME(timestamp=frameTimeStamps) + if FT == 1 : + frm.codec = CODECS.IVAS + if BR == 14 : + supportedBitrates = [-1, 256000, 384000, 512000] + reserved = bitstrm.read(1) + D = bitstrm.read(1).bool + C = bitstrm.read(1).bool + SR_BR = bitstrm.read(2).uint + reserved = bitstrm.read(3) + if SR_BR == 0: + raise Exception("Reserved bitrate in SR Config ToC Indicated") + frm.srInfo = SRINFO(bitrate=supportedBitrates[SR_BR], diegetic=D, + transportCodec= SRCODEC.LC3PLUS if C else SRCODEC.LCLD) + else: + frm.bitrate = ivasBitrates[BR] + elif FT == 0: + # Codec switch only if not NO_DATA_FRAME, as IVAS/EVS signal using this + frm.codec = CODECS.EVS if BR != 15 else self.lastCodec + if BR == 13: + raise Exception("Reserved bitrate in EVS ToC Indicated") + frm.speechLost = (BR == 14) + frm.bitrate = evsBitrates[BR] if BR < 13 else 0 + else: + frm.codec = CODECS.AMRWB + if BR >= 10 and BR <= 13: + raise Exception("Reserved bitrate in AMRWB-IO ToC Indicated") + frm.speechLost = (BR == 14) or (FT == 2) + frm.bitrate = amrwbBitrates[BR] if BR < 10 else 0 + frm.frmSizeBits = frm.bitrate // 50 + frameTimeStamps += 320 + frmList.append(frm) + + if F: + #skip all frame specific E-bytes before next header + while (bitstrm.read(1).bool): + print("Skipping unsupported frame specific subsequent Ebytes") + reserved = bitstrm.read(7) + + #Frame AUs here + for (idx, frm) in enumerate(frmList): + auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode + frm.au = bitstrm.read(auSize * 8).tobytes() + self.frameList.append(frm) + + # PI Data if Indicated + piTimeStamps = rtpTimestamp + PF = piIndicated + while PF : + PF = bitstrm.read(1).bool + PM = bitstrm.read(2).uint + PiType = bitstrm.read(5).uint + PiSize = 0 + byte = 255 + while byte == 255: + byte = bitstrm.read(8).uint + PiSize += byte + + PiFrameData = PIDataUnpacker[PiType](bitstrm, PiSize) + + self.piDataList.append( + PIDATA(timestamp=piTimeStamps if PM != 3 else rtpTimestamp, # Generic Pi has base timestamp + type=PiTypeNames[PiType], + data=PiFrameData)) + piTimeStamps += 320 if PM == 2 else 0 + except ReadError as error: + print ("Underflow before completion of unpacking, error = {}".format(error)) + +@dataclass +class IvasPacket: + hdr: RTPHDR = field(default_factory=RTPHDR) + payload: IvasPayload = field(default_factory=IvasPayload) + +class IvasRtp: + def __init__(self): + self.packets = list[IvasPacket]() + self.lastCodec: CODECS = CODECS.IVAS # Track last frame's codec + + def dumpToJSON(self, jsonFileName): + with open(jsonFileName, "w") as fd: + packets = list() + for packet in self.packets: + packets.append(asdict(packet)) + json_output = json.dumps(packets, indent=4) + fd.write(json_output) + + def getPackets(self): + return self.packets + + def unpackFile(self, rtpDumpFile): + with open (rtpDumpFile, mode="rb") as fd: + while True: + size = fd.read(4) + if not size: + break + size = struct.unpack( 'i', size)[0] + packet = fd.read(size) + if not packet: + break + self.packets.append(self.unpackPacket(packet)) + + def unpackPacket(self, packet) -> IvasPacket : + bitStrm = ConstBitStream(packet) + hdr = RTPHDR(bitStrm) + payload = IvasPayload(bitStrm, rtpTimestamp=hdr.timestamp, lastCodec=self.lastCodec) + self.lastCodec = payload.frameList[-1].codec #Last Frame's codec for next frame for NO_DATA case + return IvasPacket(hdr=hdr, payload=payload) + +class ArgsParser: + def __init__(self): + self.parser = argparse.ArgumentParser() + self.parser.add_argument("-r", "--rtpdump", type=str, required=True, help="RTP Dump to unpack") + self.parser.add_argument("-j", "--json", type=str, default="unpack.json", help="Output unpacked RTP frames to JSON file") + + def parse(self): + args = self.parser.parse_args() + return args + +if __name__ == "__main__": + args = ArgsParser().parse() + rtp = IvasRtp() + if args.rtpdump: + rtp.unpackFile(args.rtpdump) + rtp.dumpToJSON(args.json) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py new file mode 100644 index 0000000000..00b4da5c1b --- /dev/null +++ b/tests/rtp/test_rtp.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 + +__copyright__ = """ +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +__doc__ = """ +To configure test modules. +""" + +import pytest +import csv +import os +import sys + +from tempfile import TemporaryDirectory +from pathlib import Path +from ivasrtp import * +from bitstring import ConstBitStream, BitStream + +THIS_PATH = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../../scripts")) + +from tests.conftest import EncoderFrontend, DecoderFrontend + +@pytest.mark.parametrize("dtx", [False, True]) +@pytest.mark.parametrize("bitrate", [6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850]) +@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +def test_rtp_bitstream_amrwb ( + test_info, + bitrate: int, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend +): + run_rtp_bitstream_tests ( + CODECS.AMRWB, + bitrate, + "WB", + "OFF", + "MONO", + dtx, + framesPerPacket, + dut_encoder_frontend + ) + +@pytest.mark.parametrize("dtx", [False, True]) +@pytest.mark.parametrize("bitrate", [7200, 8000, 9600, 13200, 16400, 24400, 32000, 48000, 64000, 96000, 128000]) +@pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) +@pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) +@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +def test_rtp_bitstream_evs ( + test_info, + bitrate: int, + bandwidth: str, + caMode: str, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend +): + run_rtp_bitstream_tests ( + CODECS.EVS, + bitrate, + bandwidth, + caMode, + "MONO", + dtx, + framesPerPacket, + dut_encoder_frontend + ) + + +@pytest.mark.parametrize("bitrate", [13200, 24400, 80000, 512000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) +#@pytest.mark.parametrize("bandwidth, format, bitrate", [("WB", "MASA", 13200)]) +@pytest.mark.parametrize("framesPerPacket", [8]) +def test_rtp_bitstream_ivas_nodtx ( + test_info, + bitrate: int, + bandwidth: str, + format: str, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend +): + run_rtp_bitstream_tests ( + CODECS.IVAS, + bitrate, + bandwidth, + "OFF", + format, + False, + framesPerPacket, + dut_encoder_frontend + ) + +@pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +@pytest.mark.parametrize("format", ["STEREO", "SBA"]) +@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +def test_rtp_bitstream_ivas_dtx ( + test_info, + bitrate: int, + bandwidth: str, + format: str, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend +): + run_rtp_bitstream_tests ( + CODECS.IVAS, + bitrate, + bandwidth, + "OFF", + format, + True, + framesPerPacket, + dut_encoder_frontend + ) + + +def isEqualFrame(refFrame: bytes, dutFrame: bytes): + assert len(refFrame) == len(dutFrame), "Encoded frame size is different" + for refByte, dutByte in zip(refFrame, dutFrame): + assert refByte == dutByte, "Encoded frames should be bitexact between ref and rtpdump" + +def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): + assert abs(ref.w - dut.w) < 0.0001, "Scene Orientation PI Data mismatch in w" + assert abs(ref.x - dut.x) < 0.0001, "Scene Orientation PI Data mismatch in x" + assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" + assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" + + +class CSVREADER: + def __init__(self, csvFile: Path): + self.rIdx = 0 + self.rows = [] + with open(csvFile, 'r') as fd: + self.rows = [ row for row in csv.reader(fd) ] + self.count = len(self.rows) + + def next(self) -> list[float]: + row = self.rows[self.rIdx] + self.rIdx += 1 + if self.rIdx == self.count: + self.rIdx = 0 + return [ float(x) for x in row ] + +class RTPVALIDATE: + + DTX_BITRATES = { CODECS.IVAS: 5200, CODECS.EVS: 2400, CODECS.AMRWB: 1750 } + + + def __init__(self, codec = CODECS.IVAS, bitrate = 24400, framesPerPacket = 1, dtx = False): + self.framesPerPacket = framesPerPacket + self.dtx = dtx + self.codec = codec + self.bitrate = bitrate + self.timestamp = 0 + self.seqnum = -1 + self.ssrc = -1 + self.numFrames = 0 + self.validatePiData = False + self.g192File = None + self.frameIdx = 0 + #PI DATA + self.readers: dict[str : CSVREADER] = dict() + + def setPiDataFiles(self, piFiles: tuple[Path]): + self.validatePiData = True + self.readers[PiTypeNames[0]] = CSVREADER(piFiles[0]) + self.readers[PiTypeNames[1]] = CSVREADER(piFiles[1]) + + def setRefG192Bitstream(self, g192File: Path): + self.refPackets = list[bytes]() + self.refBitStrm = None + with open(g192File, "rb") as fd: + self.refBitStrm = ConstBitStream(fd.read()) + while self.refBitStrm.pos < self.refBitStrm.len: + sync = hex(self.refBitStrm.read(16).intle) + nBits = self.refBitStrm.read(16).intle + assert sync == "0x6b21", "G192 syncword not found at start of packet" + writer = BitStream() + for _ in range(nBits): + bit = "0b1" if self.refBitStrm.read(16).uintle == 129 else "0b0" + writer.append(bit) + self.refPackets.append(writer.tobytes()) + + def packet(self, packet: IvasPacket): + self.header(packet.hdr) + self.payload(packet.payload) + self.seqnum = (self.seqnum + 1) % 65536 + self.timestamp += 320 * self.numFrames + + def header(self, hdr: RTPHDR): + if self.timestamp == 0: + self.seqnum = hdr.sequenceNum + self.ssrc = hdr.ssrc + assert hdr.version == 2, "RTP Header Version must be 2" + assert self.ssrc == hdr.ssrc, "SSRC changed mid-stream in RTP Header" + assert self.timestamp == hdr.timestamp, "Timestamp mismatch in RTP Header" + assert self.seqnum == hdr.sequenceNum, "Sequence number mismatch in RTP Header" + + def payload(self, payload: IvasPayload): + self.numFrames = len(payload.frameList) + assert self.numFrames >= 1 and self.numFrames <= self.framesPerPacket, f"Packet must have atleast 1 frame and atmost {self.framesPerPacket} frames" + + for frame in payload.frameList: + assert self.codec == frame.codec, "Codec mismatch in RTP Payload" + if self.dtx: + assert frame.bitrate in (self.bitrate, RTPVALIDATE.DTX_BITRATES[self.codec], 0), "Bitrate mismatch in RTP Payload in DTX mode" + else: + assert frame.bitrate == self.bitrate, "Bitrate mismatch in RTP Payload" + + assert self.frameIdx < len(self.refPackets), "No. of frames mismatch" + isEqualFrame(frame.au, self.refPackets[self.frameIdx]) + self.frameIdx += 1 + + # Vallidate the PI Data + if self.validatePiData: + self.piData(payload.piDataList) + + def piData(self, piDataList: list[PIDATA]): + for piData in piDataList: + assert piData.timestamp >= self.timestamp and piData.timestamp < self.timestamp + (self.numFrames * 320), "PI Data Time stamp is OOB" + assert piData.type == PiTypeNames[0] or piData.type == PiTypeNames[1], "PI Data is neither Scene nor Device Orientation" + assert type(piData.data) == ORIENTATION, "Orientation type data expected" + #validate the PI Data provided is the PI data in the packet + refData = self.readers[piData.type].next() + isEqualOrientation(ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), piData.data) +@dataclass +class TVARGS: + TVROOT = Path(THIS_PATH).joinpath("../../scripts/testv") + def __init__(self): + self.tvDict = dict() + self.sceneFile = Path(THIS_PATH).joinpath("../../scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv").absolute() + self.deviceFile = Path(THIS_PATH).joinpath("../../scripts/trajectories/headrot-1.5s.csv").absolute() + + def add(self, fmt:str, inputFile:str, args:list[str] = []): + inputFile = str(TVARGS.TVROOT.joinpath(inputFile).absolute()) + if fmt == "MASA": + args[2] = str(TVARGS.TVROOT.joinpath(args[2]).absolute()) + self.tvDict[fmt] = (inputFile, args) + + def input(self, fmt): + return self.tvDict[fmt][0] + + def args(self, fmt, addPI=False) -> list[str]: + args = [ x for x in self.tvDict[fmt][1] ] + if addPI and fmt != "MONO": + args += [ "-scene_orientation", str(self.sceneFile), "-device_orientation", str(self.deviceFile) ] + return args + + def piFiles(self) -> tuple[Path]: + return (self.sceneFile, self.deviceFile) + +def run_rtp_bitstream_tests ( + codec: CODECS, + bitrate: int, + bandwidth: str, + caMode: str, + format: str, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend +): + tvArgs = TVARGS() + tvArgs.add("MONO", "stv48n.wav") + + if dtx: #use bigger file for dtx stereo + tvArgs.add("STEREO", "stvST48n.wav", ["-stereo"]) + else: + tvArgs.add("STEREO", "stv2MASA2TC48c.wav", ["-stereo"]) + + tvArgs.add("MC", "stv51MC48c.wav", ["-mc", "5_1"]) + tvArgs.add("MASA", "stv2MASA2TC48c.wav", ["-masa", "2", "stv2MASA2TC48c.met"]) + tvArgs.add("SBA", "stvFOA48c.wav", ["-sba", "+1"]) + + if (bitrate > 24400 and bandwidth == "NB") or (format == "STEREO" and bitrate > 256000): + pytest.skip() + + print("Test: dut_encoder_frontend={}, dtx={}, codec:={}, bitrate={}, bandwidth={}, caMode={}, format={},".format(dut_encoder_frontend._path, dtx, codec, bitrate, bandwidth, caMode, format)) + + validate = RTPVALIDATE( + codec=codec, + bitrate=bitrate, + framesPerPacket=framesPerPacket, + dtx=dtx + ) + + with TemporaryDirectory() as tmp_dir: + g192Out = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192").absolute() + rtpdumpOut = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump").absolute() + + # Run WITHOUT rtpdump first to generate reference bitstream + dut_encoder_frontend.run( + bitrate=bitrate, + input_sampling_rate=48, + input_path=tvArgs.input(format), + output_bitstream_path=g192Out, + sba_order=None, + dtx_mode=dtx, + max_band=bandwidth, + add_option_list=tvArgs.args(format) + ) + validate.setRefG192Bitstream(g192File=g192Out) + + # Generate RTPDUMP + addPI = False if format == "MONO" else True + if addPI: + # Add PI Data to Pack in RTP + validate.setPiDataFiles(tvArgs.piFiles()) + + extra_args = tvArgs.args(format, addPI) + extra_args += ["-rtpdump", str(framesPerPacket)] + dut_encoder_frontend.run( + bitrate=bitrate, + input_sampling_rate=48, + input_path=tvArgs.input(format), + output_bitstream_path=rtpdumpOut, + sba_order=None, + dtx_mode=dtx, + max_band=bandwidth, + add_option_list=extra_args + ) + + ivasRtp = IvasRtp() + ivasRtp.unpackFile(rtpdumpOut) + for packet in ivasRtp.getPackets(): + validate.packet(packet) -- GitLab From ce4f93c05a7d3575cffacfb4f7d6e97215102e89 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 11 Aug 2025 09:56:59 +0200 Subject: [PATCH 069/340] Remove unneeded postfix from struct name ISAR_CLDFB_RINGBUF_DATA --- lib_dec/ivas_cldfb_ring_buffer.c | 2 +- lib_isar/isar_stat.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_dec/ivas_cldfb_ring_buffer.c b/lib_dec/ivas_cldfb_ring_buffer.c index 82f79687ff..fdabe30fe8 100644 --- a/lib_dec/ivas_cldfb_ring_buffer.c +++ b/lib_dec/ivas_cldfb_ring_buffer.c @@ -90,7 +90,7 @@ ivas_error ivas_CLDFB_RINGBUF_Open( capacity = capacity_columns * CLDFB_NO_CHANNELS_MAX; - if ( ( h = malloc( sizeof( ISAR_CLDFB_RINGBUF_DATA ) ) ) == NULL ) + if ( ( h = malloc( sizeof( ISAR_CLDFB_RINGBUF ) ) ) == NULL ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Failed to allocate memory for CLDFB ring buffer\n" ); } diff --git a/lib_isar/isar_stat.h b/lib_isar/isar_stat.h index 0e0577d570..b244370dd3 100644 --- a/lib_isar/isar_stat.h +++ b/lib_isar/isar_stat.h @@ -355,7 +355,7 @@ typedef struct uint32_t read_pos; int16_t is_full; -} ISAR_CLDFB_RINGBUF_DATA, *ISAR_CLDFB_RINGBUF_HANDLE; +} ISAR_CLDFB_RINGBUF, *ISAR_CLDFB_RINGBUF_HANDLE; #endif #endif /* ISAR_STAT_H */ -- GitLab From 3d3cc22cc9695ab618002aec144bd7b9242b5248 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 11 Aug 2025 11:08:12 +0200 Subject: [PATCH 070/340] Ensure splitRendBits is not NULL when outputting to split rendering config --- lib_dec/lib_dec.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 25865123b0..0de65e331a 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3750,7 +3750,14 @@ ivas_error IVAS_DEC_VoIP_GetSamples return IVAS_ERR_WRONG_PARAMS; } -#ifndef FIX_1119_SPLIT_RENDERING_VOIP +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( ( hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || + hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) && + splitRendBits == NULL ) + { + return IVAS_ERR_UNEXPECTED_NULL_POINTER; + } +#else if ( hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) { -- GitLab From 1571d77dcc2a9bca28a2deecdfbd9c1eea7f3989 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 12:15:40 +0200 Subject: [PATCH 071/340] Remove fprintf call from library internals --- lib_dec/lib_dec.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index bdb69ab8fc..bd38ab5e5e 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -4063,7 +4063,6 @@ ivas_error IVAS_DEC_VoIP_GetSplitBinauralBitstream( if ( ( error = IVAS_DEC_GetRenderFramesizeSamples( hIvasDec, &nSamplesPerChannel ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nError getting render frame size in samples\n" ); return error; } -- GitLab From f6a1bc21e687ea4656ee23f39421e874730cd8f4 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 16:49:24 +0200 Subject: [PATCH 072/340] Fix split rendering with frame sizes other than 20 ms --- lib_dec/lib_dec.c | 5 +++-- lib_isar/lib_isar_pre_rend.c | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index bd38ab5e5e..609eab8707 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1935,7 +1935,7 @@ static ivas_error isar_generate_metadata_and_bitstream( int16_t td_input; int16_t ro_md_flag; IVAS_QUATERNION Quaternion; - int16_t i, j, num_poses; + int16_t i, j, num_poses, num_cldfb_slots; float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; @@ -1952,10 +1952,11 @@ static ivas_error isar_generate_metadata_and_bitstream( if ( !td_input ) { num_poses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; + num_cldfb_slots = hIvasDec->st_ivas->hDecoderConfig->render_framesize * JBM_CLDFB_SLOTS_IN_SUBFRAME; for ( i = 0; i < (int16_t) ( BINAURAL_CHANNELS * num_poses ); ++i ) { - for ( j = 0; j < CLDFB_NO_COL_MAX; ++j ) + for ( j = 0; j < num_cldfb_slots; ++j ) { /* Save pointers to first CLDFB column in the ring buffer. Allows us to save * significant amounts of memory by not copying CLDFB values into a separate buffer. */ diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index 23ff136c51..dd713d0134 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -381,20 +381,22 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( } else { - int16_t ch, slot_idx; + int16_t ch, slot_idx, num_slots; + num_slots = isar_frame_size_ms * 1000000 / CLDFB_SLOT_NS; + /* CLDFB synthesis of main pose */ for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { float *Cldfb_In_BinReal_p[CLDFB_NO_COL_MAX]; float *Cldfb_In_BinImag_p[CLDFB_NO_COL_MAX]; - for ( slot_idx = 0; slot_idx < CLDFB_NO_COL_MAX; slot_idx++ ) + for ( slot_idx = 0; slot_idx < num_slots; slot_idx++ ) { Cldfb_In_BinReal_p[slot_idx] = Cldfb_In_BinReal[ch][slot_idx]; Cldfb_In_BinImag_p[slot_idx] = Cldfb_In_BinImag[ch][slot_idx]; } - cldfbSynthesis( Cldfb_In_BinReal_p, Cldfb_In_BinImag_p, output[ch], hSplitBin->hCldfbHandles->cldfbSyn[0]->no_channels * CLDFB_NO_COL_MAX, hSplitBin->hCldfbHandles->cldfbSyn[ch] ); + cldfbSynthesis( Cldfb_In_BinReal_p, Cldfb_In_BinImag_p, output[ch], hSplitBin->hCldfbHandles->cldfbSyn[0]->no_channels * num_slots, hSplitBin->hCldfbHandles->cldfbSyn[ch] ); } pBits->pose_correction = hSplitBin->multiBinPoseData.poseCorrectionMode; -- GitLab From 11220e779847aa168b7cd544b24306d245fbcd35 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 18:40:16 +0200 Subject: [PATCH 073/340] Fix signal scaling in OSBA --- lib_dec/ivas_osba_dec.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index e9cb54b6bc..145c36c9f7 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -186,12 +186,8 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( #ifdef FIX_1119_SPLIT_RENDERING_VOIP CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); - - for ( b = 0; b < num_cldfb_bands; b++ ) - { - re[b] = 0.5f * re[b] + 0.5f * Cldfb_RealBuffer[b]; - im[b] = 0.5f * im[b] + 0.5f * Cldfb_ImagBuffer[b]; - } + v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); + v_add( im, Cldfb_ImagBuffer, im, num_cldfb_bands ); #else for ( b = 0; b < num_cldfb_bands; b++ ) { -- GitLab From 0dd84471328ac23817452775e22f6e0880645943 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 18:44:10 +0200 Subject: [PATCH 074/340] Remove unused variable --- lib_dec/ivas_osba_dec.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib_dec/ivas_osba_dec.c b/lib_dec/ivas_osba_dec.c index 145c36c9f7..23374a3f03 100644 --- a/lib_dec/ivas_osba_dec.c +++ b/lib_dec/ivas_osba_dec.c @@ -161,7 +161,10 @@ ivas_error ivas_osba_dirac_td_binaural_jbm( if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { - int16_t slot_idx, num_cldfb_bands, b, nchan_transport_orig; + int16_t slot_idx, num_cldfb_bands, nchan_transport_orig; +#ifndef FIX_1119_SPLIT_RENDERING_VOIP + int16_t b; +#endif int16_t cldfb_slots; float Cldfb_RealBuffer[CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer[CLDFB_NO_CHANNELS_MAX]; -- GitLab From 2d963fd424e888d63dc2636fe2b83903cb91ef49 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 18:47:47 +0200 Subject: [PATCH 075/340] Fix int conversion warnings --- lib_dec/lib_dec.c | 2 +- lib_isar/lib_isar_pre_rend.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 609eab8707..1b73d7ca23 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1952,7 +1952,7 @@ static ivas_error isar_generate_metadata_and_bitstream( if ( !td_input ) { num_poses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; - num_cldfb_slots = hIvasDec->st_ivas->hDecoderConfig->render_framesize * JBM_CLDFB_SLOTS_IN_SUBFRAME; + num_cldfb_slots = (int16_t) hIvasDec->st_ivas->hDecoderConfig->render_framesize * JBM_CLDFB_SLOTS_IN_SUBFRAME; for ( i = 0; i < (int16_t) ( BINAURAL_CHANNELS * num_poses ); ++i ) { diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index dd713d0134..a3e7cdde64 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -382,7 +382,7 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( else { int16_t ch, slot_idx, num_slots; - num_slots = isar_frame_size_ms * 1000000 / CLDFB_SLOT_NS; + num_slots = (int16_t) isar_frame_size_ms * 1000000 / CLDFB_SLOT_NS; /* CLDFB synthesis of main pose */ for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) -- GitLab From e95802d13529901d5b940cf6e9be34f8dd53bb7e Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 18:51:51 +0200 Subject: [PATCH 076/340] Try to fix integer conversion warning again --- lib_isar/lib_isar_pre_rend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index a3e7cdde64..46ff7d3bee 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -382,7 +382,7 @@ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( else { int16_t ch, slot_idx, num_slots; - num_slots = (int16_t) isar_frame_size_ms * 1000000 / CLDFB_SLOT_NS; + num_slots = (int16_t) ( isar_frame_size_ms * 1000000 / CLDFB_SLOT_NS ); /* CLDFB synthesis of main pose */ for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) -- GitLab From 91adc344be8e143e461377c9458119ccefeb8989 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 12 Aug 2025 19:10:42 +0200 Subject: [PATCH 077/340] Modify test in preparation for -no_delay_cmp being required with -Tracefile in IVAS_dec --- .../test_voip_be_splitrend_vs_binaural.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py index 3c7b1da6ec..dd9c4f317c 100644 --- a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -102,7 +102,7 @@ def test_voip_be_splitrend_vs_binaural( ).absolute() trace_out = wav_out.with_suffix(".trace") - options.extend(["-Tracefile", str(trace_out)]) + options.extend(["-Tracefile", str(trace_out), "-no_delay_cmp"]) if out_format == "BINAURAL_SPLIT_PCM": isar_md_file = wav_out.with_suffix(".isarmd") @@ -124,13 +124,16 @@ def test_voip_be_splitrend_vs_binaural( wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") wav_out_sr, trace_out_sr, isar_md_out_sr = run_decoder("BINAURAL_SPLIT_PCM") - # Delay-align audio - assert isar_md_out_sr is not None - sr_delay_samples = IsarBitstream(isar_md_out_sr).delay_samples + # Note regarding delay alignment: both output audio files contain the same decoder delay. + # + # - When outputting to BINAURAL with -no_delay_cmp, decoder delay is present in the audio + # output, as expected. + # + # - When outputting to BINAURAL_SPLIT_PCM, decoder delay is never compensated in output + # audio (irrespective of the -no_delay_cmp flag). The delay value is saved in the ISAR + # metadata file and compensated at the post-rendering stage. audio_sr, _ = audiofile.readfile(str(wav_out_sr)) audio_bin, _ = audiofile.readfile(str(wav_out_bin)) - audio_sr = audio_sr[sr_delay_samples:] - audio_bin = audio_bin[:-sr_delay_samples] # Ensure audio and tracefiles are BE audio_cmp_result = audioarray.compare( -- GitLab From 57d6702efe20e4046027117191387447918cf139 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 13 Aug 2025 15:12:54 +0200 Subject: [PATCH 078/340] Fix missing initialisation of tmp st_ivas->flushing flag --- lib_dec/ivas_init_dec.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 0b04eedb3b..3d93f68b02 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1234,6 +1234,10 @@ ivas_error ivas_init_decoder_front( } } +#ifdef TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR + st_ivas->flushing = 0; +#endif + return error; } -- GitLab From cfc9dcaa1d721034c8bbc1692e8cc59aaaa28159 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 13 Aug 2025 15:45:49 +0200 Subject: [PATCH 079/340] Avoid flushing audio when outputting to ISAR bitstream --- apps/decoder.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/decoder.c b/apps/decoder.c index c02e770135..9c9054d62a 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3629,7 +3629,11 @@ static ivas_error decodeVoIP( goto cleanup; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( nSamplesFlushed && !isSplitCoded ) +#else if ( nSamplesFlushed ) +#endif { /* Write current frame */ if ( ( error = AudioFileWriter_write( afWriter, pcmBuf, nSamplesFlushed * nOutChannels ) ) != IVAS_ERR_OK ) -- GitLab From 75b1060a7d4e8d6a8ce366edf527253f7bd089d3 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 13 Aug 2025 16:13:48 +0200 Subject: [PATCH 080/340] Fix formatting --- lib_isar/lib_isar_pre_rend.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib_isar/lib_isar_pre_rend.c b/lib_isar/lib_isar_pre_rend.c index 66d965001a..5318d217b2 100644 --- a/lib_isar/lib_isar_pre_rend.c +++ b/lib_isar/lib_isar_pre_rend.c @@ -272,26 +272,26 @@ void ISAR_PRE_REND_GetMultiBinPoseData( *------------------------------------------------------------------------*/ ivas_error ISAR_PRE_REND_MultiBinToSplitBinaural( - SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renerer handle */ - const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ - const int32_t SplitRendBitRate, /* i : Split renderer bitrate */ - ISAR_SPLIT_REND_CODEC splitCodec, /* i/o: Split renderer codec */ - const int16_t isar_frame_size_ms, /* i : ISAR framesize */ - int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ - ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ + SPLIT_REND_WRAPPER *hSplitBin, /* i/o: Split renderer pre-renerer handle */ + const IVAS_QUATERNION headPosition, /* i : head rotation QUATERNION */ + const int32_t SplitRendBitRate, /* i : Split renderer bitrate */ + ISAR_SPLIT_REND_CODEC splitCodec, /* i/o: Split renderer codec */ + const int16_t isar_frame_size_ms, /* i : ISAR framesize */ + int16_t codec_frame_size_ms, /* i/o: ISAR transport codec framesize */ + ISAR_SPLIT_REND_BITS_HANDLE pBits, /* i/o: ISAR bits struct handle */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ - float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ + float *Cldfb_In_BinReal[][CLDFB_NO_COL_MAX], /* i/o: CLDFB real buffer */ + float *Cldfb_In_BinImag[][CLDFB_NO_COL_MAX], /* i/o: CLDFB imag buffer */ #else float Cldfb_In_BinReal[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB real buffer */ float Cldfb_In_BinImag[][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX], /* i/o: CLDFB imag buffer */ #endif - const int16_t max_bands, /* i : CLDFB bands */ - float *output[], /* i/o: PCM in/out buffer */ - const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ - const int16_t cldfb_in_flag, /* i : Flag to indicate CLDFB or time domain input */ - const int16_t pcm_out_flag, /* i : Flag to indicate PCM output */ - const int16_t ro_md_flag /* i : Flag to indicate real only metadata for yaw */ + const int16_t max_bands, /* i : CLDFB bands */ + float *output[], /* i/o: PCM in/out buffer */ + const int16_t low_res_pre_rend_rot, /* i : low time resolution pre-renderer flag */ + const int16_t cldfb_in_flag, /* i : Flag to indicate CLDFB or time domain input */ + const int16_t pcm_out_flag, /* i : Flag to indicate PCM output */ + const int16_t ro_md_flag /* i : Flag to indicate real only metadata for yaw */ ) { ivas_error error; -- GitLab From ab4e5f4778b0c737b291dd76d10db0cbc536821d Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 14 Aug 2025 10:38:52 +0200 Subject: [PATCH 081/340] Keep function name prefixes consistent in ivas_cldfb_ring_buffer.c --- lib_dec/ivas_cldfb_ring_buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_dec/ivas_cldfb_ring_buffer.c b/lib_dec/ivas_cldfb_ring_buffer.c index fdabe30fe8..bc0e2c6ee0 100644 --- a/lib_dec/ivas_cldfb_ring_buffer.c +++ b/lib_dec/ivas_cldfb_ring_buffer.c @@ -227,7 +227,7 @@ void ivas_CLDFB_RINGBUF_Pop( /* Returns total number of buffered samples (including number of channels) */ -static uint32_t CLDFB_RINGBUF_total_size( +static uint32_t ivas_CLDFB_RINGBUF_total_size( ISAR_CLDFB_RINGBUF_HANDLE h ) { if ( ivas_CLDFB_RINGBUF_IsFull( h ) ) @@ -263,7 +263,7 @@ void ivas_CLDFB_RINGBUF_GetByIdx( const int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; - int32_t num_floats = (int32_t) CLDFB_RINGBUF_total_size( h ); + int32_t num_floats = (int32_t) ivas_CLDFB_RINGBUF_total_size( h ); uint32_t offset; assert( -num_floats <= idx && idx <= num_floats ); -- GitLab From de43d43ad6c169a7b8acbcf4ab2ccc0f6fc678a2 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Tue, 12 Aug 2025 19:15:52 +1000 Subject: [PATCH 082/340] Add dumping of PI Data in decoder, more PI support and bugfixes * API updates for Pack and Unpack APIs * Do not report NO_PI_DATA in unpack * Add support for pack/unpack Listner Position, Diegetic * Fix PI Data structure for LISTERENR POSITION * Fix shift in acoustic env pi data unpack * Add a python implementation of PACK API * Add tests to validate decoded audio and dumped PI data Signed-off-by: Ripinder Singh --- apps/decoder.c | 254 +++++++- apps/encoder.c | 2 +- lib_util/ivas_rtp_api.h | 18 +- lib_util/ivas_rtp_payload.c | 25 +- lib_util/ivas_rtp_pi_data.c | 161 ++++- lib_util/ivas_rtp_pi_data.h | 6 +- tests/requirements.txt | 2 + tests/rtp/ivasrtp.py | 1145 ++++++++++++++++++++++++++++------- tests/rtp/test_rtp.py | 197 ++++-- 9 files changed, 1508 insertions(+), 302 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index c9eb700df3..4f2307b3dc 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -130,6 +130,10 @@ typedef struct IVAS_DEC_COMPLEXITY_LEVEL complexityLevel; bool tsmEnabled; IVAS_RENDER_FRAMESIZE renderFramesize; +#ifdef IVAS_RTPDUMP + bool applyPiData; + char *piOutputFilename; +#endif #ifdef DEBUGGING IVAS_DEC_FORCED_REND_MODE forcedRendMode; #ifdef DEBUG_FOA_AGC @@ -183,10 +187,11 @@ typedef struct typedef struct { - uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; FILE *f_rtpstream; + FILE *f_piDataOut; IVAS_RTP_CODEC codecId; uint32_t nReadPiData; uint32_t nProcPiData; @@ -207,6 +212,166 @@ typedef struct #endif } IVAS_RTP; +static const char *PiDataNames[IVAS_PI_MAX_ID] = { + "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", + "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", + "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", + "RESERVED14", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", + "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_DIRECTION", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", + "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", + "RESERVED30", "NO_DATA" +}; + + +static void LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) +{ + uint32_t timestamp = ~0u; + if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) + { + return; + } + + if ( ftell( f_piDataOut ) > 2 ) + { + fprintf( f_piDataOut, ",\n" ); + } + + while ( nPiDataPresent-- > 0 ) + { + PIDATA_TS *cur = piData++; + + if ( timestamp != ( ~0u ) && timestamp != cur->timestamp ) + { + fprintf( f_piDataOut, "\n\t},\n\t\"%d\": {\n", cur->timestamp ); + } + else if ( timestamp != cur->timestamp ) + { + fprintf( f_piDataOut, "\t\"%d\": {\n", cur->timestamp ); + } + else + { + fprintf( f_piDataOut, ",\n" ); + } + fprintf( f_piDataOut, "\t\t\"%s\" : ", PiDataNames[cur->data.noPiData.piDataType] ); + switch ( cur->data.noPiData.piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + case IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED: +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: + case IVAS_PI_HEAD_ORIENTATION: + case IVAS_PI_AUDIO_FOCUS_DIRECTION: + case IVAS_PI_R_ISM_ORIENTATION: +#endif + { + fprintf( f_piDataOut, "{\n\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", + cur->data.scene.orientation.w, cur->data.scene.orientation.x, cur->data.scene.orientation.y, cur->data.scene.orientation.z ); + } + break; + + case IVAS_PI_ACOUSTIC_ENVIRONMENT: + { + fprintf( f_piDataOut, "{\n\t\t\t\"aeid\": %d", cur->data.acousticEnv.aeid ); + if ( cur->data.acousticEnv.availLateReverb ) + { + fprintf( f_piDataOut, ",\n\t\t\t\"rt60\": [ %f, %f, %f ],\n", cur->data.acousticEnv.rt60[0], cur->data.acousticEnv.rt60[1], cur->data.acousticEnv.rt60[2] ); + fprintf( f_piDataOut, "\t\t\t\"dsr\": [ %f, %f, %f ]", cur->data.acousticEnv.dsr[0], cur->data.acousticEnv.dsr[1], cur->data.acousticEnv.dsr[2] ); + } + if ( cur->data.acousticEnv.availEarlyReflections ) + { + fprintf( f_piDataOut, ",\n\t\t\t\"dim\": [ %f, %f, %f ],\n", cur->data.acousticEnv.roomDimensions.x, cur->data.acousticEnv.roomDimensions.y, cur->data.acousticEnv.roomDimensions.z ); + fprintf( f_piDataOut, "\t\t\t\"abscoeff\": [ %f, %f, %f, %f, %f, %f ]", cur->data.acousticEnv.absorbCoeffs[0], cur->data.acousticEnv.absorbCoeffs[1], cur->data.acousticEnv.absorbCoeffs[2], cur->data.acousticEnv.absorbCoeffs[3], cur->data.acousticEnv.absorbCoeffs[4], cur->data.acousticEnv.absorbCoeffs[5] ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_LISTENER_POSITION: + case IVAS_PI_R_ISM_POSITION: + { + fprintf( f_piDataOut, "{\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", + cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); + } + break; + case IVAS_PI_AUDIO_DESCRIPTION: + { + uint32_t nEntries = cur->data.audioDesc.nValidEntries; + IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; + + fprintf( f_piDataOut, "[\n" ); + while ( nEntries-- > 0 ) + { + fprintf( f_piDataOut, "\t\t\t{\n" ); + fprintf( f_piDataOut, "\t\t\t\t\"isSpeech\": %s,\n", audioId->speech ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isMusic\": %s,\n", audioId->music ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isAmbiance\": %s,\n", audioId->ambiance ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isEditable\": %s,\n", audioId->editable ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isBinaural\": %s\n", audioId->binaural ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t}%c\n", ( nEntries == 0 ) ? ' ' : ',' ); + audioId++; + } + fprintf( f_piDataOut, "\t\t]" ); + } + break; + case IVAS_PI_DIEGETIC_TYPE: + { + bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; + fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "\t\t\t\"isDigetic\": [\n" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[0] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[1] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[2] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[3] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s\n", isDiegetic[4] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t]\n\t\t}" ); + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: + { + IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; + fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferAmbiance\": %s,\n", das->ambiance ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"level\": %d", das->sli ); + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; + case IVAS_PI_RESERVED13: + case IVAS_PI_RESERVED14: + case IVAS_PI_RESERVED15: + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: + case IVAS_PI_RESERVED30: + { + fprintf( f_piDataOut, "{}" ); + } + break; + case IVAS_PI_ISM_NUM: + case IVAS_PI_ISM_ID: + case IVAS_PI_ISM_GAIN: + case IVAS_PI_ISM_ORIENTATION: + case IVAS_PI_ISM_POSITION: + case IVAS_PI_ISM_DISTANCE_ATTENUATION: + case IVAS_PI_ISM_DIRECTIVITY: + case IVAS_PI_PI_LATENCY: + case IVAS_PI_R_ISM_ID: + case IVAS_PI_R_ISM_GAIN: + case IVAS_PI_R_ISM_DIRECTION: +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + case IVAS_PI_NO_DATA: + { + fprintf( f_piDataOut, "{}" ); + } + break; + } + timestamp = cur->timestamp; + } + fprintf( f_piDataOut, "\n\t}" ); +} + static void IVAS_RTP_Term( IVAS_RTP *rtp ) { if ( rtp->f_rtpstream ) @@ -214,27 +379,34 @@ static void IVAS_RTP_Term( IVAS_RTP *rtp ) fclose( rtp->f_rtpstream ); rtp->f_rtpstream = NULL; } + if ( rtp->f_piDataOut ) + { + fprintf( rtp->f_piDataOut, "\n}\n" ); + fclose( rtp->f_piDataOut ); + rtp->f_piDataOut = NULL; + } IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); } -static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, FILE *f_rtpstream ) +static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, FILE *f_rtpstream, FILE *f_piDataOut ) { ivas_error error = IVAS_ERR_OK; memset( rtp, 0, sizeof( IVAS_RTP ) ); - rtp->unpackCfg.jitterMarginMs = JITTER_DURATION_MS; + rtp->unpackCfg.maxFramesPerPacket = IVAS_MAX_FRAMES_PER_RTP_PACKET; error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); rtp->f_rtpstream = f_rtpstream; + rtp->f_piDataOut = f_piDataOut; rtp->rtpPacket.buffer = rtp->packet; rtp->rtpPacket.capacity = sizeof( rtp->packet ); return error; } -static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) +static ivas_error readNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) { ivas_error error = IVAS_ERR_OK; IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; @@ -289,6 +461,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ rtp->nReadPiData++; rtp->numPiDataInPacket--; } + LogPiData( rtp->f_piDataOut, rtp->piData, rtp->nReadPiData ); } #ifdef RTP_S4_251135_CR26253_0016_REV1 @@ -329,8 +502,14 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ rtp->numFramesProduced++; *nextPacketRcvTime_ms += 20; + return IVAS_ERR_OK; +} + +static ivas_error applyPiData( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint32_t rtpTimeStamp ) +{ + ivas_error error = IVAS_ERR_OK; while ( rtp->nProcPiData < rtp->nReadPiData && - rtp->piData[rtp->nProcPiData].timestamp <= *rtpTimeStamp ) + rtp->piData[rtp->nProcPiData].timestamp <= rtpTimeStamp ) { PIDATA_TS *piData = &rtp->piData[rtp->nProcPiData++]; if ( hIvasDec ) @@ -356,7 +535,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ default: { - fprintf( stderr, "Unhandled PI data of type : %d\n", piDataType ); + fprintf( stderr, "Unhandled PI data of type : %s\n", PiDataNames[piDataType] ); } break; } @@ -366,8 +545,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint8_ } } } - - return IVAS_ERR_OK; + return error; } #endif /* IVAS_RTPDUMP */ @@ -1304,6 +1482,10 @@ static bool parseCmdlIVAS_dec( arg->referenceVectorTrajFileName = NULL; arg->enableExternalOrientation = false; arg->externalOrientationTrajFileName = NULL; +#ifdef IVAS_RTPDUMP + arg->applyPiData = false; + arg->piOutputFilename = NULL; +#endif #ifdef SUPPORT_JBM_TRACEFILE arg->jbmTraceFilename = NULL; @@ -1390,6 +1572,25 @@ static bool parseCmdlIVAS_dec( arg->inputFormat = IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF; i++; } +#ifdef IVAS_RTPDUMP + else if ( strcmp( argv_to_upper, "-PIDATAFILE" ) == 0 ) + { + i++; + if ( argc - i <= 3 || argv[i][0] == '-' ) + { + fprintf( stderr, "Error: PI Data Output file name not specified!\n\n" ); + usage_dec(); + return false; + } + + arg->piOutputFilename = argv[i++]; + } + else if ( strcmp( argv_to_upper, "-APPLYPIDATA" ) == 0 ) + { + arg->applyPiData = true; + i++; + } +#endif #ifdef SUPPORT_JBM_TRACEFILE else if ( strcmp( argv_to_upper, "-TRACEFILE" ) == 0 ) { @@ -1979,6 +2180,8 @@ static void usage_dec( void ) fprintf( stdout, " EVS RTP Payload Format or rtpdump files containing TS26.253 Annex A\n" ); fprintf( stdout, " IVAS RTP Payload Format. The SDP parameter hf_only is required.\n" ); fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); + fprintf( stdout, "-PiDataFile PF Log the timestampped PI data.\n" ); + fprintf( stdout, "-ApplyPiData Apply the PI data found in the rtp packet.\n" ); #else fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump\n" ); fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); @@ -3270,7 +3473,7 @@ static ivas_error printBitstreamInfoVoip( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, NULL, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = readNextFrame( &ivasRtp, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); #endif @@ -3373,8 +3576,10 @@ static ivas_error decodeVoIP( int16_t i; FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP - IVAS_RTP ivasRtp = {0}; + IVAS_RTP ivasRtp = { 0 }; IVAS_DEC_HANDLE hIvasDec = *phIvasDec; + FILE *f_piDataOut = NULL; + int32_t initialTsOffsetSystemAndRTP = 0; #else EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; @@ -3441,7 +3646,17 @@ static ivas_error decodeVoIP( } #ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream ) ) != IVAS_ERR_OK ) + if ( arg.piOutputFilename != NULL ) + { + f_piDataOut = fopen( arg.piOutputFilename, "w" ); + if ( f_piDataOut == NULL ) + { + fprintf( stderr, "could not open: %s\n", arg.piOutputFilename ); + goto cleanup; + } + fprintf( f_piDataOut, "{\n" ); + } + if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream, f_piDataOut ) ) != IVAS_ERR_OK ) { fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); goto cleanup; @@ -3496,7 +3711,8 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, hIvasDec, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = readNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + initialTsOffsetSystemAndRTP = rtpTimeStamp - systemTime_ms * 16; /* For time mapping */ #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif @@ -3700,7 +3916,7 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, hIvasDec, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = readNextFrame( &ivasRtp, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; @@ -3745,6 +3961,18 @@ static ivas_error decodeVoIP( /* decode and get samples */ while ( nSamplesRendered < nOutSamples ) { +#ifdef IVAS_RTPDUMP + if ( arg.applyPiData ) + { + /* Rudimentry Time Mapping to map system time to rtp timestamp */ + uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; + if ( ( error = applyPiData( &ivasRtp, hIvasDec, piTs ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + } +#endif #ifdef SUPPORT_JBM_TRACEFILE if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else diff --git a/apps/encoder.c b/apps/encoder.c index 14db97bc1a..00c7cb519a 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -225,7 +225,7 @@ int main( IVAS_RTP_PACK_HANDLE hPack = NULL; uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; - uint8_t packet[IVAS_NOMINAL_RTP_BYTES_PER_FRAME * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + uint8_t packet[NOMINAL_BUFFER_SIZE(IVAS_MAX_FRAMES_PER_RTP_PACKET)]; IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; uint32_t numFramesInPayload = 0, packetLength = 0; diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 597258f9f1..5e269eaa43 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -82,7 +82,9 @@ extern "C" * estimate or RTP packet size per frame. The additional PI data is assumed to add upto a 20% * overhead in bitrate for the computation. */ -#define IVAS_NOMINAL_RTP_BYTES_PER_FRAME ( ( IVAS_MAX_BITS_PER_FRAME + ( IVAS_MAX_BITS_PER_FRAME / 5 ) ) / 8 ) +#define NOMINAL_BUFFER_SIZE( numFramesPerPacket ) ( ( IVAS_MAX_BITS_PER_FRAME + ( IVAS_MAX_BITS_PER_FRAME / 5 ) ) * ( numFramesPerPacket ) / 8 ) + +#define DEFAULT_MAX_PACKET_BYTES ( 1400 ) /* Typical MTU size of 4G/5G Cellular Interfaces */ #define NO_BITRATE_REQ ( 0u ) /* If no bitrate is requested from remote */ @@ -375,8 +377,8 @@ extern "C" /* Generate a rtp payload using available pushed frames * - * Available remote requests, pi data and frames will be packed into a rtp payload. If no - * frame is pushed before call to this api, NO_DATA_FRAME will be generated + * Available remote requests, pi data and frames will be packed into a rtp payload. The + * capacity field of payload is used to limits the maximum RTP packet size. * */ ivas_error IVAS_RTP_PACK_GetPayload( @@ -408,7 +410,7 @@ extern "C" /* Initial configuration for rtp unpacker */ typedef struct { - uint32_t jitterMarginMs; /* expected max jitter duration in milliseconds */ + uint32_t maxFramesPerPacket; /* maximum no of frame per packet expected during the session */ } IVAS_RTP_UNPACK_CONFIG; /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success @@ -432,6 +434,10 @@ extern "C" * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. * + * It is important to ensure IVAS_RTP_UNPACK_PushPayload, IVAS_RTP_UNPACK_PullFrame and + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context else a race condition + * can arise if new packet is pushed while frames/pidata are still being pulled out. + * * Example usage : - * ================== * err = IVAS_RTP_UNPACK_PushPayload(hIvasUnpack, payload, rtpTs, seqNum, @@ -489,6 +495,10 @@ extern "C" * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. * + * It is important to ensure IVAS_RTP_UNPACK_PushPacket, IVAS_RTP_UNPACK_PullFrame and + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context else a race condition + * can arise if new packet is pushed while frames/pidata are still being pulled out. + * * Example usage : - * ================== * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, &nFrames, &nPiData, &reqBitmap); diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 367aea06c8..779c627274 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -489,6 +489,7 @@ ivas_error IVAS_RTP_PACK_Open( { ivas_error error; IVAS_RTP_PACK_HANDLE hPack; + uint32_t numFramesPerPacket = ( config->maxFramesPerPacket == 0 ) ? 1 : config->maxFramesPerPacket; if ( phPack == NULL ) { @@ -501,7 +502,7 @@ ivas_error IVAS_RTP_PACK_Open( return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtppack handle" ); } - error = BPOOL_Create( &hPack->packNodePool, sizeof( FRAME_NODE ), IVAS_MAX_FRAMES_IN_QUEUE ); + error = BPOOL_Create( &hPack->packNodePool, sizeof( FRAME_NODE ), numFramesPerPacket * 4 ); ERR_CHECK_RETURN( error ); error = QUEUE_Create( &hPack->frameQ ); @@ -1192,9 +1193,7 @@ ivas_error IVAS_RTP_UNPACK_Open( return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtp unpack handle" ); } - /* maxNumberOfFrames = Ping Pong RTP Packet with max frames per packet + any jitter adjustment frames */ - hUnpack->maxNumberOfFrames += IVAS_MAX_FRAMES_PER_RTP_PACKET * 2; - hUnpack->maxNumberOfFrames += ( ( config->jitterMarginMs * IVAS_NUM_FRAMES_PER_SEC + 1000 - 1 ) / 1000 ); /* Jitter in Packets + Ping/Pong */ + hUnpack->maxNumberOfFrames = ( config->maxFramesPerPacket < IVAS_MAX_FRAMES_PER_RTP_PACKET ) ? IVAS_MAX_FRAMES_PER_RTP_PACKET : config->maxFramesPerPacket; hUnpack->maxNumberOfPiData = hUnpack->maxNumberOfFrames * IVAS_PI_MAX_ID; error = BPOOL_Create( &hUnpack->unpackNodePool, sizeof( UNPACK_NODE ), hUnpack->maxNumberOfFrames ); @@ -1544,7 +1543,16 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes } } while ( byte == 255 ); - if ( nBytes + piSize <= payload->length ) + if ( piDataType == IVAS_PI_NO_DATA ) + { + if ( piSize > 0 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "NO_PI_DATA should be 0 sized" ); + } + + /* Do not add a node for NO_DATA */ + } + else if ( nBytes + piSize <= payload->length ) { PIDATA_NODE *node = NULL; ivas_error error = BPOOL_GetBuffer( hUnpack->piDataPool, (void **) &node ); @@ -1614,6 +1622,13 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( *numPiDataInPacket = 0; } + /* Sanity check if any frame or PI data from last packet is still not pulled */ + if ( QUEUE_Size( hUnpack->frameQ ) > 0 || QUEUE_Size( hUnpack->piDataQ ) > 0 ) + { + assert( 0 ); + return IVAS_ERROR( IVAS_ERR_INTERNAL, "Previous packet data should be consumed before next packet is pushed" ); + } + memcpy( oldRequests, hUnpack->requests, sizeof( oldRequests ) ); /* Unpack IVAS Payload, starting with the E-Bytes */ diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index 8e918d76a2..b88bb0e661 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -40,14 +40,14 @@ typedef ivas_error ( *PACK_PI_FN )( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ); typedef ivas_error ( *UNPACK_PI_FN )( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ); -static uint32_t writeInt16( uint8_t *buffer, uint32_t idx, int16_t val ) +static __inline uint32_t writeInt16( uint8_t *buffer, uint32_t idx, int16_t val ) { buffer[idx++] = (uint8_t) ( val >> 8 ); buffer[idx++] = (uint8_t) ( val & 0x00FF ); return idx; } -static uint16_t readInt16( const uint8_t *buffer ) +static __inline uint16_t readInt16( const uint8_t *buffer ) { return ( (uint16_t) buffer[0] << 8 ) | ( (uint16_t) buffer[1] ); } @@ -295,7 +295,7 @@ static ivas_error unpackAcousticEnvironment( const uint8_t *buffer, uint32_t num dWord <<= 8; dWord |= buffer[n]; } - dWord <<= ( 8 - numDataBytes ); + dWord <<= ( 8 - numDataBytes ) * 8; aeEnv->aeid = (uint8_t) ( ( dWord >> 57 ) & MASK_AEID ); aeEnv->rt60[IVAS_PI_AE_LOW] = mapRT60[( dWord >> 52 ) & MASK_RT60]; @@ -424,6 +424,119 @@ static ivas_error unpackDynamicSuppression( const uint8_t *buffer, uint32_t numD return IVAS_ERR_OK; } +static ivas_error packListenerPosition( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + const IVAS_PIDATA_LISTENER_POSITION *listener = (const IVAS_PIDATA_LISTENER_POSITION *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_LISTENER_POSITION ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in LISTENER POSITION PI data" ); + } + + if ( piData->piDataType != IVAS_PI_LISTENER_POSITION ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in LISTENER POSITION PI data" ); + } + + /* Position data is 6 bytes, header is 2 bytes */ + if ( maxDataBytes < 6 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack LISTENER POSITION PI data" ); + } + + buffer[nBytes++] = ( listener->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 6; + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.x / 327.68f ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.y / 327.68f ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.z / 327.68f ) ); + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackListenerPosition( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_LISTENER_POSITION *listener = (IVAS_PIDATA_LISTENER_POSITION *) piData; + + /* Position data is 6 bytes */ + if ( numDataBytes != 6 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack LISTENER POSITION PI data" ); + } + + listener->size = sizeof( IVAS_PIDATA_LISTENER_POSITION ); + listener->piDataType = IVAS_PI_LISTENER_POSITION; + listener->position.x = ( (int16_t) readInt16( &buffer[0] ) ) / 100.0f; + listener->position.y = ( (int16_t) readInt16( &buffer[2] ) ) / 100.0f; + listener->position.z = ( (int16_t) readInt16( &buffer[4] ) ) / 100.0f; + return IVAS_ERR_OK; +} + +static ivas_error packDiegetic( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0, n; + uint8_t byte = 0; + const IVAS_PIDATA_DIEGETIC *diegetic = (const IVAS_PIDATA_DIEGETIC *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_DIEGETIC ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in Diegetic Type PI data" ); + } + + if ( piData->piDataType != IVAS_PI_DIEGETIC_TYPE ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect PI ID in Diegetic Type PI data" ); + } + + /* Diegetic data is 1 bytes, header is 2 bytes */ + if ( maxDataBytes < 1 + 2 ) + { + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Diegetic Type PI data" ); + } + + for ( n = 0; n < 5; n++ ) + { + byte <<= 1; + byte |= ( diegetic->isDiegetic[n] ); + } + byte <<= 3; + + buffer[nBytes++] = ( diegetic->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = 1; + buffer[nBytes++] = byte; + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackDiegetic( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + uint32_t n; + IVAS_PIDATA_DIEGETIC *diegetic = (IVAS_PIDATA_DIEGETIC *) piData; + uint8_t byte; + + /* Diegetic data is 1 bytes */ + if ( numDataBytes != 1 ) + { + return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Diegetic PI data" ); + } + + diegetic->size = sizeof( IVAS_PIDATA_DIEGETIC ); + diegetic->piDataType = IVAS_PI_DIEGETIC_TYPE; + + byte = buffer[0]; + for ( n = 0; n < 5; n++ ) + { + diegetic->isDiegetic[n] = ( ( byte >> ( 7 - n ) ) & 1 ) != 0; + } + + return IVAS_ERR_OK; +} + + #endif /* RTP_S4_251135_CR26253_0016_REV1 */ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { @@ -434,7 +547,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #ifdef RTP_S4_251135_CR26253_0016_REV1 packAudioDescription, /* AUDIO_DESCRIPTION */ #else - packUnsupportedData, /* AUDIO_DESCRIPTION */ + packUnsupportedData, /* AUDIO_DESCRIPTION */ #endif /* RTP_S4_251135_CR26253_0016_REV1 */ packUnsupportedData, /* ISM_NUM */ packUnsupportedData, /* ISM_ID */ @@ -443,24 +556,26 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* ISM_POSITION */ packUnsupportedData, /* ISM_DISTANCE_ATTENUATION */ packUnsupportedData, /* ISM_DIRECTIVITY */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + packDiegetic, /* DIEGETIC_TYPE */ +#else packUnsupportedData, /* DIEGETIC_TYPE */ +#endif packUnsupportedData, /* RESERVED13 */ packUnsupportedData, /* RESERVED14 */ packUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ - packOrientation, /* HEAD_ORIENTATION */ -#else - packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ - packUnsupportedData, /* HEAD_ORIENTATION */ -#endif - packUnsupportedData, /* LISTENER_POSITION */ -#ifdef RTP_S4_251135_CR26253_0016_REV1 + packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ + packOrientation, /* HEAD_ORIENTATION */ + packListenerPosition, /* LISTENER_POSITION */ packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ packAudioDescription, /* AUDIO_FOCUS_DIRECTION */ #else - packUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ - packUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ + packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ + packUnsupportedData, /* HEAD_ORIENTATION */ + packUnsupportedData, /* LISTENER_POSITION */ + packUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ + packUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ #endif packUnsupportedData, /* PI_LATENCY */ packUnsupportedData, /* R_ISM_ID */ @@ -468,7 +583,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #ifdef RTP_S4_251135_CR26253_0016_REV1 packOrientation, /* R_ISM_ORIENTATION */ #else - packUnsupportedData, /* R_ISM_ORIENTATION */ + packUnsupportedData, /* R_ISM_ORIENTATION */ #endif packUnsupportedData, /* R_ISM_POSITION */ packUnsupportedData, /* R_ISM_DIRECTION */ @@ -496,22 +611,24 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* ISM_POSITION */ unpackUnsupportedData, /* ISM_DISTANCE_ATTENUATION */ unpackUnsupportedData, /* ISM_DIRECTIVITY */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + unpackDiegetic, /* DIEGETIC_TYPE */ +#else unpackUnsupportedData, /* DIEGETIC_TYPE */ +#endif unpackUnsupportedData, /* RESERVED13 */ unpackUnsupportedData, /* RESERVED14 */ unpackUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ - unpackOrientation, /* HEAD_ORIENTATION */ + unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ + unpackOrientation, /* HEAD_ORIENTATION */ + unpackListenerPosition, /* LISTENER_POSITION */ + unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ + unpackOrientation, /* AUDIO_FOCUS_DIRECTION */ #else unpackUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ unpackUnsupportedData, /* HEAD_ORIENTATION */ -#endif unpackUnsupportedData, /* LISTENER_POSITION */ -#ifdef RTP_S4_251135_CR26253_0016_REV1 - unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - unpackOrientation, /* AUDIO_FOCUS_DIRECTION */ -#else unpackUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ unpackUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ #endif diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 5780f32d8f..ba82914df1 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -296,9 +296,9 @@ extern "C" /* Listener position */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_LISTENER_POSITION) */ - uint32_t piDataType; /* IVAS_PI_LISTENER_POSITION */ - IVAS_COORDINATE position[IVAS_PI_MAX_OBJECTS]; /* Position of audio objects in ISM(s) */ + size_t size; /* sizeof(IVAS_PIDATA_LISTENER_POSITION) */ + uint32_t piDataType; /* IVAS_PI_LISTENER_POSITION */ + IVAS_COORDINATE position; /* Position of audio objects in ISM(s) */ } IVAS_PIDATA_LISTENER_POSITION; diff --git a/tests/requirements.txt b/tests/requirements.txt index 2eb090f4fb..a8d8f7b064 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,3 +2,5 @@ pytest>=5.3.5 pytest-xdist>=1.31.0 scipy>=1.5.2 numpy>=1.19.2 +bitstring>=4.3.1 +soundfile>=0.13 diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index ecb3924866..f6923c0077 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -37,10 +37,24 @@ To configure test modules. import struct from enum import Enum from dataclasses import dataclass, field, asdict -from bitstring import ConstBitStream, ReadError +from bitstring import ConstBitStream, BitStream, ReadError import json import base64 import argparse +from pathlib import Path +from typing import cast + +NO_REQ="NO_REQ" + +class CAMODE(str, Enum): + CA_LO_02 = "CA-LO-O2" + CA_LO_03 = "CA-LO-O3" + CA_LO_05 = "CA-LO-O5" + CA_LO_07 = "CA-LO-O7" + CA_HI_02 = "CA-HI-O2" + CA_HI_03 = "CA-HI-O3" + CA_HI_05 = "CA-HI-O5" + CA_HI_07 = "CA-HI-O7" class CODECS(str, Enum): AMRWB = "amrwb_io" @@ -50,14 +64,12 @@ class CODECS(str, Enum): class SRCODEC(str, Enum): LCLD = "lcld" LC3PLUS = "lc3+" - NA = "NONE" class BANDWIDTH(str, Enum): NB = "narrowband" WB = "wideband" SWB = "super wideband" FB = "fullband" - NREQ = "NO_REQ" class REQUESTS(str, Enum): CODEC = "codec", @@ -68,6 +80,134 @@ class REQUESTS(str, Enum): SUBFMT = "sub-format" SRCFG = "sr-config" +class FORMATS(str, Enum): + STEREO="Stereo" + SBA="SBA" + MASA="MASA" + ISM="ISM" + MC="MC" + OMASA="OMASA" + OSBA="OSBA" + +class SUBFORMATS(str, Enum): + FOA_PLANAR = "FOA planar" + HOA2_PLANAR = "HOA2 planar" + HOA3_PLANAR = "HOA3 planar" + FOA = "FOA" + HOA2 = "HOA2" + HOA3 = "HOA3" + MASA1 = "MASA1" + MASA2 = "MASA2" + ISM1 = "ISM1" + ISM2 = "ISM2" + ISM3 = "ISM3" + ISM4 = "ISM4" + ISM1_EXTENDED_METADATA = "ISM1 extended metadata" + ISM2_EXTENDED_METADATA = "ISM2 extended metadata" + ISM3_EXTENDED_METADATA = "ISM3 extended metadata" + ISM4_EXTENDED_METADATA = "ISM4 extended metadata" + MC_5_1 = "MC 5.1" + MC_7_1 = "MC 7.1" + MC_5_1_2 = "MC 5.1.2" + MC_5_1_4 = "MC 5.1.4" + MC_7_1_4 = "MC 7.1.4" + Reserved22 = "Reserved22" + Reserved23 = "Reserved23" + Reserved24 = "Reserved24" + Reserved25 = "Reserved25" + Reserved26 = "Reserved26" + Reserved27 = "Reserved27" + Reserved28 = "Reserved28" + Reserved29 = "Reserved29" + Reserved30 = "Reserved30" + Reserved31 = "Reserved31" + Reserved32 = "Reserved32" + OMASA_ISM1_1TC = "OMASA ISM1 1TC" + OMASA_ISM2_1TC = "OMASA ISM2 1TC" + OMASA_ISM3_1TC = "OMASA ISM3 1TC" + OMASA_ISM4_1TC = "OMASA ISM4 1TC" + OMASA_ISM1_2TC = "OMASA ISM1 2TC" + OMASA_ISM2_2TC = "OMASA ISM2 2TC" + OMASA_ISM3_2TC = "OMASA ISM3 2TC" + OMASA_ISM4_2TC = "OMASA ISM4 2TC" + OSBA_ISM1_FOA_PLANAR = "OSBA ISM1 FOA planar" + OSBA_ISM2_FOA_PLANAR = "OSBA ISM2 FOA planar" + OSBA_ISM3_FOA_PLANAR = "OSBA ISM3 FOA planar" + OSBA_ISM4_FOA_PLANAR = "OSBA ISM4 FOA planar" + OSBA_ISM1_FOA = "OSBA ISM1 FOA" + OSBA_ISM2_FOA = "OSBA ISM2 FOA" + OSBA_ISM3_FOA = "OSBA ISM3 FOA" + OSBA_ISM4_FOA = "OSBA ISM4 FOA" + OSBA_ISM1_HOA2_PLANAR = "OSBA ISM1 HOA2 planar" + OSBA_ISM2_HOA2_PLANAR = "OSBA ISM2 HOA2 planar" + OSBA_ISM3_HOA2_PLANAR = "OSBA ISM3 HOA2 planar" + OSBA_ISM4_HOA2_PLANAR = "OSBA ISM4 HOA2 planar" + OSBA_ISM1_HOA2 = "OSBA ISM1 HOA2" + OSBA_ISM2_HOA2 = "OSBA ISM2 HOA2" + OSBA_ISM3_HOA2 = "OSBA ISM3 HOA2" + OSBA_ISM4_HOA2 = "OSBA ISM4 HOA2" + OSBA_ISM1_HOA3_PLANAR = "OSBA ISM1 HOA3 planar" + OSBA_ISM2_HOA3_PLANAR = "OSBA ISM2 HOA3 planar" + OSBA_ISM3_HOA3_PLANAR = "OSBA ISM3 HOA3 planar" + OSBA_ISM4_HOA3_PLANAR = "OSBA ISM4 HOA3 planar" + OSBA_ISM1_HOA3 = "OSBA ISM1 HOA3" + OSBA_ISM2_HOA3 = "OSBA ISM2 HOA3" + OSBA_ISM3_HOA3 = "OSBA ISM3 HOA3" + OSBA_ISM4_HOA3 = "OSBA ISM4 HOA3" + +class PIDATAS(str, Enum): + SCENE_ORIENTATION = "SCENE_ORIENTATION" + DEVICE_ORIENTATION_COMPENSATED = "DEVICE_ORIENTATION_COMPENSATED" + DEVICE_ORIENTATION_UNCOMPENSATED = "DEVICE_ORIENTATION_UNCOMPENSATED" + ACOUSTIC_ENVIRONMENT = "ACOUSTIC_ENVIRONMENT" + AUDIO_DESCRIPTION = "AUDIO_DESCRIPTION" + ISM_NUM = "ISM_NUM" + ISM_ID = "ISM_ID" + ISM_GAIN = "ISM_GAIN" + ISM_ORIENTATION = "ISM_ORIENTATION" + ISM_POSITION = "ISM_POSITION" + ISM_DISTANCE_ATTENUATION = "ISM_DISTANCE_ATTENUATION" + ISM_DIRECTIVITY = "ISM_DIRECTIVITY" + DIEGETIC_TYPE = "DIEGETIC_TYPE" + RESERVED13 = "RESERVED13" + RESERVED14 = "RESERVED14" + RESERVED15 = "RESERVED15" + PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" + HEAD_ORIENTATION = "HEAD_ORIENTATION" + LISTENER_POSITION = "LISTENER_POSITION" + DYNAMIC_AUDIO_SUPPRESSION = "DYNAMIC_AUDIO_SUPPRESSION" + AUDIO_FOCUS_DIRECTION = "AUDIO_FOCUS_DIRECTION" + PI_LATENCY = "PI_LATENCY" + R_ISM_ID = "R_ISM_ID" + R_ISM_GAIN = "R_ISM_GAIN" + R_ISM_ORIENTATION = "R_ISM_ORIENTATION" + R_ISM_POSITION = "R_ISM_POSITION" + R_ISM_DIRECTION = "R_ISM_DIRECTION" + RESERVED27 = "RESERVED27" + RESERVED28 = "RESERVED28" + RESERVED29 = "RESERVED29" + RESERVED30 = "RESERVED30" + NO_PI_DATA = "NO_PI_DATA" + +class SUPPRESSION_LEVEL(int, Enum): + SUPPRESSION_LEVEL_NONE = 0 + SUPPRESSION_LEVEL_1 = 1 + SUPPRESSION_LEVEL_2 = 2 + SUPPRESSION_LEVEL_3 = 3 + SUPPRESSION_LEVEL_4 = 4 + SUPPRESSION_LEVEL_5 = 5 + SUPPRESSION_LEVEL_6 = 6 + SUPPRESSION_LEVEL_7 = 7 + SUPPRESSION_LEVEL_8 = 8 + SUPPRESSION_LEVEL_9 = 9 + SUPPRESSION_LEVEL_10 = 10 + SUPPRESSION_LEVEL_11 = 11 + SUPPRESSION_LEVEL_12 = 12 + SUPPRESSION_LEVEL_13 = 13 + SUPPRESSION_LEVEL_14 = 14 + SUPPRESSION_LEVEL_MAX = 15 + + @dataclass class RTPHDR: version: int = 2 @@ -82,31 +222,54 @@ class RTPHDR: extensionType: int = 0 extensionLength: int = 0 csrcList: list = field(default_factory=list) - extensionBytes: list = field(default_factory=list) - - def __init__(self, bitstrm: ConstBitStream): - self.version = bitstrm.read(2).uint - self.padding = bitstrm.read(1).bool - self.extension = bitstrm.read(1).bool - self.csrcCount = bitstrm.read(4).int - self.marker = bitstrm.read(1).bool - self.payloadType = bitstrm.read(7).int - self.sequenceNum = bitstrm.read(16).uintbe - self.timestamp = bitstrm.read(32).uintbe - self.ssrc = bitstrm.read(32).uintbe - self.csrcList = [ bitstrm.read(32).uintbe for _ in range(self.csrcCount) ] + extensionWords: list = field(default_factory=list) + + def updateHeader(self, numFrames: int): + self.sequenceNum = (self.sequenceNum + 1) % 65536 + self.timestamp += 320 * numFrames + + def pack(self, bitstrm: BitStream): + bitstrm.append(f'uint:2={self.version}') + bitstrm.append(f'bool={self.padding}') + bitstrm.append(f'bool={self.extension}') + bitstrm.append(f'uint:4={self.csrcCount}') + bitstrm.append(f'bool={self.marker}') + bitstrm.append(f'uint:7={self.payloadType}') + bitstrm.append(f'uintbe:16={self.sequenceNum}') + bitstrm.append(f'uintbe:32={self.timestamp}') + bitstrm.append(f'uintbe:32={self.ssrc}') + assert len(self.csrcList) == self.csrcCount, "csrcList must be of length csrcCount" + for csrc in self.csrcList: + bitstrm.append(f'uintbe:32={csrc}') if self.extension: - self.extensionType = bitstrm.read(16).uintbe - self.extensionLength = bitstrm.read(16).uintbe - self.extensionBytes = [ bitstrm.read(32).uintbe for _ in range(self.extensionLength) ] - else: - self.extensionType = 0 - self.extensionLength = 0 - self.extensionBytes = list() + bitstrm.append(f'uintbe:16={self.extensionType}') + bitstrm.append(f'uintbe:16={self.extensionLength}') + assert len(self.extensionWords) == self.extensionLength, "extensionWords must be of extensionLength csrcCount" + for ext in self.extensionWords: + bitstrm.append(f'uintbe:32={ext}') + + @classmethod + def unpack(cls, bitstrm: ConstBitStream): + hdr = cls() + hdr.version = bitstrm.read(2).uint + hdr.padding = bitstrm.read(1).bool + hdr.extension = bitstrm.read(1).bool + hdr.csrcCount = bitstrm.read(4).int + hdr.marker = bitstrm.read(1).bool + hdr.payloadType = bitstrm.read(7).int + hdr.sequenceNum = bitstrm.read(16).uintbe + hdr.timestamp = bitstrm.read(32).uintbe + hdr.ssrc = bitstrm.read(32).uintbe + if hdr.csrcCount: + hdr.csrcList = [ bitstrm.read(32).uintbe for _ in range(hdr.csrcCount) ] + if hdr.extension: + hdr.extensionType = bitstrm.read(16).uintbe + hdr.extensionLength = bitstrm.read(16).uintbe + hdr.extensionWords = [ bitstrm.read(32).uintbe for _ in range(hdr.extensionLength) ] + return hdr @dataclass class SRCONFIG: - enabled: bool = False diegetic: bool = False yaw: bool = False pitch: bool = False @@ -124,7 +287,7 @@ class CMR: class SRINFO: bitrate: int = 0 diegetic: bool = False - transportCodec: SRCODEC = SRCODEC.NA + transportCodec: SRCODEC = SRCODEC.LCLD @dataclass class FRAME: @@ -136,6 +299,7 @@ class FRAME: timestamp: int = 0 au: bytes = field(default_factory=bytes) +#PI DATA STRUCTURES @dataclass class ORIENTATION: w: float = 0.0 @@ -143,15 +307,73 @@ class ORIENTATION: y: float = 0.0 z: float = 0.0 +@dataclass +class POSITION: + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + +class ISM_POSITIONS: + positions: list[POSITION] + +@dataclass +class AUDIO_DESCRIPTION: + isSpeech: bool = False + isMusic: bool = False + isAmbiance: bool = False + isEditable: bool = False + isBinaural: bool = False + +@dataclass +class DYNAMIC_AUDIO_SUPPRESSION: + preferSpeech: bool = False + preferMusic: bool = False + preferAmbiance: bool = False + level: SUPPRESSION_LEVEL = SUPPRESSION_LEVEL.SUPPRESSION_LEVEL_MAX + +@dataclass +class DIEGETIC_TYPE: + isDigetic: list[bool] + +@dataclass +class ACOUSTIC_ENVIRONMENT: + aeid: int = 0 + rt60: tuple[float, float, float] = () + dsr: tuple[float, float, float] = () + dim: tuple[float, float, float] = () + abscoeff: tuple[float, float, float, float, float, float] = () + @dataclass class PIDATA: timestamp: int = 0 type: str = "NO_PI_DATA" data: any = None +MAX_PACKED_PI_SIZE = 32 ivasBitrates = [13200, 16400, 24400, 32000, 48000, 64000, 80000, 96000, 128000, 160000, 192000, 256000, 384000, 512000, -1, 5200] evsBitrates = [5900, 7200, 8000, 9600, 13200, 16400, 24400, 32000, 48000, 64000, 96000, 128000, 2400, -1, -1, -1] amrwbBitrates = [6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850, 1750, -1, -1, -1, -1, -1, -1] +requestBitratesForCodec = {CODECS.AMRWB: amrwbBitrates[0:9], CODECS.EVS: evsBitrates[0:12], CODECS.IVAS: ivasBitrates[0:14]} +rt60Value = [0.01, 0.0126, 0.0159, 0.02, 0.0252, 0.0317, 0.04, 0.0504, 0.0635, 0.08, 0.1008, 0.1270, 0.16, 0.2016, 0.2540, 0.32, 0.4032, + 0.5080, 0.64, 0.8063, 1.0159, 1.28, 1.6127, 2.0319, 2.56, 3.2254, 4.0637, 5.12, 6.4508, 8.1275, 10.24, 12.9016 ] +dsrValue = [ -20.0, -21.0, -22.0, -23.0, -24.0, -25.0, -26.0, -27.0, -28.0, -29.0, -30.0, -31.0, -32.0, -33.0, -34.0, -35.0, -36.0, -37.0, + -38.0, -39.0, -40.0, -41.0, -42.0, -43.0, -44.0, -45.0, -46.0, -47.0, -48.0, -49.0, -50.0, -51.0, -52.0, -53.0, -54.0, -55.0, + -56.0, -57.0, -58.0, -59.0, -60.0, -61.0, -62.0, -63.0, -64.0, -65.0, -66.0, -67.0, -68.0, -69.0, -70.0, -71.0, -72.0, -73.0, + -74.0, -75.0, -76.0, -77.0, -78.0, -79.0, -80.0, -81.0, -82.0, -83.0 ] +roomDimensionValue = [0.5, 0.707, 1.0, 1.4141, 2.0, 2.8282, 4.0, 5.6568, 8.0, 11.314, 16.0, 22.627, 32.0, 45.255, 64.0, 90.51] +absorptionCoeffValues = [0.0800, 0.1656, 0.3430, 0.7101] +codedFormats = list(FORMATS) +codedSubFormats = list(SUBFORMATS) +PiTypeNames = list(PIDATAS) + +def mapNearestIndex(table: list, val: float) -> int: + for idx, entry in enumerate(table): + if abs(entry) >= abs(val): + return idx + return len(table) - 1 + +getListIndex = lambda mylist, val: mylist.index(val) if val in mylist else -1 + cmrLookup = [ CMR(bandwidth=BANDWIDTH.NB, codec=CODECS.EVS, startIdx=0, endIdx=7, bitrates = evsBitrates), #000 = NB-EVS CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.AMRWB, startIdx=0, endIdx=9, bitrates = amrwbBitrates), #001 = AMRWB IO @@ -160,74 +382,225 @@ cmrLookup = [ CMR(bandwidth=BANDWIDTH.FB, codec=CODECS.EVS, startIdx=5, endIdx=12, bitrates = evsBitrates), #100 = FB-EVS CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #101 = WB-CA CMR(bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #110 = SWB-CA - CMR(bandwidth=BANDWIDTH.NREQ, codec=CODECS.IVAS, startIdx=0, endIdx=14, bitrates = ivasBitrates), #111 = IVAS + CMR(bandwidth=NO_REQ, codec=CODECS.IVAS, startIdx=0, endIdx=14, bitrates = ivasBitrates), #111 = IVAS ] -codedFormats = ["Stereo", "SBA", "MASA", "ISM", "MC", "OMASA", "OSBA", "NO_REQ"] -codedSubFormats = [ - "FOA planar", "HOA2 planar", "HOA3 planar", "FOA", "HOA2", "HOA3", "MASA1", "MASA2", "ISM1", - "ISM2", "ISM3", "ISM4", "ISM1 extended metadata", "ISM2 extended metadata", - "ISM3 extended metadata", "ISM4 extended metadata", "MC 5.1", "MC 7.1", "MC 5.1.2", "MC 5.1.4", - "MC 7.1.4", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", - "Reserved", "Reserved", "Reserved", "Reserved", "OMASA ISM1 1TC", "OMASA ISM2 1TC", - "OMASA ISM3 1TC", "OMASA ISM4 1TC", "OMASA ISM1 2TC", "OMASA ISM2 2TC", "OMASA ISM3 2TC", - "OMASA ISM4 2TC", "OSBA ISM1 FOA planar", "OSBA ISM2 FOA planar", "OSBA ISM3 FOA planar", - "OSBA ISM4 FOA planar", "OSBA ISM1 FOA", "OSBA ISM2 FOA", "OSBA ISM3 FOA", "OSBA ISM4 FOA", - "OSBA ISM1 HOA2 planar", "OSBA ISM2 HOA2 planar", "OSBA ISM3 HOA2 planar", "OSBA ISM4 HOA2 planar", - "OSBA ISM1 HOA2", "OSBA ISM2 HOA2", "OSBA ISM3 HOA2", "OSBA ISM4 HOA2", "OSBA ISM1 HOA3 planar", - "OSBA ISM2 HOA3 planar", "OSBA ISM3 HOA3 planar", "OSBA ISM4 HOA3 planar", "OSBA ISM1 HOA3", - "OSBA ISM2 HOA3", "OSBA ISM3 HOA3", "OSBA ISM4 HOA3"] -PiTypeNames = [ - "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", - "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", - "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", "RESERVED14", - "RESERVED15","PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", "DYNAMIC_AUDIO_SUPPRESSION", - "AUDIO_FOCUS_DIRECTION", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", - "RESERVED27", "RESERVED28", "RESERVED29", "RESERVED30", "NO_PI_DATA" -] +q15 = lambda x : int(min(32767.0, max(-32768.0, x * 32768.0))) def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: #assert False, "Unsupported PI Data" return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode('utf-8') +def packUnsupported(bitstrm: ConstBitStream, data: any) -> any: + assert False, f"unsupported PI Data of type : {type(data)}" + def unpackNoPiData(bitstrm: ConstBitStream, piSize: int) -> None: assert piSize == 0, "NO_PI_DATA should be 0 size" +def packNoPiData(bitstrm: BitStream, data: any = None): + pass + +def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION]: + assert (piSize % 8) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" + orientations = list() + while piSize > 0: + w = bitstrm.read(16).int / 32768.0 + x = bitstrm.read(16).int / 32768.0 + y = bitstrm.read(16).int / 32768.0 + z = bitstrm.read(16).int / 32768.0 + orientations.append(ORIENTATION(w, x, y, z)) + piSize -= 8 + return orientations + +def packOrientations(bitstrm: BitStream, data: any): + assert type(data) == list, "Orientation PI Data expects a data of type list" + for orientation in cast(list, data): + assert type(orientation) == ORIENTATION, "Orientation PI Data expects a data of type list[ORIENTATION]" + bitstrm.append(f'intbe:16={q15(orientation.w)}') + bitstrm.append(f'intbe:16={q15(orientation.x)}') + bitstrm.append(f'intbe:16={q15(orientation.y)}') + bitstrm.append(f'intbe:16={q15(orientation.z)}') + +def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: + assert piSize <= 24 and (piSize % 6) == 0, "Incorrect PI Data Size for Positions" + positions = list() + while piSize > 0: + x = bitstrm.read(16).int / 100.0 + y = bitstrm.read(16).int / 100.0 + z = bitstrm.read(16).int / 100.0 + positions.append(POSITION(x, y, z)) + piSize -= 6 + return positions + +def packPositions(bitstrm: BitStream, data: any): + assert type(data) == list, "Position PI Data expects a data of type list" + positions = cast(list, data) + assert len(positions) <= 4, "Max one position per ISM object" + for position in positions: + assert type(position) == POSITION, "Position PI Data expects a data of type list[POSITIONS]" + bitstrm.append(f'intbe:16={q15(position.x / 327.68)}') + bitstrm.append(f'intbe:16={q15(position.y / 327.68)}') + bitstrm.append(f'intbe:16={q15(position.z / 327.68)}') + def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: - assert piSize == 8, "Incorrect PI Data Size for Orientation" - w = bitstrm.read(16).int / 32768.0 - x = bitstrm.read(16).int / 32768.0 - y = bitstrm.read(16).int / 32768.0 - z = bitstrm.read(16).int / 32768.0 - return ORIENTATION(w, x, y, z) + assert piSize == 8, "Incorrect PI Data Size for ORIENTATION" + orientations = unpackOrientations(bitstrm, piSize) + assert len(orientations) == 1 + return orientations[0] + +def packOrientation(bitstrm: BitStream, data: any): + assert type(data) == ORIENTATION, "Orientation PI Data expects a data of type ORIENTATION" + orientation = cast(ORIENTATION, data) + packOrientations(bitstrm, [orientation]) + +def unpackPosition(bitstrm: ConstBitStream, piSize: int) -> POSITION: + assert piSize == 6, "Incorrect PI Data Size for POSITION" + positions = unpackPositions(bitstrm, piSize) + assert len(positions) == 1 + return positions[0] + +def packPosition(bitstrm: BitStream, data: any): + assert type(data) == POSITION, "Position PI Data expects a data of type POSITION" + position = cast(POSITION, data) + packPositions(bitstrm, [position]) + +def unpackAudioDescription(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: + assert piSize <= 5, "Incorrect PI Data Size for AUDIO_DESCRIPTION" + ad = list() + for byte in range(piSize): + V = bitstrm.read(1).bool + M = bitstrm.read(1).bool + A = bitstrm.read(1).bool + E = bitstrm.read(1).bool + B = bitstrm.read(1).bool + _ = bitstrm.read(3) + ad.append(AUDIO_DESCRIPTION(isSpeech=V, isMusic=M, isAmbiance=A, isEditable=E, isBinaural=B)) + return ad + +def packAudioDescription(bitstrm: BitStream, data: any): + assert type(data) == list, "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + for desc in cast(list, data): + assert type(desc) == AUDIO_DESCRIPTION, "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + ad = cast(AUDIO_DESCRIPTION, desc) + bitstrm.append(f'bool={ad.isSpeech}') + bitstrm.append(f'bool={ad.isMusic}') + bitstrm.append(f'bool={ad.isAmbiance}') + bitstrm.append(f'bool={ad.isEditable}') + bitstrm.append(f'bool={ad.isBinaural}') + bitstrm.append(f'uint:3=0') + +def unpackDAS(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: + assert piSize == 2, "Incorrect PI Data Size for DYNAMIC_AUDIO_SUPPRESSION" + V = bitstrm.read(1).bool + M = bitstrm.read(1).bool + A = bitstrm.read(1).bool + _ = bitstrm.read(5) + SLI = bitstrm.read(4).uint + _ = bitstrm.read(4) + return DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=V, preferMusic=M, preferAmbiance=A, level=SLI) + +def packDAS(bitstrm: BitStream, data: any): + assert type(data) == DYNAMIC_AUDIO_SUPPRESSION, "Dynamic Audio Suppression PI Data expects a data of type DYNAMIC_AUDIO_SUPPRESSION" + das = cast(DYNAMIC_AUDIO_SUPPRESSION, data) + bitstrm.append(f'bool={das.preferSpeech}') + bitstrm.append(f'bool={das.preferMusic}') + bitstrm.append(f'bool={das.preferAmbiance}') + bitstrm.append(f'uint:5=0') + bitstrm.append(f'uint:4={das.level}') + bitstrm.append(f'uint:4=0') + +def unpackDiegetic(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: + assert piSize == 1, "Incorrect PI Data Size for DIEGETIC_TYPE" + digType = list() + for _ in range(5): # no way to know how many bits are valid bits, so all 5 read + digType.append(bitstrm.read(1).bool) + bitstrm.bytealign() + return DIEGETIC_TYPE(isDigetic=digType) + +def packDiegetic(bitstrm: BitStream, data: any): + assert type(data) == DIEGETIC_TYPE, "Diegetic type PI Data expects a data of type DIEGETIC_TYPE" + diegetic = cast(DIEGETIC_TYPE, data) + assert len(diegetic.isDigetic) <= 5, "Maximum 1 bit per object + 1 bit for SBA/MASA is required (max 5)" + for isDigetic in diegetic.isDigetic: + bitstrm.append(f'bool={isDigetic}') + nPad = 8 - (bitstrm.pos % 8) + if nPad > 0: + bitstrm.append(f'uint:{nPad}=0') + +def unpackAcousticEnv(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: + assert piSize == 1 or piSize == 5 or piSize == 8, "Incorrect PI Data Size for ACOUSTIC_ENVIRONMENT" + rt60 = list() + dsr = list() + dim = list() + absCoeff = list() + + if piSize == 1: + bitstrm.read(1) + + aeid = bitstrm.read(7).uint + + if piSize >= 5: + for _ in range(3): + rt60.append(rt60Value[bitstrm.read(5).uint]) + dsr.append(dsrValue[bitstrm.read(6).uint]) + if piSize == 8: + for _ in range(3): + dim.append(roomDimensionValue[bitstrm.read(4).uint]) + for _ in range(6): + absCoeff.append(absorptionCoeffValues[bitstrm.read(2).uint]) + + return ACOUSTIC_ENVIRONMENT(aeid=aeid, rt60=tuple(rt60), dsr=tuple(dsr), dim=tuple(dim), abscoeff=tuple(absCoeff)) + +def packAcousticEnv(bitstrm: BitStream, data: any): + assert type(data) == ACOUSTIC_ENVIRONMENT, "Diegetic type PI Data expects a data of type ACOUSTIC_ENVIRONMENT" + aenv = cast(ACOUSTIC_ENVIRONMENT, data) + if not aenv.rt60 and not aenv.dsr: + bitstrm.append(f'uint:8={aenv.aeid % 128}') + else: + assert len(aenv.rt60) == 3 and len(aenv.dsr) == 3, "Lo, Mi, Hi only required for RT60 and DSR values" + bitstrm.append(f'uint:7={aenv.aeid % 128}') + for n in range(3): + rt60 = mapNearestIndex(rt60Value, aenv.rt60[n]) + dsr = mapNearestIndex(dsrValue, aenv.dsr[n]) + bitstrm.append(f'uint:5={rt60}') + bitstrm.append(f'uint:6={dsr}') + if aenv.abscoeff and aenv.dim: + assert len(aenv.abscoeff) == 6 and len(aenv.dim) == 3 + for n in range(3): + dim = mapNearestIndex(roomDimensionValue, aenv.dim[n]) + bitstrm.append(f'uint:4={dim}') + for n in range(6): + absCoeff = mapNearestIndex(absorptionCoeffValues, aenv.abscoeff[n]) + bitstrm.append(f'uint:2={absCoeff}') + PIDataUnpacker = [ unpackOrientation, # SCENE_ORIENTATION, unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED - unpackUnsupported, # ACOUSTIC_ENVIRONMENT - unpackUnsupported, # AUDIO_DESCRIPTION + unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT + unpackAudioDescription, # AUDIO_DESCRIPTION unpackUnsupported, # ISM_NUM unpackUnsupported, # ISM_ID unpackUnsupported, # ISM_GAIN - unpackUnsupported, # ISM_ORIENTATION - unpackUnsupported, # ISM_POSITION + unpackOrientations,# ISM_ORIENTATION + unpackPositions, # ISM_POSITION unpackUnsupported, # ISM_DISTANCE_ATTENUATION unpackUnsupported, # ISM_DIRECTIVITY - unpackUnsupported, # DIEGETIC_TYPE + unpackDiegetic, # DIEGETIC_TYPE unpackUnsupported, # RESERVED13 unpackUnsupported, # RESERVED14 unpackUnsupported, # RESERVED15 - unpackUnsupported, # PLAYBACK_DEVICE_ORIENTATION - unpackUnsupported, # HEAD_ORIENTATION - unpackUnsupported, # LISTENER_POSITION - unpackUnsupported, # DYNAMIC_AUDIO_SUPPRESSION - unpackUnsupported, # AUDIO_FOCUS_DIRECTION + unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION + unpackOrientation, # HEAD_ORIENTATION + unpackPosition, # LISTENER_POSITION + unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION + unpackOrientation, # AUDIO_FOCUS_DIRECTION unpackUnsupported, # PI_LATENCY unpackUnsupported, # R_ISM_ID unpackUnsupported, # R_ISM_GAIN - unpackUnsupported, # R_ISM_ORIENTATION - unpackUnsupported, # R_ISM_POSITION + unpackOrientation, # R_ISM_ORIENTATION + unpackPosition, # R_ISM_POSITION unpackUnsupported, # R_ISM_DIRECTION unpackUnsupported, # RESERVED27 unpackUnsupported, # RESERVED28 @@ -236,176 +609,498 @@ PIDataUnpacker = [ unpackNoPiData # NO_DATA ] +PIDataPacker = [ + packOrientation, # SCENE_ORIENTATION, + packOrientation, # DEVICE_ORIENTATION_COMPENSATED, + packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + packAcousticEnv, # ACOUSTIC_ENVIRONMENT + packAudioDescription, # AUDIO_DESCRIPTION + packUnsupported, # ISM_NUM + packUnsupported, # ISM_ID + packUnsupported, # ISM_GAIN + packOrientations,# ISM_ORIENTATION + packPositions, # ISM_POSITION + packUnsupported, # ISM_DISTANCE_ATTENUATION + packUnsupported, # ISM_DIRECTIVITY + packDiegetic, # DIEGETIC_TYPE + packUnsupported, # RESERVED13 + packUnsupported, # RESERVED14 + packUnsupported, # RESERVED15 + packOrientation, # PLAYBACK_DEVICE_ORIENTATION + packOrientation, # HEAD_ORIENTATION + packPosition, # LISTENER_POSITION + packDAS, # DYNAMIC_AUDIO_SUPPRESSION + packOrientation, # AUDIO_FOCUS_DIRECTION + packUnsupported, # PI_LATENCY + packUnsupported, # R_ISM_ID + packUnsupported, # R_ISM_GAIN + packOrientation, # R_ISM_ORIENTATION + packPosition, # R_ISM_POSITION + packUnsupported, # R_ISM_DIRECTION + packUnsupported, # RESERVED27 + packUnsupported, # RESERVED28 + packUnsupported, # RESERVED29 + packUnsupported, # RESERVED30 + packNoPiData # NO_DATA +] + +def ReadG192Bitstream(g192File: Path) -> list[bytes]: + refPackets = list[bytes]() + with open(g192File, "rb") as fd: + refBitStrm = ConstBitStream(fd.read()) + while refBitStrm.pos < refBitStrm.len: + sync = hex(refBitStrm.read(16).intle) + nBits = refBitStrm.read(16).intle + assert sync == "0x6b21", "G192 syncword not found at start of packet" + writer = BitStream() + for _ in range(nBits): + bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" + writer.append(bit) + refPackets.append(writer.tobytes()) + return refPackets + +def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: + piIndicated = False + requests = dict() + try: + if bitstrm.read(1).bool: + T = bitstrm.read(3).uint + BR = bitstrm.read(4).uint + if T in [5, 6]:#CA MODES + if BR < 8: + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = 13200 + requests[REQUESTS.CA] = BR + requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception("Unsupported BR bits in CA Mode") + elif T == 7: #IVAS + if BR < 14 : + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + requests[REQUESTS.CA] = -1 + elif BR == 14 : + raise Exception("Reserved BR idx in IVAS EByte") + else: + if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + requests[REQUESTS.CA] = -1 + requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception("Reserved BR idx in {} EByte".format(cmrLookup[T].codec)) + #Try to get all subsequent E-bytes + while bitstrm.read(1).bool: + ET = bitstrm.read(3).uint + if ET == 0 : + supportedBW = [BANDWIDTH.WB, BANDWIDTH.SWB, BANDWIDTH.FB, BANDWIDTH.NREQ] + reserved = bitstrm.read(2) + BW = bitstrm.read(2).uint + requests[REQUESTS.BW] = supportedBW[BW] + elif ET == 1 : + S = bitstrm.read(1).bool + FMT = bitstrm.read(3).uint + if not S: + requests[REQUESTS.FMT] = codedFormats[FMT] + requests[REQUESTS.SUBFMT] = NO_REQ + else: + reserved = bitstrm.read(2) + subFMT = bitstrm.read(6).uint + requests[REQUESTS.FMT] = NO_REQ + requests[REQUESTS.SUBFMT] = codedSubFormats[subFMT] + elif ET == 2: + reserved = bitstrm.read(4) + piIndicated = True + elif ET == 3: + D = bitstrm.read(1).bool + Y = bitstrm.read(1).bool + P = bitstrm.read(1).bool + R = bitstrm.read(1).bool + requests[REQUESTS.SRCFG] = SRCONFIG(diegetic=D, yaw=Y, pitch=P, roll=R) + else: + reserved = bitstrm.read(4) + raise Exception("Unsupported subsequent EByte with ET={}".format(ET)) + except ReadError as error: + print ("Underflow in E-Bytes parsing during unpacking, error = {}".format(error)) + return piIndicated, requests + +def packEBytes(bitstrm: BitStream, requests: dict[str: any], piIndication: bool = False): + codec = requests[REQUESTS.CODEC] if REQUESTS.CODEC in requests.keys() else NO_REQ + bitrate = requests[REQUESTS.BR] if REQUESTS.BR in requests.keys() else 0 + bandwidth = requests[REQUESTS.BW] if REQUESTS.BW in requests.keys() else NO_REQ + camode = requests[REQUESTS.CA] if REQUESTS.CA in requests.keys() else NO_REQ + format = requests[REQUESTS.FMT] if REQUESTS.FMT in requests.keys() else NO_REQ + subFormat = requests[REQUESTS.SUBFMT] if REQUESTS.SUBFMT in requests.keys() else NO_REQ + srcfg = SRCONFIG(requests[REQUESTS.SRCFG]) if REQUESTS.SRCFG in requests.keys() else None + + # Check if any request needs to be sent + isInitialEByteNeeded = piIndication or bitrate != 0 or bandwidth != NO_REQ or camode != NO_REQ or \ + format != NO_REQ or subFormat != NO_REQ or srcfg != None + + if not isInitialEByteNeeded: + return + + if camode != NO_REQ: + T = 6 if bandwidth == BANDWIDTH.SWB else 5 + BR = getListIndex(list(CAMODE), camode) + assert BR > 0, "Channel Aware Mode not supported" + elif bitrate == 0: + T = 7 + BR = 15 + else: + mapBandwidthToTBit = {BANDWIDTH.NB: 0, BANDWIDTH.WB: 2, BANDWIDTH.SWB: 3, BANDWIDTH.FB: 4, NO_REQ: 2}[bandwidth] + T = {CODECS.AMRWB: 1, CODECS.IVAS: 7, CODECS.EVS: mapBandwidthToTBit} [codec] + BR = getListIndex(requestBitratesForCodec[codec], bitrate) + assert BR >= cmrLookup [T].startIdx and BR < cmrLookup [T].endIdx, "EVS Bitrate Index and Bandwidth Combination cannot be requested" + + # Write the Initial E-Byte + bitstrm.append(f'bool={True}') #E-Byte H=1 + bitstrm.append(f'uint:3={T}') + bitstrm.append(f'uint:4={BR}') + + # Subsequent E-bytes follow + if piIndication: + bitstrm.append('hex:8=A0') + + if codec != CODECS.IVAS: + return + + # Bandwidth E-Byte + if bandwidth != NO_REQ: + bw = {BANDWIDTH.WB: 0, BANDWIDTH.SWB: 1, BANDWIDTH.FB: 2}[bandwidth] + bitstrm.append('hex:4=8') + bitstrm.append(f'uint:4={bw}') + + # Coded Format/SubFormat Request E-Byte + if subFormat in SUBFORMATS: + subFmt = getListIndex(codedSubFormats, subFormat) + bitstrm.append('hex:8=9F') #S=0, FMT=111 + bitstrm.append(f'uint:8={subFmt}') + elif format in FORMATS: + fmt = getListIndex(codedFormats, format) + bitstrm.append('hex:4=9') + bitstrm.append(f'bool={False}') # S=0 + bitstrm.append(f'uint:3={fmt}') + + # SR Config E-Byte + with srcfg: + bitstrm.append('hex:4=B') + bitstrm.append(f'bool={srcfg.diegetic}') + bitstrm.append(f'bool={srcfg.yaw}') + bitstrm.append(f'bool={srcfg.pitch}') + bitstrm.append(f'bool={srcfg.roll}') + +def unpackAUFrames(bitstrm: ConstBitStream, frameList: list[FRAME]): + try: + #Unpack Frame AUs here + for (idx, frm) in enumerate(frameList): + auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode + frm.au = bitstrm.read(auSize * 8).tobytes() + except ReadError as error: + print ("Underflow in AU Frames parsing during unpacking, error = {}".format(error)) + +def packAUFrames(bitstrm: BitStream, frameList: list[FRAME]): + for frm in frameList: + bitstrm.append(frm.au) + bitstrm.bytealign() + +def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> list[FRAME]: + F = True + frmList = list() + + try: + while F: + F = bitstrm.read(1).bool + FT = bitstrm.read(2).uint + BR = bitstrm.read(4).uint + frm = FRAME(timestamp=rtpTimestamp) + if FT == 1 : + frm.codec = CODECS.IVAS + if BR == 14 : + supportedBitrates = [-1, 256000, 384000, 512000] + reserved = bitstrm.read(1) + D = bitstrm.read(1).bool + C = bitstrm.read(1).bool + SR_BR = bitstrm.read(2).uint + reserved = bitstrm.read(3) + if SR_BR == 0: + raise Exception("Reserved bitrate in SR Config ToC Indicated") + frm.srInfo = SRINFO(bitrate=supportedBitrates[SR_BR], diegetic=D, + transportCodec= SRCODEC.LC3PLUS if C else SRCODEC.LCLD) + else: + frm.bitrate = ivasBitrates[BR] + elif FT == 0: + # Codec switch only if not NO_DATA_FRAME, as IVAS/EVS signal using this + frm.codec = CODECS.EVS if BR != 15 else Codec + if BR == 13: + raise Exception("Reserved bitrate in EVS ToC Indicated") + frm.speechLost = (BR == 14) + frm.bitrate = evsBitrates[BR] if BR < 13 else 0 + else: + frm.codec = CODECS.AMRWB + if BR >= 10 and BR <= 13: + raise Exception("Reserved bitrate in AMRWB-IO ToC Indicated") + frm.speechLost = (BR == 14) or (FT == 2) + frm.bitrate = amrwbBitrates[BR] if BR < 10 else 0 + frm.frmSizeBits = frm.bitrate // 50 + rtpTimestamp += 320 + frmList.append(frm) + if F: + #skip all frame specific E-bytes before next header + while (bitstrm.read(1).bool): + print("Skipping unsupported frame specific subsequent Ebytes") + reserved = bitstrm.read(7) + except ReadError as error: + print ("Underflow in ToC parsing during unpacking, error = {}".format(error)) + + return frmList + +def packToCBytes(bitstrm: BitStream, frameList: list[FRAME]): + numFrames = len(frameList) + + for idx, frame in enumerate(frameList): + F = (idx != (numFrames - 1)) + FT = 0 + BR = 0 + if frame.frmSizeBits == 0: + FT = 3 if frame.codec == CODECS.AMRWB else 0 # Only AMRWB or EVS support 0 frame case + BR = 14 if frame.speechLost else 15 #SPEECH_LOST or NO_DATA + elif frame.codec == CODECS.AMRWB: + FT = 3 + BR = getListIndex(amrwbBitrates[0:10], frame.bitrate) + elif frame.codec == CODECS.IVAS: + FT = 1 + BR = 14 if frame.srInfo else getListIndex(ivasBitrates, frame.bitrate) + else: + FT = 0 + BR = getListIndex(evsBitrates[0:13], frame.bitrate) + + assert BR >= 0, "Index for table not found" + + bitstrm.append(f'bool={False}') #ToC 0 bit + bitstrm.append(f'bool={F}') #Frame follows bit + bitstrm.append(f'uint:2={FT}') #Frame Type + bitstrm.append(f'uint:4={BR}') #Frame Type + if frame.srInfo and frame.codec == CODECS.IVAS: + SRBR = (frame.srInfo.bitrate//128000) - 1 + bitstrm.append(f'bool={False}') #ToC 0 bit + bitstrm.append(f'bool={frame.srInfo.diegetic}') + bitstrm.append(f'bool={frame.srInfo.transportCodec == SRCODEC.LC3PLUS}') + bitstrm.append(f'uint:2={SRBR}') + bitstrm.append('uint:3=0') + +def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: + piDataList = list[PIDATA]() + try: + # PI Data if Indicated + PF = True + piTimeStamps = rtpTimestamp + while PF : + PF = bitstrm.read(1).bool + PM = bitstrm.read(2).uint + PiType = bitstrm.read(5).uint + PiSize = 0 + byte = 255 + while byte == 255: + byte = bitstrm.read(8).uint + PiSize += byte + + PiFrameData = PIDataUnpacker[PiType](bitstrm, PiSize) + + if PiTypeNames[PiType] != PIDATAS.NO_PI_DATA: + piDataList.append( + PIDATA(timestamp=piTimeStamps if PM != 3 else rtpTimestamp, # Generic Pi has base timestamp + type=PiTypeNames[PiType], + data=PiFrameData)) + piTimeStamps += 320 if PM == 2 else 0 + except ReadError as error: + print ("Underflow before completion of unpacking, error = {}".format(error)) + return piDataList + +def packPiData(bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataList: list[PIDATA]): + # sort the piDataList by timestamp and eliminate data where timestamp is OOB for this packet + piDataList = [data for data in piDataList if (data.timestamp >= rtpTimestampBounds[0] and data.timestamp < rtpTimestampBounds[1])] + sorted(piDataList, key=lambda data: data.timestamp) + numPiData = len(piDataList) + + # Group PI data by timestamps + piDataDict = dict[int, list[PIDATA]]() + for data in piDataList: + ts = (data.timestamp // 320) * 320 + if ts not in piDataDict.keys(): + piDataDict[ts] = list() + piDataDict[ts].append(data) + + curTimestamp = rtpTimestampBounds[0] + for ts, dataList in piDataDict.items(): + while curTimestamp < ts: + # Inset NO_PI_DATA till current Timestamp is reached + bitstrm.append('hex:16=DF00') #PI Frame follows, Last PI header for this frame, NO_PI_DATA, size=0 + curTimestamp += 320 + + for idx, data in enumerate(dataList): + pack = BitStream() + PM = 2 if idx == len(dataList) - 1 else 1 + PF = 0 if numPiData == 1 else 1 + TYPE = PiTypeNames.index(data.type) + PIDataPacker[TYPE](pack, data.data) + assert (pack.pos % 8) == 0, "PI data must be byte aligned" + SIZE = pack.pos // 8 + assert SIZE < MAX_PACKED_PI_SIZE, f"Packed PI Size should be less than MAX_PACKED_PI_SIZE ({MAX_PACKED_PI_SIZE})" + + bitstrm.append(f'uint:1={PF}') + bitstrm.append(f'uint:2={PM}') + bitstrm.append(f'uint:5={TYPE}') + bitstrm.append(f'uint:8={SIZE}') + bitstrm.append(pack) + numPiData -= 1 + curTimestamp += 320 + assert numPiData == 0, "Not all PI data was packed due to internal error" + + @dataclass class IvasPayload: - lastCodec: CODECS = CODECS.IVAS # Track last frame's codec frameList: list[FRAME] = field(default_factory=list) piDataList: list[PIDATA] = field(default_factory=list) requests: dict [str, any] = field(default_factory=dict) - def __init__(self, bitstrm: ConstBitStream, rtpTimestamp, lastCodec: CODECS): - self.lastCodec = lastCodec - self.frameList = list[FRAME]() - self.piDataList = list[PIDATA]() - self.requests = dict() + def pack(self, bitstrm: BitStream) -> int: + piIndication = len(self.piDataList) > 0 + numFrames = len(self.frameList) + packEBytes(bitstrm, self.requests, piIndication) + packToCBytes(bitstrm, self.frameList) + packAUFrames(bitstrm, self.frameList) + if piIndication: + packPiData(bitstrm, (self.frameList[0].timestamp, self.frameList[-1].timestamp + 320), self.piDataList) + return numFrames - try: - piIndicated = False - if bitstrm.read(1).bool: - T = bitstrm.read(3).uint - BR = bitstrm.read(4).uint - - if T in [5, 6]:#CA MODES - if BR < 8: - self.requests[REQUESTS.CODEC] = cmrLookup[T].codec - self.requests[REQUESTS.BR] = 13200 - self.requests[REQUESTS.CA] = BR - self.requests[REQUESTS.BW] = cmrLookup[T].bandwidth - else: - raise Exception("Unsupported BR bits in CA Mode") - elif T == 7: #IVAS - if BR < 14 : - self.requests[REQUESTS.CODEC] = cmrLookup[T].codec - self.requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] - self.requests[REQUESTS.CA] = -1 - elif BR == 14 : - raise Exception("Reserved BR idx in IVAS EByte") - else: - if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: - self.requests[REQUESTS.CODEC] = cmrLookup[T].codec - self.requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] - self.requests[REQUESTS.CA] = -1 - self.requests[REQUESTS.BW] = cmrLookup[T].bandwidth - else: - raise Exception("Reserved BR idx in {} EByte".format(cmrLookup[T].codec)) + @classmethod + def unpack(cls, bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS): + # Unpack the E-bytes + piIndicated, requests = unpackEBytes(bitstrm) - #Try to get all subsequent E-bytes - while bitstrm.read(1).bool: - ET = bitstrm.read(3).uint - if ET == 0 : - supportedBW = [BANDWIDTH.WB, BANDWIDTH.SWB, BANDWIDTH.FB, BANDWIDTH.NREQ] - reserved = bitstrm.read(2) - BW = bitstrm.read(2).uint - self.requests[REQUESTS.BW] = supportedBW[BW] - elif ET == 1 : - S = bitstrm.read(1).bool - FMT = bitstrm.read(3).uint - if not S: - self.requests[REQUESTS.FMT] = codedFormats[FMT] - self.requests[REQUESTS.SUBFMT] = "NO_REQ" - else: - reserved = bitstrm.read(2) - subFMT = bitstrm.read(6).uint - self.requests[REQUESTS.FMT] = "NO_REQ" - self.requests[REQUESTS.SUBFMT] = codedSubFormats[subFMT] - elif ET == 2: - reserved = bitstrm.read(4) - piIndicated = True - elif ET == 3: - D = bitstrm.read(1).bool - Y = bitstrm.read(1).bool - P = bitstrm.read(1).bool - R = bitstrm.read(1).bool - self.requests[REQUESTS.SRCFG] = SRCONFIG(enabled=True, diegetic=D, yaw=Y, pitch=P, roll=R) - else: - reserved = bitstrm.read(4) - raise Exception("Unsupported subsequent EByte with ET={}".format(ET)) - - # ToC Byte parsing starts with 'F' bit as H bit is already read above - frameTimeStamps = rtpTimestamp - F = True - frmList = list() - while F: - F = bitstrm.read(1).bool - FT = bitstrm.read(2).uint - BR = bitstrm.read(4).uint - - frm = FRAME(timestamp=frameTimeStamps) - if FT == 1 : - frm.codec = CODECS.IVAS - if BR == 14 : - supportedBitrates = [-1, 256000, 384000, 512000] - reserved = bitstrm.read(1) - D = bitstrm.read(1).bool - C = bitstrm.read(1).bool - SR_BR = bitstrm.read(2).uint - reserved = bitstrm.read(3) - if SR_BR == 0: - raise Exception("Reserved bitrate in SR Config ToC Indicated") - frm.srInfo = SRINFO(bitrate=supportedBitrates[SR_BR], diegetic=D, - transportCodec= SRCODEC.LC3PLUS if C else SRCODEC.LCLD) - else: - frm.bitrate = ivasBitrates[BR] - elif FT == 0: - # Codec switch only if not NO_DATA_FRAME, as IVAS/EVS signal using this - frm.codec = CODECS.EVS if BR != 15 else self.lastCodec - if BR == 13: - raise Exception("Reserved bitrate in EVS ToC Indicated") - frm.speechLost = (BR == 14) - frm.bitrate = evsBitrates[BR] if BR < 13 else 0 - else: - frm.codec = CODECS.AMRWB - if BR >= 10 and BR <= 13: - raise Exception("Reserved bitrate in AMRWB-IO ToC Indicated") - frm.speechLost = (BR == 14) or (FT == 2) - frm.bitrate = amrwbBitrates[BR] if BR < 10 else 0 - frm.frmSizeBits = frm.bitrate // 50 - frameTimeStamps += 320 - frmList.append(frm) - - if F: - #skip all frame specific E-bytes before next header - while (bitstrm.read(1).bool): - print("Skipping unsupported frame specific subsequent Ebytes") - reserved = bitstrm.read(7) - - #Frame AUs here - for (idx, frm) in enumerate(frmList): - auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode - frm.au = bitstrm.read(auSize * 8).tobytes() - self.frameList.append(frm) - - # PI Data if Indicated - piTimeStamps = rtpTimestamp - PF = piIndicated - while PF : - PF = bitstrm.read(1).bool - PM = bitstrm.read(2).uint - PiType = bitstrm.read(5).uint - PiSize = 0 - byte = 255 - while byte == 255: - byte = bitstrm.read(8).uint - PiSize += byte - - PiFrameData = PIDataUnpacker[PiType](bitstrm, PiSize) - - self.piDataList.append( - PIDATA(timestamp=piTimeStamps if PM != 3 else rtpTimestamp, # Generic Pi has base timestamp - type=PiTypeNames[PiType], - data=PiFrameData)) - piTimeStamps += 320 if PM == 2 else 0 - except ReadError as error: - print ("Underflow before completion of unpacking, error = {}".format(error)) + # ToC Byte parsing starts with 'F' bit as H bit is already read for E-byte parsing + frameList = unpackToCBytes(bitstrm, rtpTimestamp, Codec) + + # Extract packed AU + unpackAUFrames(bitstrm, frameList) + + if piIndicated: + piDataList = unpackPiData(bitstrm, rtpTimestamp) + else: + piDataList = list() + + return cls(frameList=frameList, piDataList=piDataList, requests=requests) @dataclass class IvasPacket: hdr: RTPHDR = field(default_factory=RTPHDR) payload: IvasPayload = field(default_factory=IvasPayload) + def pack(self, bitstrm: BitStream) : + self.hdr.pack(bitstrm) + numFrames = self.payload.pack(bitstrm) + self.hdr.updateHeader(numFrames) + class IvasRtp: - def __init__(self): + def __init__(self, numFramesPerPacket = 4, codec: CODECS = CODECS.IVAS): + self.numFramesPerPacket = numFramesPerPacket self.packets = list[IvasPacket]() - self.lastCodec: CODECS = CODECS.IVAS # Track last frame's codec + self.Codec: CODECS = codec# Track last frame's codec + self.requests = dict() + self.piData = dict() def dumpToJSON(self, jsonFileName): with open(jsonFileName, "w") as fd: packets = list() for packet in self.packets: - packets.append(asdict(packet)) + packetDict = asdict(packet) + for frame in packetDict['payload']['frameList']: + frame['au'] = base64.b64encode(frame['au']).decode('utf-8') + packets.append(packetDict) json_output = json.dumps(packets, indent=4) fd.write(json_output) + def requestReader(self, timestamp: int) -> dict[str, any]: + tsList = sorted(self.requests.keys()) + if len(tsList) > 0: + lastTs = int(tsList[0]) + for ts in tsList: + if timestamp >= lastTs and timestamp < int(ts): + return self.requests[str(lastTs)] + lastTs = int(ts) + return dict() + + def piDataReader(self, startTimestamp: int, endTimestamp: int) -> list[PIDATA]: + + piDataList = list() + while startTimestamp < endTimestamp: + ts = str(startTimestamp) + if ts in self.piData.keys(): + for piTypes in self.piData[ts].keys(): + dataDict = self.piData[ts][piTypes] + if type(dataDict) != dict: + data = dataDict + elif piTypes == PIDATAS.ISM_ORIENTATION: + data = list() + for orientation in dataDict: + data.append(ORIENTATION(**orientation)) + elif "ORIENTATION" in piTypes or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION: + data = ORIENTATION(**dataDict) + elif piTypes == PIDATAS.ACOUSTIC_ENVIRONMENT: + data = ACOUSTIC_ENVIRONMENT(**dataDict) + elif piTypes == PIDATAS.AUDIO_DESCRIPTION: + data = list() + for desc in dataDict: + data.append(AUDIO_DESCRIPTION(**desc)) + elif piTypes == PIDATAS.DIEGETIC_TYPE: + data = DIEGETIC_TYPE(**dataDict) + elif piTypes == PIDATAS.LISTENER_POSITION or piTypes == PIDATAS.R_ISM_POSITION: + data = POSITION(**dataDict) + elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: + data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) + else: + assert False, "Unhandled PI Data" + piDataList.append(PIDATA(timestamp=startTimestamp, type=piTypes, data=data)) + startTimestamp += 320 + return piDataList + + def packG192File(self, g192File: Path, rtpDumpOut: Path, piData: dict = None, requestsData: dict = None): + packet = IvasPacket() + packet.hdr.sequenceNum=int("0xFFFF", 16) + packet.hdr.timestamp = 0 + packet.hdr.ssrc = int("0xDEADBEEF", 16) + + self.piData = piData + self.requests = requestsData + + timestamp = packet.hdr.timestamp + piTimestamps = 0 + with open(g192File, "rb") as fin: + with open(rtpDumpOut, "wb") as fout: + refBitStrm = ConstBitStream(fin.read()) + frames = list[FRAME]() + while refBitStrm.pos < refBitStrm.len: + sync = hex(refBitStrm.read(16).intle) + nBits = refBitStrm.read(16).intle + assert sync == "0x6b21", "G192 syncword not found at start of packet" + writer = BitStream() + for _ in range(nBits): + bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" + writer.append(bit) + frames.append(FRAME(codec=self.Codec, frmSizeBits=nBits, bitrate=nBits * 50, speechLost=False, timestamp=timestamp, au=writer.tobytes())) + if (len(frames) == self.numFramesPerPacket) or (refBitStrm.pos == refBitStrm.len): + rtpBitstrm = BitStream() + numFrames = len(frames) + packet.payload = IvasPayload(frameList=frames, piDataList=self.piDataReader(piTimestamps, piTimestamps + (numFrames * 320)), requests=self.requestReader(piTimestamps)) + packet.pack(bitstrm=rtpBitstrm) + fout.write(struct.pack('i', rtpBitstrm.bytepos)) + fout.write(rtpBitstrm.tobytes()) + frames = list() + piTimestamps += numFrames * 320 + timestamp += 320 + + def getPackets(self): return self.packets @@ -423,16 +1118,21 @@ class IvasRtp: def unpackPacket(self, packet) -> IvasPacket : bitStrm = ConstBitStream(packet) - hdr = RTPHDR(bitStrm) - payload = IvasPayload(bitStrm, rtpTimestamp=hdr.timestamp, lastCodec=self.lastCodec) - self.lastCodec = payload.frameList[-1].codec #Last Frame's codec for next frame for NO_DATA case + hdr = RTPHDR.unpack(bitStrm) + payload = IvasPayload.unpack(bitStrm, rtpTimestamp=hdr.timestamp, Codec=self.Codec) + self.Codec = payload.frameList[-1].codec #Last Frame's codec for next frame for NO_DATA case return IvasPacket(hdr=hdr, payload=payload) class ArgsParser: def __init__(self): self.parser = argparse.ArgumentParser() - self.parser.add_argument("-r", "--rtpdump", type=str, required=True, help="RTP Dump to unpack") + self.parser.add_argument("-r", "--rtpdump", type=str, default=None, help="RTP Dump to unpack") self.parser.add_argument("-j", "--json", type=str, default="unpack.json", help="Output unpacked RTP frames to JSON file") + self.parser.add_argument("-g", "--g192", type=str, default=None, help="G192 bitstream input for RTP Packing") + self.parser.add_argument("-f", "--framesPerPacket", type=int, default=1, help="Number of IVAS frames per RTP Packet") + self.parser.add_argument("-o", "--outrtpdump", type=str, default="output.rtpdump", help="Output RTP Dump file") + self.parser.add_argument("-p", "--piDataJson", type=str, default=None, help="piData to be packed") + self.parser.add_argument("-x", "--requestsJson", type=str, default=None, help="Requests to be packed") def parse(self): args = self.parser.parse_args() @@ -440,7 +1140,18 @@ class ArgsParser: if __name__ == "__main__": args = ArgsParser().parse() - rtp = IvasRtp() + rtp = IvasRtp(numFramesPerPacket=args.framesPerPacket) + if args.rtpdump: rtp.unpackFile(args.rtpdump) rtp.dumpToJSON(args.json) + elif args.g192: + piData = dict() + requestsData = dict() + if args.piDataJson: + with open(args.piDataJson) as f: + piData = json.load(f) + if args.requestsJson: + with open(args.requestsJson) as f: + requestsData = json.load(f) + rtp.packG192File(g192File=args.g192, rtpDumpOut=args.outrtpdump, piData=piData, requestsData=requestsData) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 00b4da5c1b..342e29c8b6 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -38,26 +38,29 @@ import pytest import csv import os import sys +import random from tempfile import TemporaryDirectory from pathlib import Path from ivasrtp import * -from bitstring import ConstBitStream, BitStream +import soundfile as sf +import numpy as np -THIS_PATH = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(os.path.join(os.path.dirname(THIS_PATH), "../../scripts")) +ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) +sys.path.append(ROOT_DIR) from tests.conftest import EncoderFrontend, DecoderFrontend @pytest.mark.parametrize("dtx", [False, True]) -@pytest.mark.parametrize("bitrate", [6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850]) +@pytest.mark.parametrize("bitrate", [6600, 12650, 23850]) @pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) def test_rtp_bitstream_amrwb ( test_info, bitrate: int, dtx: bool, framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend ): run_rtp_bitstream_tests ( CODECS.AMRWB, @@ -67,11 +70,12 @@ def test_rtp_bitstream_amrwb ( "MONO", dtx, framesPerPacket, - dut_encoder_frontend + dut_encoder_frontend, + dut_decoder_frontend ) @pytest.mark.parametrize("dtx", [False, True]) -@pytest.mark.parametrize("bitrate", [7200, 8000, 9600, 13200, 16400, 24400, 32000, 48000, 64000, 96000, 128000]) +@pytest.mark.parametrize("bitrate", [9600, 24400, 128000]) @pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) @pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) @pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) @@ -82,7 +86,8 @@ def test_rtp_bitstream_evs ( caMode: str, dtx: bool, framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend ): run_rtp_bitstream_tests ( CODECS.EVS, @@ -92,14 +97,14 @@ def test_rtp_bitstream_evs ( "MONO", dtx, framesPerPacket, - dut_encoder_frontend + dut_encoder_frontend, + dut_decoder_frontend ) -@pytest.mark.parametrize("bitrate", [13200, 24400, 80000, 512000]) +@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) -#@pytest.mark.parametrize("bandwidth, format, bitrate", [("WB", "MASA", 13200)]) @pytest.mark.parametrize("framesPerPacket", [8]) def test_rtp_bitstream_ivas_nodtx ( test_info, @@ -107,7 +112,8 @@ def test_rtp_bitstream_ivas_nodtx ( bandwidth: str, format: str, framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend ): run_rtp_bitstream_tests ( CODECS.IVAS, @@ -117,20 +123,22 @@ def test_rtp_bitstream_ivas_nodtx ( format, False, framesPerPacket, - dut_encoder_frontend + dut_encoder_frontend, + dut_decoder_frontend ) @pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA"]) -@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +@pytest.mark.parametrize("framesPerPacket", [1, 3]) def test_rtp_bitstream_ivas_dtx ( test_info, bitrate: int, bandwidth: str, format: str, framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend ): run_rtp_bitstream_tests ( CODECS.IVAS, @@ -140,9 +148,39 @@ def test_rtp_bitstream_ivas_dtx ( format, True, framesPerPacket, - dut_encoder_frontend + dut_encoder_frontend, + dut_decoder_frontend ) +def generateRequests(startTs: int, endTs: int) -> dict: + requests = dict() + return requests + +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) + somePosition = lambda : POSITION( x=random.randint(-32788, 32767)/100.0, y=random.randint(-32788, 32767)/100.0, z=random.randint(-32788, 32767)/100.0) + someDesc = lambda : AUDIO_DESCRIPTION(isSpeech=bool(random.getrandbits(1)), isMusic=bool(random.getrandbits(1)), isAmbiance=bool(random.getrandbits(1)), isEditable=bool(random.getrandbits(1)), isBinaural=bool(random.getrandbits(1))) + someDAS = lambda : DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=bool(random.getrandbits(1)), preferMusic=bool(random.getrandbits(1)), preferAmbiance=bool(random.getrandbits(1)), level=random.randint(0, 15)) + someDIG = lambda : DIEGETIC_TYPE(isDigetic=[ bool(random.getrandbits(1)) for _ in range(random.randint(1, 5)) ]) + + for ts in range(startTs, endTs, 320): + pidata = dict() + pidata["SCENE_ORIENTATION"] = someOrientation() + pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() + pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() + pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() + pidata["HEAD_ORIENTATION"] = someOrientation() + pidata["AUDIO_FOCUS_DIRECTION"] = someOrientation() + pidata["LISTENER_POSITION"] = somePosition() + pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() + pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] + pidata["DIEGETIC_TYPE"] = someDIG() + pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) + data[str(ts)] = pidata + return data + def isEqualFrame(refFrame: bytes, dutFrame: bytes): assert len(refFrame) == len(dutFrame), "Encoded frame size is different" @@ -155,6 +193,36 @@ def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" +def isEqualPosition(ref: POSITION, dut: POSITION): + assert abs(ref.x - dut.x) < 0.3, "Position PI Data mismatch in x" + assert abs(ref.y - dut.y) < 0.3, "Position PI Data mismatch in y" + assert abs(ref.z - dut.z) < 0.3, "Position PI Data mismatch in z" + +def isEqualAD(ref: AUDIO_DESCRIPTION, dut: AUDIO_DESCRIPTION): + assert ref.isSpeech == dut.isSpeech, "Audio Description PI Data mismatch in isSpeech" + assert ref.isMusic == dut.isMusic, "Audio Description PI Data mismatch in isMusic" + assert ref.isAmbiance == dut.isAmbiance, "Audio Description PI Data mismatch in isAmbiance" + assert ref.isEditable == dut.isEditable, "Audio Description PI Data mismatch in isEditable" + assert ref.isBinaural == dut.isBinaural, "Audio Description PI Data mismatch in isBinaural" + +def isEqualDAS(ref: DYNAMIC_AUDIO_SUPPRESSION, dut: DYNAMIC_AUDIO_SUPPRESSION): + assert ref.preferSpeech == dut.preferSpeech, "Dynamic Audio Suppression PI Data mismatch in preferSpeech" + assert ref.preferMusic == dut.preferMusic, "Dynamic Audio Suppression PI Data mismatch in preferMusic" + assert ref.preferAmbiance == dut.preferAmbiance, "Dynamic Audio Suppression PI Data mismatch in preferAmbiance" + assert ref.level == dut.level, "Dynamic Audio Suppression PI Data mismatch in level" + +def isEqualDiegetic(ref: DIEGETIC_TYPE, dut: DIEGETIC_TYPE): + for r, d in zip(ref.isDigetic, dut.isDigetic): + assert r == d, f"Diegetic PI Data mismatch {r} != {d}" + +def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): + assert ref.aeid == dut.aeid, "Acoustic Env PI Data mismatch in Acoustic Identifier" + assert len(ref.rt60) == len(dut.rt60), "Acoustic Env PI Data mismatch in len(rt60)" + assert len(ref.dsr) == len(dut.dsr), "Acoustic Env PI Data mismatch in len(dsr)" + assert len(ref.dim) == len(dut.dim), "Acoustic Env PI Data mismatch in len(dim)" + assert len(ref.abscoeff) == len(dut.abscoeff), "Acoustic Env PI Data mismatch in len(abscoeff)" + for r, d in zip(ref.rt60, dut.rt60): + assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" class CSVREADER: def __init__(self, csvFile: Path): @@ -197,19 +265,7 @@ class RTPVALIDATE: self.readers[PiTypeNames[1]] = CSVREADER(piFiles[1]) def setRefG192Bitstream(self, g192File: Path): - self.refPackets = list[bytes]() - self.refBitStrm = None - with open(g192File, "rb") as fd: - self.refBitStrm = ConstBitStream(fd.read()) - while self.refBitStrm.pos < self.refBitStrm.len: - sync = hex(self.refBitStrm.read(16).intle) - nBits = self.refBitStrm.read(16).intle - assert sync == "0x6b21", "G192 syncword not found at start of packet" - writer = BitStream() - for _ in range(nBits): - bit = "0b1" if self.refBitStrm.read(16).uintle == 129 else "0b0" - writer.append(bit) - self.refPackets.append(writer.tobytes()) + self.refPackets = ReadG192Bitstream(g192File) def packet(self, packet: IvasPacket): self.header(packet.hdr) @@ -255,11 +311,11 @@ class RTPVALIDATE: isEqualOrientation(ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), piData.data) @dataclass class TVARGS: - TVROOT = Path(THIS_PATH).joinpath("../../scripts/testv") + TVROOT = Path(ROOT_DIR).joinpath("scripts/testv") def __init__(self): self.tvDict = dict() - self.sceneFile = Path(THIS_PATH).joinpath("../../scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv").absolute() - self.deviceFile = Path(THIS_PATH).joinpath("../../scripts/trajectories/headrot-1.5s.csv").absolute() + self.sceneFile = Path(ROOT_DIR).joinpath("scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv").absolute() + self.deviceFile = Path(ROOT_DIR).joinpath("scripts/trajectories/headrot-1.5s.csv").absolute() def add(self, fmt:str, inputFile:str, args:list[str] = []): inputFile = str(TVARGS.TVROOT.joinpath(inputFile).absolute()) @@ -275,7 +331,7 @@ class TVARGS: if addPI and fmt != "MONO": args += [ "-scene_orientation", str(self.sceneFile), "-device_orientation", str(self.deviceFile) ] return args - + def piFiles(self) -> tuple[Path]: return (self.sceneFile, self.deviceFile) @@ -287,7 +343,8 @@ def run_rtp_bitstream_tests ( format: str, dtx: bool, framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend ): tvArgs = TVARGS() tvArgs.add("MONO", "stv48n.wav") @@ -316,6 +373,10 @@ def run_rtp_bitstream_tests ( with TemporaryDirectory() as tmp_dir: g192Out = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192").absolute() rtpdumpOut = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump").absolute() + rtpdumpIn = Path(tmp_dir).joinpath(f"input-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump").absolute() + pcmOut = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav").absolute() + pcmOutG192 = Path(tmp_dir).joinpath(f"output_g192-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav").absolute() + piDataOutJson = Path(tmp_dir).joinpath(f"piData-{codec}-{bitrate}-{caMode}-{format}-{dtx}.json").absolute() # Run WITHOUT rtpdump first to generate reference bitstream dut_encoder_frontend.run( @@ -330,6 +391,68 @@ def run_rtp_bitstream_tests ( ) validate.setRefG192Bitstream(g192File=g192Out) + packer = IvasRtp(numFramesPerPacket=framesPerPacket, codec=codec) + + if codec == CODECS.IVAS: + outMode = "STEREO" if format == "STEREO" else "BINAURAL" + generatedPIData = generatePiData(0, 16000) + else: + outMode = "" + generatedPIData = dict() + + packer.packG192File(g192File=g192Out, rtpDumpOut=rtpdumpIn, piData=generatedPIData, requestsData=generateRequests(0, 1600)) + + dut_decoder_frontend.run( + output_config=outMode, + output_sampling_rate=48, + input_bitstream_path=g192Out, + output_path=pcmOutG192, + add_option_list= [] + ) + + dut_decoder_frontend.run( + output_config=outMode, + output_sampling_rate=48, + input_bitstream_path=rtpdumpIn, + output_path=pcmOut, + add_option_list= ["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)] + ) + + decAudio, fs = sf.read(pcmOut) + g192Audio, Fs = sf.read(pcmOutG192) + decAudio = decAudio[4*960:] + assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= (4 * 960), "Decoded PCM Audio is not same length as input" + minSamples = min(decAudio.shape[0], g192Audio.shape[0]) + rmsdB = 10.0 * np.log10(np.finfo(float).eps + np.sum(np.abs(g192Audio[:minSamples] - decAudio[:minSamples])**2)/minSamples) + + if dtx: + assert rmsdB < -60.0, "Bitdiff in the RTP unpacked and G192 streams for DTX stream" + else: + assert rmsdB < -96.0, "Bitdiff in the RTP unpacked and G192 streams" + + with open(piDataOutJson, "r") as fd: + decodedPiData = json.load(fd) + assert decodedPiData.keys() == generatedPIData.keys(), f"Timestamp of PI data {generatedPIData.keys()} not found in Decoded PI Data {decodedPiData.keys()}" + for ts in generatedPIData.keys(): + for pitype in generatedPIData[ts]: + data = generatedPIData[ts][pitype] + decoded = decodedPiData[ts][pitype] + if type(generatedPIData[ts][pitype]) == ORIENTATION: + isEqualOrientation(ORIENTATION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == POSITION: + isEqualPosition(POSITION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == DYNAMIC_AUDIO_SUPPRESSION: + isEqualDAS(DYNAMIC_AUDIO_SUPPRESSION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == DIEGETIC_TYPE: + isEqualDiegetic(DIEGETIC_TYPE(**decoded), data) + elif type(generatedPIData[ts][pitype]) == ACOUSTIC_ENVIRONMENT: + isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) + elif type(generatedPIData[ts][pitype]) == list: + for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + isEqualAD(AUDIO_DESCRIPTION(**d), r) + else: + assert False, "Unsupported PI data found" + # Generate RTPDUMP addPI = False if format == "MONO" else True if addPI: @@ -349,7 +472,7 @@ def run_rtp_bitstream_tests ( add_option_list=extra_args ) - ivasRtp = IvasRtp() - ivasRtp.unpackFile(rtpdumpOut) - for packet in ivasRtp.getPackets(): + unpacker = IvasRtp() + unpacker.unpackFile(rtpdumpOut) + for packet in unpacker.getPackets(): validate.packet(packet) -- GitLab From d7fd842515cda390c0b770c9421c27f3bea9e1c4 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 14 Aug 2025 11:35:21 +0200 Subject: [PATCH 083/340] [tmp] extend timeout of renderer sanitizer tests --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0f3a23e827..1b9a05f639 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -550,7 +550,7 @@ renderer-smoke-test: - .rules-merge-request-to-main needs: ["build-codec-linux-cmake"] stage: test - timeout: "30 minutes" + timeout: "60 minutes" artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week @@ -570,7 +570,7 @@ renderer-smoke-test: - mv IVAS_dec IVAS_dec_ref - mv IVAS_rend IVAS_rend_ref - mv ISAR_post_rend ISAR_post_rend_ref - - testcase_timeout=180 + - testcase_timeout=360 # test renderer executable with cmake + asan renderer-asan: -- GitLab From caff271eef5cf95f95a13b9bf4f9dec07ec9fd99 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 14 Aug 2025 11:37:36 +0200 Subject: [PATCH 084/340] Revert "[tmp] extend timeout of renderer sanitizer tests" This reverts commit d7fd842515cda390c0b770c9421c27f3bea9e1c4. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b9a05f639..0f3a23e827 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -550,7 +550,7 @@ renderer-smoke-test: - .rules-merge-request-to-main needs: ["build-codec-linux-cmake"] stage: test - timeout: "60 minutes" + timeout: "30 minutes" artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week @@ -570,7 +570,7 @@ renderer-smoke-test: - mv IVAS_dec IVAS_dec_ref - mv IVAS_rend IVAS_rend_ref - mv ISAR_post_rend ISAR_post_rend_ref - - testcase_timeout=360 + - testcase_timeout=180 # test renderer executable with cmake + asan renderer-asan: -- GitLab From fd09a4ce0b1d18f860009b8fb5a40bdde05f4f5d Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 21 Aug 2025 17:04:14 +0200 Subject: [PATCH 085/340] Extend split rendering tests to also run decoder in VoIP mode --- tests/split_rendering/constants.py | 11 ++++++++ tests/split_rendering/test_split_rendering.py | 26 ++++++++++++++++++- tests/split_rendering/utils.py | 22 ++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/split_rendering/constants.py b/tests/split_rendering/constants.py index 0e5f715da2..7d58a03b0c 100644 --- a/tests/split_rendering/constants.py +++ b/tests/split_rendering/constants.py @@ -56,6 +56,14 @@ CUSTOM_LAYOUT_DIR = SCRIPTS_DIR.joinpath("ls_layouts") HR_TRAJECTORY_DIR = SCRIPTS_DIR.joinpath("trajectories") TESTV_DIR = SCRIPTS_DIR.joinpath("testv") +if platform.system() == "Windows": + TOOLS_DIR = SCRIPTS_DIR / "tools" / "Win32" +elif platform.system() == "Linux": + TOOLS_DIR = SCRIPTS_DIR / "tools"/ "Linux" +elif platform.system() == "Darwin": + TOOLS_DIR = SCRIPTS_DIR / "tools" / "Darwin" +else: + assert False, "Unsupported platform" """ Renderer configurations """ RENDERER_CONFIGS_DEFAULT_CODEC = [ @@ -180,6 +188,9 @@ INPUT_DURATION_SEC = 5 """ PLC constants """ PLC_ERROR_PATTERNS = [str(ep.stem) for ep in ERROR_PATTERNS_DIR.glob("*.ep")] +""" Delay profiles for testing VoIP mode """ +DELAY_PROFILES = [None, "dly_error_profile_5"] + """ Encoder commandline template """ SPLIT_PRE_COD_CMD = [ str(TESTS_DIR.parent.parent.joinpath("IVAS_cod")), diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index c284bf19f4..c6a795ca0c 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -37,6 +37,7 @@ from tests.split_rendering.utils import * """ Ambisonics """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_AMBI) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_AMBI) @@ -54,6 +55,7 @@ def test_ambisonics_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -73,6 +75,7 @@ def test_ambisonics_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -114,6 +117,7 @@ def test_ambisonics_external_split( """ Multichannel """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MC) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MC) @@ -131,6 +135,7 @@ def test_multichannel_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -150,6 +155,7 @@ def test_multichannel_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -191,6 +197,7 @@ def test_multichannel_external_split( """ ISM """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_ISM) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_ISM) @@ -208,6 +215,7 @@ def test_ism_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -227,6 +235,7 @@ def test_ism_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -268,6 +277,7 @@ def test_ism_external_split( """ MASA """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MASA) @@ -285,6 +295,7 @@ def test_masa_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -304,6 +315,7 @@ def test_masa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -345,6 +357,7 @@ def test_masa_external_split( """ OMASA """ +# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OMASA) @@ -362,6 +375,7 @@ def test_omasa_full_chain_split( bitrate, render_config, trajectory, + delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -381,6 +395,7 @@ def test_omasa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -425,6 +440,7 @@ def test_omasa_external_split( """ OSBA """ +# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OSBA) @@ -442,6 +458,7 @@ def test_osba_full_chain_split( bitrate, render_config, trajectory, + delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -461,6 +478,7 @@ def test_osba_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -552,6 +570,7 @@ full_chain_split_pcm_params = [ ] +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("in_fmt,bitrate,render_config", full_chain_split_pcm_params) def test_full_chain_split_pcm( record_property, @@ -565,6 +584,7 @@ def test_full_chain_split_pcm( in_fmt, bitrate, render_config, + delay_profile, ): trajectory = SPLIT_REND_HR_TRAJECTORIES_TO_TEST[0] post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") @@ -586,6 +606,7 @@ def test_full_chain_split_pcm( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -671,9 +692,10 @@ def test_framing_combinations_external_split( ) +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_FRAMING) -@pytest.mark.parametrize("in_fmt", ["5_1"]) +@pytest.mark.parametrize("in_fmt", ["5_1", "FOA"]) @pytest.mark.parametrize("pre_rend_fr", SPLIT_RENDERER_PRE_FRAMINGS) @pytest.mark.parametrize("post_rend_fr", SPLIT_RENDERER_POST_FRAMINGS) def test_framing_combinations_full_chain_split( @@ -690,6 +712,7 @@ def test_framing_combinations_full_chain_split( trajectory, post_rend_fr, pre_rend_fr, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -711,4 +734,5 @@ def test_framing_combinations_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index 1a6ff61edc..ffb14eb55a 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -50,6 +50,7 @@ from tests.renderer.utils import ( run_isar_post_rend_cmd, run_ivas_isar_dec_cmd, run_ivas_isar_enc_cmd, + run_cmd, ) from tests.split_rendering.constants import * @@ -184,6 +185,7 @@ def run_full_chain_split_rendering( get_ssnr=False, get_odg=False, get_odg_bin=False, + delay_profile: Path | None=None, ) -> str: """ Runs the full split rendering chain consisting of @@ -201,6 +203,9 @@ def run_full_chain_split_rendering( renderer_fmt_for_filename = renderer_fmt.replace("BINAURAL_", "") filename_base = f"{in_fmt}_{bitrate}_{renderer_fmt_for_filename}_full_cfg_{render_config.stem}_fr_pre_{pre_rend_fr}_post_{post_rend_fr}" + if delay_profile: + filename_base += f"_{delay_profile.stem}" + ivas_bitstream_stem = f"{filename_base}.192" # NOTE: the split bitstream files need to have ".bit" extension otherwise the conformance test breaks split_bitstream_stem = f"{filename_base}.splt.bit" @@ -253,6 +258,20 @@ def run_full_chain_split_rendering( run_ivas_isar_enc_cmd(cmd) + if delay_profile: + rtp_bitstream = ivas_bitstream.with_suffix(".rtpg192") + cmd = [ + str(TOOLS_DIR / f"networkSimulator_g192{EXE_SUFFIX}"), + str(delay_profile), + str(ivas_bitstream), + str(rtp_bitstream), + str(tmp_dir / "tmp.netsimtrace"), + "1", + "0" + ] + run_cmd(cmd, test_info=test_info) + ivas_bitstream = rtp_bitstream + # decode to split-rendering bitstream cmd = SPLIT_PRE_DEC_CMD[:] @@ -269,6 +288,9 @@ def run_full_chain_split_rendering( if renderer_fmt == "BINAURAL_SPLIT_PCM": cmd[5:5] = ["-om", str(split_md_file)] + if delay_profile: + cmd[5:5] = ["-voip"] + run_ivas_isar_dec_cmd(cmd) # run split renderer -- GitLab From e3fa8dc55d2ad50fc287aaac0d59287bfc851f85 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 21 Aug 2025 10:15:00 +1000 Subject: [PATCH 086/340] Code review comments #2 * Split rtp file writing * Update copyrights * Added additional comments * Added preprocessor defined for constants * Fix bug in pidata pack * Remove un-needed defines Signed-off-by: Ripinder Singh --- Workspace_msvc/lib_util.vcxproj | 1 + Workspace_msvc/lib_util.vcxproj.filters | 3 + apps/decoder.c | 145 +++++++++++------------- apps/encoder.c | 73 +++++------- lib_util/ivas_bpool.c | 2 +- lib_util/ivas_bpool.h | 2 +- lib_util/ivas_queue.c | 2 +- lib_util/ivas_queue.h | 2 +- lib_util/ivas_rtp_api.h | 22 ++-- lib_util/ivas_rtp_file.c | 138 ++++++++++++++++++++++ lib_util/ivas_rtp_file.h | 65 +++++++++++ lib_util/ivas_rtp_internal.h | 19 ++-- lib_util/ivas_rtp_payload.c | 35 +++--- lib_util/ivas_rtp_payload.h | 2 +- lib_util/ivas_rtp_pi_data.c | 41 ++++--- lib_util/ivas_rtp_pi_data.h | 2 +- lib_util/ivas_rtp_rom.c | 2 +- 17 files changed, 377 insertions(+), 179 deletions(-) create mode 100644 lib_util/ivas_rtp_file.c create mode 100644 lib_util/ivas_rtp_file.h diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 84ff59beae..94f22c71ca 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -115,6 +115,7 @@ + diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index 776a38e3d3..f81b34b6da 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -97,6 +97,9 @@ util_c + + util_c + diff --git a/apps/decoder.c b/apps/decoder.c index 4f2307b3dc..8cd444ec9a 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -41,6 +41,7 @@ #ifdef IVAS_RTPDUMP #include "ivas_rtp_api.h" #include "ivas_rtp_pi_data.h" +#include "ivas_rtp_file.h" #endif #include "jbm_file_writer.h" #include "hrtf_file_reader.h" @@ -172,7 +173,6 @@ typedef struct } IVAS_DEC_HRTF_BINARY_WRAPPER; #ifdef IVAS_RTPDUMP -#define JITTER_DURATION_MS ( 60 ) #ifdef DEBUGGING #define DEBUG_PRINT fprintf #else @@ -190,7 +190,7 @@ typedef struct uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; - FILE *f_rtpstream; + IVAS_RTP_FILE_HANDLE hReader; FILE *f_piDataOut; IVAS_RTP_CODEC codecId; uint32_t nReadPiData; @@ -223,7 +223,7 @@ static const char *PiDataNames[IVAS_PI_MAX_ID] = { }; -static void LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) +static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) { uint32_t timestamp = ~0u; if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) @@ -374,70 +374,71 @@ static void LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPre static void IVAS_RTP_Term( IVAS_RTP *rtp ) { - if ( rtp->f_rtpstream ) + if ( NULL != rtp ) { - fclose( rtp->f_rtpstream ); - rtp->f_rtpstream = NULL; - } - if ( rtp->f_piDataOut ) - { - fprintf( rtp->f_piDataOut, "\n}\n" ); - fclose( rtp->f_piDataOut ); - rtp->f_piDataOut = NULL; + IvasRtpFile_Close( &rtp->hReader ); + if ( rtp->f_piDataOut != NULL ) + { + fprintf( rtp->f_piDataOut, "\n}\n" ); + fclose( rtp->f_piDataOut ); + rtp->f_piDataOut = NULL; + } + IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); } - IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); } -static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, FILE *f_rtpstream, FILE *f_piDataOut ) +static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ) { ivas_error error = IVAS_ERR_OK; memset( rtp, 0, sizeof( IVAS_RTP ) ); rtp->unpackCfg.maxFramesPerPacket = IVAS_MAX_FRAMES_PER_RTP_PACKET; + rtp->rtpPacket.buffer = rtp->packet; + rtp->rtpPacket.capacity = sizeof( rtp->packet ); error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); + if ( error == IVAS_ERR_OK ) + { + error = IvasRtpFile_Open( inputBitstreamFilename, false, &rtp->hReader ); + if ( error != IVAS_ERR_OK ) + { + return error; + } - rtp->f_rtpstream = f_rtpstream; - rtp->f_piDataOut = f_piDataOut; - rtp->rtpPacket.buffer = rtp->packet; - rtp->rtpPacket.capacity = sizeof( rtp->packet ); + if ( piOutputFilename != NULL ) + { + rtp->f_piDataOut = fopen( piOutputFilename, "w" ); + if ( rtp->f_piDataOut == NULL ) + { + fprintf( stderr, "could not open: %s\n", piOutputFilename ); + return IVAS_ERR_FAILED_FILE_OPEN; + } + fprintf( rtp->f_piDataOut, "{\n" ); + } + } return error; } -static ivas_error readNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) +static ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) { ivas_error error = IVAS_ERR_OK; - IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; - size_t packetLen = 0u; + IVAS_DATA_BUFFER packedFrame; IVAS_RTP_CODEC codecId = IVAS_RTP_IVAS; bool isAMRWB_IOmode = false; + packedFrame.length = 0; packedFrame.buffer = au; packedFrame.capacity = ( IVAS_MAX_BITS_PER_FRAME / 8 ); if ( rtp->numFramesInPacket == 0 ) { - size_t nread = fread( &packetLen, sizeof( uint32_t ), 1, rtp->f_rtpstream ); /* Read Packet Length */ - if ( nread == 0 ) + rtp->rtpPacket.length = 0; + if ( ( error = IvasRtpFile_Read( rtp->hReader, rtp->rtpPacket.buffer, &rtp->rtpPacket.length, rtp->rtpPacket.capacity ) ) != IVAS_ERR_OK ) { - return IVAS_ERR_END_OF_FILE; - } - if ( packetLen > sizeof( rtp->packet ) ) - { - fprintf( stderr, "RTP packet > buffer capacity %lu bytes\n", sizeof( rtp->packet ) ); - return IVAS_ERR_INVALID_OUTPUT_BUFFER_SIZE; - } - - nread = fread( rtp->packet, sizeof( uint8_t ), packetLen, rtp->f_rtpstream ); /* Read Packet */ - if ( nread < packetLen ) - { - return IVAS_ERR_END_OF_FILE; + return error; } - rtp->rtpPacket.buffer = rtp->packet; - rtp->rtpPacket.length = packetLen; - if ( ( error = IVAS_RTP_UNPACK_PushPacket( rtp->hUnpack, &rtp->rtpPacket, &rtp->numFramesInPacket, &rtp->numPiDataInPacket, &rtp->remoteRequestBitmap ) ) != IVAS_ERR_OK ) @@ -461,7 +462,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits rtp->nReadPiData++; rtp->numPiDataInPacket--; } - LogPiData( rtp->f_piDataOut, rtp->piData, rtp->nReadPiData ); + IVAS_RTP_LogPiData( rtp->f_piDataOut, rtp->piData, rtp->nReadPiData ); } #ifdef RTP_S4_251135_CR26253_0016_REV1 @@ -505,7 +506,7 @@ static ivas_error readNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits return IVAS_ERR_OK; } -static ivas_error applyPiData( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint32_t rtpTimeStamp ) +static ivas_error IVAS_RTP_ApplyPiData( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint32_t rtpTimeStamp ) { ivas_error error = IVAS_ERR_OK; while ( rtp->nProcPiData < rtp->nReadPiData && @@ -633,7 +634,7 @@ static int restartDecoder( if ( arg->voipMode ) { - if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, JITTER_DURATION_MS, arg->inputFormat ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, 60, arg->inputFormat ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nCould not enable VOIP: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; @@ -975,11 +976,7 @@ int main( if ( arg.voipMode ) { -#ifdef IVAS_RTPDUMP - if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, JITTER_DURATION_MS, arg.inputFormat ) ) != IVAS_ERR_OK ) -#else if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, 60, arg.inputFormat ) ) != IVAS_ERR_OK ) -#endif { fprintf( stderr, "\nCould not enable VOIP: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; @@ -3406,18 +3403,18 @@ static ivas_error printBitstreamInfoVoip( { bool previewFailed = true; ivas_error error = IVAS_ERR_OK; - FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp; #else + FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; + bool isAMRWB_IOmode; + uint16_t frameTypeIndex; #endif uint8_t au[( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3]; int16_t auSizeBits; uint8_t *auPtr = NULL; - bool isAMRWB_IOmode; - uint16_t frameTypeIndex; bool qBit; uint32_t nextPacketRcvTime_ms = 0; uint16_t rtpSequenceNumber; @@ -3430,6 +3427,13 @@ static ivas_error printBitstreamInfoVoip( { case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: +#ifdef IVAS_RTPDUMP + if ( ( error = IVAS_RTP_Init( &ivasRtp, arg.inputBitstreamFilename, NULL ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); + goto cleanup; + } +#else f_rtpstream = fopen( arg.inputBitstreamFilename, "r" ); if ( f_rtpstream == NULL ) @@ -3438,13 +3442,6 @@ static ivas_error printBitstreamInfoVoip( goto cleanup; } -#ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); - goto cleanup; - } -#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream, arg.inputFormat == IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF ); if ( rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) { @@ -3481,7 +3478,11 @@ static ivas_error printBitstreamInfoVoip( /* EVS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; } +#ifdef IVAS_RTPDUMP + if ( error != IVAS_ERR_OK ) +#else if ( error != IVAS_ERR_OK || rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) +#endif { fprintf( stderr, "failed to read first RTP packet\n" ); goto cleanup; @@ -3574,13 +3575,12 @@ static ivas_error decodeVoIP( int16_t delayNumSamples = -1; int32_t delayTimeScale = -1; int16_t i; - FILE *f_rtpstream = NULL; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; IVAS_DEC_HANDLE hIvasDec = *phIvasDec; - FILE *f_piDataOut = NULL; int32_t initialTsOffsetSystemAndRTP = 0; #else + FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; #endif @@ -3634,34 +3634,19 @@ static ivas_error decodeVoIP( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP - f_rtpstream = fopen( arg.inputBitstreamFilename, "rb" ); + if ( ( error = IVAS_RTP_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); + goto cleanup; + } #else f_rtpstream = fopen( arg.inputBitstreamFilename, "r" ); -#endif - if ( f_rtpstream == NULL ) { fprintf( stderr, "could not open: %s\n", arg.inputBitstreamFilename ); goto cleanup; } -#ifdef IVAS_RTPDUMP - if ( arg.piOutputFilename != NULL ) - { - f_piDataOut = fopen( arg.piOutputFilename, "w" ); - if ( f_piDataOut == NULL ) - { - fprintf( stderr, "could not open: %s\n", arg.piOutputFilename ); - goto cleanup; - } - fprintf( f_piDataOut, "{\n" ); - } - if ( ( error = IVAS_RTP_Init( &ivasRtp, f_rtpstream, f_piDataOut ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); - goto cleanup; - } -#else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_open( &rtpdumpDepacker, f_rtpstream, arg.inputFormat == IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF ); if ( rtpdumpDepackerError != EVS_RTPDUMP_DEPACKER_NO_ERROR ) { @@ -3711,7 +3696,7 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); initialTsOffsetSystemAndRTP = rtpTimeStamp - systemTime_ms * 16; /* For time mapping */ #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); @@ -3916,7 +3901,7 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = IVAS_RTP_ReadNextFrame( &ivasRtp, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; @@ -3966,7 +3951,7 @@ static ivas_error decodeVoIP( { /* Rudimentry Time Mapping to map system time to rtp timestamp */ uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; - if ( ( error = applyPiData( &ivasRtp, hIvasDec, piTs ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_ApplyPiData( &ivasRtp, hIvasDec, piTs ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; diff --git a/apps/encoder.c b/apps/encoder.c index 00c7cb519a..9fb3b60013 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -42,6 +42,7 @@ #include "ivas_rtp_api.h" #include "ivas_rtp_pi_data.h" #include "rotation_file_reader.h" +#include "ivas_rtp_file.h" #endif #ifdef DEBUGGING #include "debug.h" @@ -221,14 +222,14 @@ int main( #endif #ifdef IVAS_RTPDUMP - FILE *f_rtpstream = NULL; + IVAS_RTP_FILE_HANDLE hWriter = NULL; IVAS_RTP_PACK_HANDLE hPack = NULL; uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; - uint8_t packet[NOMINAL_BUFFER_SIZE(IVAS_MAX_FRAMES_PER_RTP_PACKET)]; + uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; - uint32_t numFramesInPayload = 0, packetLength = 0; + uint32_t numFramesInPayload = 0; packedFrame.capacity = sizeof( au ); packedFrame.buffer = au; @@ -639,8 +640,8 @@ int main( packCfg.maxFramesPerPacket = arg.numFramesPerPacket; /* Open the output file for RTPDump writing */ - f_rtpstream = fopen( arg.outputBitstreamFilename, "wb" ); - if ( f_rtpstream == NULL ) + error = IvasRtpFile_Open ( arg.outputBitstreamFilename, true, &hWriter ); + if ( error != IVAS_ERR_OK ) { fprintf( stderr, "could not open: %s\n", arg.outputBitstreamFilename ); goto cleanup; @@ -861,40 +862,17 @@ int main( } /* *** Encode one frame *** */ - if ( ( error = IVAS_ENC_EncodeFrameToSerial( hIvasEnc, pcmBuf, pcmBufSize, bitStream, &numBits ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nencodeFrame failed: %s\n\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } - #ifdef IVAS_RTPDUMP if ( hPack ) { - uint32_t n = numBits / 8, x = 0; - uint16_t *bitstrm = bitStream; - - /* Pack Encoded Stream */ - packedFrame.length = 0; - rtpPacket.length = 0; - while ( n ) + if ( ( error = IVAS_ENC_EncodeFrameToCompact( hIvasEnc, pcmBuf, pcmBufSize, packedFrame.buffer, &numBits ) ) != IVAS_ERR_OK ) { - x = ( ( bitstrm[0] << 7 ) | ( bitstrm[1] << 6 ) | ( bitstrm[2] << 5 ) | ( bitstrm[3] << 4 ) | - ( bitstrm[4] << 3 ) | ( bitstrm[5] << 2 ) | ( bitstrm[6] << 1 ) | ( bitstrm[7] << 0 ) ); - packedFrame.buffer[packedFrame.length++] = (uint8_t) x; - bitstrm += 8; - n--; + fprintf( stderr, "\nencodeFrame failed: %s\n\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; } - /* AMWWB-IO has non octet sized bitstream */ - if ( numBits & 0x7 ) - { - x = 0; - for ( n = 0; n < (uint32_t) ( numBits & 0x7 ); n++ ) - { - x |= (uint8_t) ( bitstrm[n] << ( 7 - n ) ); - } - packedFrame.buffer[packedFrame.length++] = (uint8_t) x; - } + packedFrame.length = ( numBits + 7 ) / 8; + rtpPacket.length = 0; /* Push Encoded Stream to */ if ( ( error = IVAS_RTP_PACK_PushFrame( hPack, @@ -961,15 +939,22 @@ int main( goto cleanup; } - /* File Format = Packet Length (uint32_t) + Packet bytes */ - packetLength = (uint32_t) rtpPacket.length; - fwrite( &packetLength, sizeof( packetLength ), 1, f_rtpstream ); - fwrite( rtpPacket.buffer, sizeof( uint8_t ), packetLength, f_rtpstream ); + if ( ( error = IvasRtpFile_Write ( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } } } else { #endif + if ( ( error = IVAS_ENC_EncodeFrameToSerial( hIvasEnc, pcmBuf, pcmBufSize, bitStream, &numBits ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nencodeFrame failed: %s\n\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } + /* write bitstream */ if ( ( error = BS_Writer_WriteFrame_short( hBsWriter, bitStream, numBits, totalBitrate ) ) != IVAS_ERR_OK ) { @@ -1068,10 +1053,11 @@ cleanup: goto cleanup; } - /* File Format = Packet Length (uint32_t) + Packet bytes */ - packetLength = (uint32_t) rtpPacket.length; - fwrite( &packetLength, sizeof( packetLength ), 1, f_rtpstream ); - fwrite( rtpPacket.buffer, sizeof( uint8_t ), packetLength, f_rtpstream ); + if ( ( error = IvasRtpFile_Write ( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } } IVAS_RTP_PACK_Close( &hPack ); @@ -1087,10 +1073,7 @@ cleanup: RotationFileReader_close( &deviceOrientationFileReader ); } - if ( f_rtpstream ) - { - fclose( f_rtpstream ); - } + IvasRtpFile_Close( &hWriter ); #endif IVAS_ENC_Close( &hIvasEnc ); diff --git a/lib_util/ivas_bpool.c b/lib_util/ivas_bpool.c index 23754a0c11..2bf707cea6 100644 --- a/lib_util/ivas_bpool.c +++ b/lib_util/ivas_bpool.c @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_bpool.h b/lib_util/ivas_bpool.h index d929ae7415..ae8f114127 100644 --- a/lib_util/ivas_bpool.h +++ b/lib_util/ivas_bpool.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_queue.c b/lib_util/ivas_queue.c index b72fcee9e4..c5806e0307 100644 --- a/lib_util/ivas_queue.c +++ b/lib_util/ivas_queue.c @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_queue.h b/lib_util/ivas_queue.h index 8a5d4d1017..9f2a9a856c 100644 --- a/lib_util/ivas_queue.h +++ b/lib_util/ivas_queue.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 5e269eaa43..29ed397beb 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other @@ -296,7 +296,7 @@ extern "C" /* Initial configuration for rtp packer * - maxFramesPerPacket is used to define if more than one frame should be packed - * in the same rtp packet. + * in the same rtp packet. If zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET. */ typedef struct { @@ -407,7 +407,11 @@ extern "C" /* Forward declaration of rtp unpack handle types */ typedef struct IVAS_RTP_UNPACK *IVAS_RTP_UNPACK_HANDLE; /* rtp unpacker handle type */ - /* Initial configuration for rtp unpacker */ + /* Initial configuration for rtp unpacker + * - maxFramesPerPacket is used to define maximum supported frames per rtp packet + * to allow for internal memory allocaton, if zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET + */ + typedef struct { uint32_t maxFramesPerPacket; /* maximum no of frame per packet expected during the session */ @@ -435,8 +439,10 @@ extern "C" * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. * * It is important to ensure IVAS_RTP_UNPACK_PushPayload, IVAS_RTP_UNPACK_PullFrame and - * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context else a race condition - * can arise if new packet is pushed while frames/pidata are still being pulled out. + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread + * safe manner else a race condition can arise if new packet is pushed while frames/pidata + * are still being pulled out. The IVAS_RTP_UNPACK_PushPayload will gererate an error if + * new paylod is pushed before all frames/pidata are pulled out. * * Example usage : - * ================== @@ -496,8 +502,10 @@ extern "C" * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. * * It is important to ensure IVAS_RTP_UNPACK_PushPacket, IVAS_RTP_UNPACK_PullFrame and - * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context else a race condition - * can arise if new packet is pushed while frames/pidata are still being pulled out. + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread + * safe manner else a race condition can arise if new packet is pushed while frames/pidata + * are still being pulled out. The IVAS_RTP_UNPACK_PushPacket will gererate an error if + * new packet is pushed before all frames/pidata are pulled out. * * Example usage : - * ================== diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c new file mode 100644 index 0000000000..0112fdfb54 --- /dev/null +++ b/lib_util/ivas_rtp_file.c @@ -0,0 +1,138 @@ +/****************************************************************************************************** + + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ +#include +#include +#include + +#include "ivas_rtp_file.h" +#include "ivas_error_utils.h" + +struct IVAS_RTP_FILE +{ + bool isFileWriter; + FILE *f_rtpstream; +}; + +ivas_error IvasRtpFile_Open( + const char *filePath, /* i : path to CA config file */ + bool isFileWriter, /* i : instance is a file writer else reader */ + IVAS_RTP_FILE_HANDLE *phRtpFile /* o : pointer to an IVAS file reader handle */ +) +{ + const char *mode = isFileWriter ? "wb" : "rb"; + FILE *f_rtpstream = fopen( filePath, mode ); + if ( f_rtpstream == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_FILE_OPEN, "could not open: %s\n", filePath ); + } + + *phRtpFile = calloc( 1, sizeof( struct IVAS_RTP_FILE ) ); + if ( *phRtpFile != NULL ) + { + ( *phRtpFile )->isFileWriter = isFileWriter; + ( *phRtpFile )->f_rtpstream = f_rtpstream; + } + + return IVAS_ERR_OK; +} + +ivas_error IvasRtpFile_Close( + IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ +) +{ + if ( phReader != NULL && *phReader != NULL ) + { + if ( ( *phReader )->f_rtpstream != NULL ) + { + fclose( ( *phReader )->f_rtpstream ); + ( *phReader )->f_rtpstream = NULL; + } + free( *phReader ); + *phReader = NULL; + } + + return IVAS_ERR_OK; +} + +ivas_error IvasRtpFile_Write( + IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ + const uint8_t *packet, + size_t numBytes ) +{ + ivas_error error = IVAS_ERR_OK; + if ( hReader->isFileWriter ) + { + uint32_t length = (uint32_t) numBytes; /* Max packet length is < 32 bits*/ + fwrite( &length, sizeof( uint32_t ), 1, hReader->f_rtpstream ); + fwrite( packet, sizeof( uint8_t ), numBytes, hReader->f_rtpstream ); + } + else + { + error = IVAS_ERR_WRONG_PARAMS; + } + return error; +} + +ivas_error IvasRtpFile_Read( + IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ + uint8_t *packet, + size_t *numBytes, + size_t capacity ) +{ + size_t nread = 0; + uint32_t length = 0; + if ( hReader->isFileWriter ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "File open for writing cannot be read" ); + } + + nread = fread( &length, sizeof( uint32_t ), 1, hReader->f_rtpstream ); /* Read Packet Length */ + if ( nread == 0 ) + { + return IVAS_ERR_END_OF_FILE; + } + + *numBytes = length; + if ( ( *numBytes ) > capacity ) + { + fprintf( stderr, "RTP packet > buffer capacity %lu bytes\n", capacity ); + return IVAS_ERR_INVALID_OUTPUT_BUFFER_SIZE; + } + + nread = fread( packet, sizeof( uint8_t ), ( *numBytes ), hReader->f_rtpstream ); /* Read Packet */ + if ( nread < ( *numBytes ) ) + { + return IVAS_ERR_END_OF_FILE; + } + + return IVAS_ERR_OK; +} diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h new file mode 100644 index 0000000000..47d545208a --- /dev/null +++ b/lib_util/ivas_rtp_file.h @@ -0,0 +1,65 @@ +/****************************************************************************************************** + + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#ifndef IVAS_RTP_FILE_H +#define IVAS_RTP_FILE_H + +#include +#include +#include "ivas_error.h" + +typedef struct IVAS_RTP_FILE *IVAS_RTP_FILE_HANDLE; + +ivas_error IvasRtpFile_Open ( + const char *filePath, /* i : path to CA config file */ + bool isFileWriter, /* i : instance is a file writer else reader */ + IVAS_RTP_FILE_HANDLE *phRtpFile /* o : pointer to an IVAS file reader handle */ +); + +ivas_error IvasRtpFile_Close ( + IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ +); + +ivas_error IvasRtpFile_Write ( + IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ + const uint8_t *packet, /* i : pointer to packed rtp packet */ + size_t numBytes /* i : length of the packet in bytes */ +); + +ivas_error IvasRtpFile_Read ( + IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ + uint8_t *packet, /* o : buffer where packet is to be written */ + size_t *numBytes, /* o : length of the packet in bytes */ + size_t capacity /* i : buffer capacity of the packet buffer */ +); + +#endif /* IVAS_RTP_FILE_H */ diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h index 0acfcd2161..f3569ca031 100644 --- a/lib_util/ivas_rtp_internal.h +++ b/lib_util/ivas_rtp_internal.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other @@ -64,6 +64,9 @@ enum MASK_BITS #define MASK_DIM ( ( 1u << NBITS_DIM ) - 1 ) #define MASK_ABS ( ( 1u << NBITS_ABS ) - 1 ) +#define MAX_PI_POSITION_METERS ( 327.68f ) +#define FLOAT_FROM_Q15( q15Val ) ( (float) ( q15Val ) / 32768.0f ) + extern const float mapDSR[1u << NBITS_DSR]; extern const float mapRT60[1u << NBITS_RT60]; extern const float mapRoomDims[1u << NBITS_DIM]; @@ -74,14 +77,14 @@ enum IVAS_RTP_HEADER_BITS EBYTE_TOC_HEADER_BIT = 0x80, /* Ebyte/ToC indicator H bit, 1 = EByte, 0 = ToC */ - TOC_HEADER_FOLLOWS = 0x40, /* ToC header byte is followed by another header byte */ + TOC_HEADER_FOLLOWS = 0x40, /* ToC header byte is followed by another header byte */ TOC_INDICATE_NO_DATA_AMRWB = 0x3F, /* ToC byte indicates NO_DATA in DTX mode for AMRWB */ - TOC_INDICATE_NO_DATA = 0x0F, /* ToC header byte indicates NO_DATA in DTX mode */ - TOC_INDICATE_IVAS = 0x10, /* ToC header byte indicates IVAS data */ - TOC_INDICATE_EVS = 0x00, /* ToC header byte indicates EVS data */ - TOC_INDICATE_AMRWB = 0x30, /* ToC header byte indicates AMR-WB IO data */ - TOC_INDICATE_ARMWB_Q = 0x20, /* ToC header byte indicates AMR-WB IO lost frame */ - TOC_INDICATE_SR = 0x1E, /* Indicate Split Rendering in ToC bytes */ + TOC_INDICATE_NO_DATA = 0x0F, /* ToC header byte indicates NO_DATA in DTX mode */ + TOC_INDICATE_IVAS = 0x10, /* ToC header byte indicates IVAS data */ + TOC_INDICATE_EVS = 0x00, /* ToC header byte indicates EVS data */ + TOC_INDICATE_AMRWB = 0x30, /* ToC header byte indicates AMR-WB IO data */ + TOC_INDICATE_ARMWB_Q = 0x20, /* ToC header byte indicates AMR-WB IO lost frame */ + TOC_INDICATE_SR = 0x1E, /* Indicate Split Rendering in ToC bytes */ EBYTE_CMR_T_EVS_NB = 0x80, /* Initial E-byte indicating EVS NarrowBand modes */ EBYTE_CMR_T_EVS_IO = 0x90, /* Initial E-byte indicating EVS AMRWB IO modes */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 779c627274..f716f31eee 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other @@ -45,15 +45,9 @@ #include "ivas_error_utils.h" #include "mutex.h" -#define IVAS_MAX_FRAMES_IN_QUEUE ( 32 ) #define IVAS_MAX_BYTES_PER_FRAME ( ( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3 ) #define MAX_TOC_PER_FRAME ( 2u ) /* Main ToC Byte + SR-ToC byte */ -/* Heuristically defined max values for no of frames per packet and no of pi data per packet */ -#define MAX_SUPPORTED_PI_DATA_SIZE ( 32 ) /* Currently no PI data exceeds 32 bytes */ -#define MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET ( 25u ) -#define MAX_SUPPORTED_PI_FRAMES_PER_PACKET ( 150u ) - /* Generic RTP Header + Header Extension data structure */ typedef struct { @@ -489,13 +483,18 @@ ivas_error IVAS_RTP_PACK_Open( { ivas_error error; IVAS_RTP_PACK_HANDLE hPack; - uint32_t numFramesPerPacket = ( config->maxFramesPerPacket == 0 ) ? 1 : config->maxFramesPerPacket; + uint32_t numFramesPerPacket = ( config->maxFramesPerPacket == 0 ) ? IVAS_MAX_FRAMES_PER_RTP_PACKET : config->maxFramesPerPacket; if ( phPack == NULL ) { return IVAS_ERR_UNEXPECTED_NULL_POINTER; } + if ( numFramesPerPacket > IVAS_MAX_FRAMES_PER_RTP_PACKET ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Max frame per packet exeeds %d", IVAS_MAX_FRAMES_PER_RTP_PACKET ); + } + *phPack = NULL; if ( ( hPack = (IVAS_RTP_PACK_HANDLE) calloc( 1, sizeof( struct IVAS_RTP_PACK ) ) ) == NULL ) { @@ -1193,7 +1192,12 @@ ivas_error IVAS_RTP_UNPACK_Open( return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtp unpack handle" ); } - hUnpack->maxNumberOfFrames = ( config->maxFramesPerPacket < IVAS_MAX_FRAMES_PER_RTP_PACKET ) ? IVAS_MAX_FRAMES_PER_RTP_PACKET : config->maxFramesPerPacket; + if ( config->maxFramesPerPacket > IVAS_MAX_FRAMES_PER_RTP_PACKET ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Max frame per packet exeeds %d", IVAS_MAX_FRAMES_PER_RTP_PACKET ); + } + + hUnpack->maxNumberOfFrames = ( config->maxFramesPerPacket == 0 ) ? IVAS_MAX_FRAMES_PER_RTP_PACKET : config->maxFramesPerPacket; hUnpack->maxNumberOfPiData = hUnpack->maxNumberOfFrames * IVAS_PI_MAX_ID; error = BPOOL_Create( &hUnpack->unpackNodePool, sizeof( UNPACK_NODE ), hUnpack->maxNumberOfFrames ); @@ -1390,7 +1394,7 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t return nBytes; } -static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numFrames, TOC_INFO *toc ) +static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numFrames, TOC_INFO *toc, uint32_t maxNumberOfToCBytes ) { bool headerFollows = true; uint32_t nBytes = *numBytes; @@ -1411,13 +1415,14 @@ static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBy nBytes++; /* Consume this e-byte */ - *numFrames += 1; - memset( toc, 0, sizeof( *toc ) ); - if ( *numFrames == MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET ) + if ( *numFrames == maxNumberOfToCBytes ) { return IVAS_ERROR( IVAS_ERR_INTERNAL, "No of frames in packet exceed max defined" ); } + *numFrames += 1; + memset( toc, 0, sizeof( *toc ) ); + if ( FT == TOC_INDICATE_ARMWB_Q || FT == TOC_INDICATE_AMRWB ) { toc->codecId = IVAS_RTP_EVS; @@ -1598,7 +1603,7 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( ivas_error error = IVAS_ERR_OK; uint32_t nBytes = 0, numFrames = 0, numPiData = 0, n; bool piDataIndicated = false; - TOC_INFO toc[MAX_SUPPORTED_IVAS_FRAMES_PER_PACKET]; + TOC_INFO toc[IVAS_MAX_FRAMES_PER_RTP_PACKET]; IVAS_RTP_REQUEST_VALUE oldRequests[IVAS_REQUEST_MAX]; @@ -1638,7 +1643,7 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( nBytes = parseSubsequentEByte( payload, nBytes, hUnpack->requests, &piDataIndicated ); /* Unpack the ToC Bytes => Extract number of frames in packet */ - error = parseToCByte( payload, &nBytes, &numFrames, toc ); + error = parseToCByte( payload, &nBytes, &numFrames, toc, sizeof( toc ) / sizeof( toc[0] ) ); ERR_CHECK_RETURN( error ); /* Read frame bits */ diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 8529de51b7..92cb0e8e71 100644 --- a/lib_util/ivas_rtp_payload.h +++ b/lib_util/ivas_rtp_payload.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index b88bb0e661..d3e8774281 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other @@ -47,9 +47,9 @@ static __inline uint32_t writeInt16( uint8_t *buffer, uint32_t idx, int16_t val return idx; } -static __inline uint16_t readInt16( const uint8_t *buffer ) +static __inline int16_t readInt16( const uint8_t *buffer ) { - return ( (uint16_t) buffer[0] << 8 ) | ( (uint16_t) buffer[1] ); + return (int16_t) ( (uint16_t) buffer[0] << 8 ) | ( (uint16_t) buffer[1] ); } /*-----------------------------------------------------------------------* @@ -166,10 +166,10 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); - orientation->orientation.w = ( (int16_t) readInt16( &buffer[0] ) ) / 32768.0f; - orientation->orientation.x = ( (int16_t) readInt16( &buffer[2] ) ) / 32768.0f; - orientation->orientation.y = ( (int16_t) readInt16( &buffer[4] ) ) / 32768.0f; - orientation->orientation.z = ( (int16_t) readInt16( &buffer[6] ) ) / 32768.0f; + 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] ) ); return IVAS_ERR_OK; } @@ -330,6 +330,11 @@ static ivas_error packAudioDescription( const IVAS_PIDATA_GENERIC *piData, uint8 *nBytesWritten = 0; + if ( audioDesc->nValidEntries > ( IVAS_PI_MAX_OBJECTS + 1 ) ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Audio Description cannot have more than 5 entries" ); + } + /* Audio Description data is max 5 bytes, 2 bytes header */ if ( maxDataBytes < packedSize + 2 ) { @@ -358,7 +363,7 @@ static ivas_error unpackAudioDescription( const uint8_t *buffer, uint32_t numDat IVAS_PIDATA_AUDIO_DESC *audioDesc = (IVAS_PIDATA_AUDIO_DESC *) piData; /* Audio Description data is max 5 bytes */ - if ( numDataBytes > 5 ) + if ( numDataBytes > ( IVAS_PI_MAX_OBJECTS + 1 ) ) { return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); } @@ -449,9 +454,9 @@ static ivas_error packListenerPosition( const IVAS_PIDATA_GENERIC *piData, uint8 buffer[nBytes++] = ( listener->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ buffer[nBytes++] = 6; - nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.x / 327.68f ) ); - nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.y / 327.68f ) ); - nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.z / 327.68f ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.x / MAX_PI_POSITION_METERS ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.y / MAX_PI_POSITION_METERS ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( listener->position.z / MAX_PI_POSITION_METERS ) ); *nBytesWritten = nBytes; return IVAS_ERR_OK; } @@ -468,9 +473,9 @@ static ivas_error unpackListenerPosition( const uint8_t *buffer, uint32_t numDat listener->size = sizeof( IVAS_PIDATA_LISTENER_POSITION ); listener->piDataType = IVAS_PI_LISTENER_POSITION; - listener->position.x = ( (int16_t) readInt16( &buffer[0] ) ) / 100.0f; - listener->position.y = ( (int16_t) readInt16( &buffer[2] ) ) / 100.0f; - listener->position.z = ( (int16_t) readInt16( &buffer[4] ) ) / 100.0f; + listener->position.x = FLOAT_FROM_Q15( readInt16( &buffer[0] ) ) * MAX_PI_POSITION_METERS; + listener->position.y = FLOAT_FROM_Q15( readInt16( &buffer[2] ) ) * MAX_PI_POSITION_METERS; + listener->position.z = FLOAT_FROM_Q15( readInt16( &buffer[4] ) ) * MAX_PI_POSITION_METERS; return IVAS_ERR_OK; } @@ -498,7 +503,8 @@ static ivas_error packDiegetic( const IVAS_PIDATA_GENERIC *piData, uint8_t *buff return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Diegetic Type PI data" ); } - for ( n = 0; n < 5; n++ ) + /* Valid bits must be based on active bits defined for the input format */ + for ( n = 0; n < ( IVAS_PI_MAX_OBJECTS + 1 ); n++ ) { byte <<= 1; byte |= ( diegetic->isDiegetic[n] ); @@ -528,7 +534,8 @@ static ivas_error unpackDiegetic( const uint8_t *buffer, uint32_t numDataBytes, diegetic->piDataType = IVAS_PI_DIEGETIC_TYPE; byte = buffer[0]; - for ( n = 0; n < 5; n++ ) + /* Valid bits must be based on active bits defined for the input format */ + for ( n = 0; n < ( IVAS_PI_MAX_OBJECTS + 1 ); n++ ) { diegetic->isDiegetic[n] = ( ( byte >> ( 7 - n ) ) & 1 ) != 0; } @@ -569,7 +576,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packOrientation, /* HEAD_ORIENTATION */ packListenerPosition, /* LISTENER_POSITION */ packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - packAudioDescription, /* AUDIO_FOCUS_DIRECTION */ + packOrientation, /* AUDIO_FOCUS_DIRECTION */ #else packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ packUnsupportedData, /* HEAD_ORIENTATION */ diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index ba82914df1..3be265bb87 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other diff --git a/lib_util/ivas_rtp_rom.c b/lib_util/ivas_rtp_rom.c index 4b8539fb30..016e6ce34e 100644 --- a/lib_util/ivas_rtp_rom.c +++ b/lib_util/ivas_rtp_rom.c @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -- GitLab From d66368c96f9d7206f8fb400db0eb81623042bf71 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 27 Aug 2025 12:48:40 +0200 Subject: [PATCH 087/340] Prepare TD_RINGBUF for use in split rendering --- lib_rend/ivas_prot_rend.h | 26 +++++++ lib_rend/ivas_td_ring_buffer.c | 129 +++++++++++++++++++++++++++++++++ lib_rend/lib_rend.c | 23 ++++++ 3 files changed, 178 insertions(+) diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 2268f62add..49909d1a6b 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1596,6 +1596,31 @@ void ivas_TD_RINGBUF_Close( TD_RINGBUF_HANDLE *ph /* i/o: Ring buffer handle */ ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +void ivas_TD_RINGBUF_PushInterleaved( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float *data, /* i : Input audio in interleaved channels layout */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +); + +void ivas_TD_RINGBUF_PushChannels( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float *p_channels[], /* i : Array of pointers to each input channel */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to store */ +); + +void ivas_TD_RINGBUF_PushConstant( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float value, /* i : Value to push */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +); + +void ivas_TD_RINGBUF_PopChannels( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + float *p_channels[], /* i : Array of pointers to each output channel */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to pop */ +); +#else void ivas_TD_RINGBUF_Push( TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ const float *data, /* i : Input data */ @@ -1612,6 +1637,7 @@ void ivas_TD_RINGBUF_Pop( float *data, /* i : Output data */ const uint32_t num_samples_per_channel /* i : Number of samples per channel to retrieve*/ ); +#endif uint32_t ivas_TD_RINGBUF_Size( const TD_RINGBUF_HANDLE h /* i : Ring buffer handle */ diff --git a/lib_rend/ivas_td_ring_buffer.c b/lib_rend/ivas_td_ring_buffer.c index 5c9c6089bb..2604fd9539 100644 --- a/lib_rend/ivas_td_ring_buffer.c +++ b/lib_rend/ivas_td_ring_buffer.c @@ -66,6 +66,40 @@ static int16_t ivas_td_ringbuf_has_space_for_num_samples( return (int16_t) ( ivas_td_ringbuf_total_size( h ) + num_samples <= h->capacity ); } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static void ivas_td_ringbuf_push_interleaved( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float *data, /* i : Input audio in interleaved channels layout */ + const uint32_t num_samples_per_channel, /* i : Number of samples per channel to push */ + const uint16_t read_stride /* i: : 1 for normal operation, 0 for reading from a single input value */ +) +{ + uint32_t s, read_s; + + assert( h != NULL ); + assert( data != NULL ); + assert( read_stride == 0 || read_stride == 1 ); + assert( ivas_td_ringbuf_has_space_for_num_samples( h, num_samples_per_channel * h->num_channels ) ); + + for ( s = 0, read_s = 0; s < num_samples_per_channel * h->num_channels; ++s, read_s += read_stride ) + { + h->data[h->write_pos] = data[read_s]; + ++h->write_pos; + + if ( h->write_pos == h->capacity ) + { + h->write_pos = 0; + } + } + + if ( h->read_pos == h->write_pos ) + { + h->is_full = 1; + } + + return; +} +#endif /*-----------------------------------------------------------------------* * Global function definitions @@ -150,6 +184,36 @@ void ivas_TD_RINGBUF_Close( } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * ivas_TD_RINGBUF_PushInterleaved() + * + * Push samples from a buffer with interleaved channel layout onto the back of the TD ring buffer. + *---------------------------------------------------------------------*/ + +void ivas_TD_RINGBUF_PushInterleaved( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float *data, /* i : Input audio in interleaved channels layout */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +) +{ + ivas_td_ringbuf_push_interleaved( h, data, num_samples_per_channel, 1 ); + + return; +} + +/*---------------------------------------------------------------------* + * ivas_TD_RINGBUF_PushChannels() + * + * Push samples from channel pointers onto the back of the TD ring buffer. + *---------------------------------------------------------------------*/ + +void ivas_TD_RINGBUF_PushChannels( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float *p_channels[], /* i : Array of pointers to each input channel */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +) +#else /*---------------------------------------------------------------------* * ivas_TD_RINGBUF_Push() * @@ -162,17 +226,30 @@ void ivas_TD_RINGBUF_Push( const float *data, /* i : Input data */ const uint32_t num_samples_per_channel /* i : Number of samples per channel to store */ ) +#endif { uint32_t s; uint16_t c; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + assert( h != NULL ); + assert( p_channels != NULL ); + for ( c = 0; c < h->num_channels; ++c ) + { + assert( p_channels[c] != NULL ); + } +#endif assert( ivas_td_ringbuf_has_space_for_num_samples( h, num_samples_per_channel * h->num_channels ) ); for ( s = 0; s < num_samples_per_channel; ++s ) { for ( c = 0; c < h->num_channels; ++c ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + h->data[h->write_pos] = p_channels[c][s]; +#else h->data[h->write_pos] = *( data + c * num_samples_per_channel + s ); +#endif ++h->write_pos; if ( h->write_pos == h->capacity ) @@ -191,6 +268,24 @@ void ivas_TD_RINGBUF_Push( } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * ivas_TD_RINGBUF_PushConstant() + * + * Push samples with a constant value onto the back of the TD ring buffer. + *---------------------------------------------------------------------*/ + +void ivas_TD_RINGBUF_PushConstant( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float value, /* i : Value to push */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +) +{ + ivas_td_ringbuf_push_interleaved( h, &value, num_samples_per_channel, 0 ); + + return; +} +#else /*---------------------------------------------------------------------* * ivas_TD_RINGBUF_PushZeros() * @@ -232,8 +327,22 @@ void ivas_TD_RINGBUF_PushZeros( return; } +#endif + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +/*---------------------------------------------------------------------* + * ivas_TD_RINGBUF_PopChannels() + * + * Pop samples from the front of the TD ring buffer to an array of channel pointers. + *---------------------------------------------------------------------*/ +void ivas_TD_RINGBUF_PopChannels( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + float *p_channels[], /* i : Array of pointers to each output channel */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to pop */ +) +#else /*---------------------------------------------------------------------* * ivas_TD_RINGBUF_Pop() * @@ -245,17 +354,30 @@ void ivas_TD_RINGBUF_Pop( float *data, /* i : Output data */ const uint32_t num_samples_per_channel /* i : Number of samples per channel to retrieve */ ) +#endif { uint32_t s; uint16_t c; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + assert( h != NULL ); + assert( p_channels != NULL ); + for ( c = 0; c < h->num_channels; ++c ) + { + assert( p_channels[c] != NULL ); + } +#endif assert( ivas_td_ringbuf_total_size( h ) >= num_samples_per_channel * h->num_channels ); for ( s = 0; s < num_samples_per_channel; ++s ) { for ( c = 0; c < h->num_channels; ++c ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + p_channels[c][s] = h->data[h->read_pos]; +#else *( data + c * num_samples_per_channel + s ) = h->data[h->read_pos]; +#endif ++h->read_pos; if ( h->read_pos == h->capacity ) @@ -265,10 +387,17 @@ void ivas_TD_RINGBUF_Pop( } } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( num_samples_per_channel != 0 ) + { + h->is_full = 0; + } +#else if ( h->is_full ) { h->is_full = 0; } +#endif return; } diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 25352b2711..77a58f2c27 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -1479,6 +1479,11 @@ static ivas_error alignInputDelay( int16_t maxGlobalDelaySamples; int32_t numSamplesToPush, numSamplesToPop; uint32_t ringBufferSize, preDelay; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + int32_t i; + const float *p_read_channels[MAX_INPUT_CHANNELS]; + float *p_write_channels[MAX_INPUT_CHANNELS]; +#endif maxGlobalDelaySamples = latencyNsToSamples( sampleRateOut, maxGlobalDelayNs ); maxGlobalDelaySamples *= cldfb2tdSampleFact; @@ -1501,7 +1506,11 @@ static ivas_error alignInputDelay( /* for the first frame we need to push zeros to align the input delay to the global delay * and then push a frame of actual data */ +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ivas_TD_RINGBUF_PushConstant( inputBase->delayBuffer, 0, preDelay ); +#else ivas_TD_RINGBUF_PushZeros( inputBase->delayBuffer, preDelay ); +#endif /* for ISM inputs, ensure the metadata sync delay is updated */ if ( getAudioConfigType( inputBase->inConfig ) == IVAS_REND_AUDIO_CONFIG_TYPE_OBJECT_BASED ) @@ -1519,8 +1528,22 @@ static ivas_error alignInputDelay( numSamplesToPush = flushInputs ? 0 : inputAudio.config.numSamplesPerChannel; numSamplesToPop = flushInputs ? ivas_TD_RINGBUF_Size( inputBase->delayBuffer ) : (uint32_t) inputAudio.config.numSamplesPerChannel; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + for ( i = 0; i < inputAudio.config.numChannels; ++i ) + { + p_read_channels[i] = inputAudio.data + i * numSamplesToPush; + } + ivas_TD_RINGBUF_PushChannels( inputBase->delayBuffer, p_read_channels, numSamplesToPush ); + + for ( i = 0; i < inputAudio.config.numChannels; ++i ) + { + p_write_channels[i] = inputBase->inputBuffer.data + i * numSamplesToPop; + } + ivas_TD_RINGBUF_PopChannels( inputBase->delayBuffer, p_write_channels, numSamplesToPop ); +#else ivas_TD_RINGBUF_Push( inputBase->delayBuffer, inputAudio.data, numSamplesToPush ); ivas_TD_RINGBUF_Pop( inputBase->delayBuffer, inputBase->inputBuffer.data, numSamplesToPop ); +#endif } else { -- GitLab From 3bdd36d26dd83ffc46add1ce012533bf1d7f6d9f Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 27 Aug 2025 16:21:24 +0200 Subject: [PATCH 088/340] Keep head poses in ring buffer to persist data between calls to IVAS_DEC_VoIP_GetSplitBinauralBitstream --- lib_dec/ivas_stat_dec.h | 1 + lib_dec/lib_dec.c | 83 ++++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 39a50b5e52..b0e6631361 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -849,6 +849,7 @@ typedef struct typedef struct { #ifdef FIX_1119_SPLIT_RENDERING_VOIP + TD_RINGBUF_HANDLE hMultiBinTdData; CLDFB_RINGBUF_HANDLE hMultiBinCldfbData[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS]; #else ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE hMultiBinCldfbData; /*scratch buffer for frame by frame processing*/ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 1b73d7ca23..3b3a439357 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -129,6 +129,10 @@ static ivas_error ivas_dec_init_split_rend( Decoder_Struct *st_ivas ); static ivas_error ivas_create_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *hSplitBinRend_out ); static void ivas_destroy_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *hSplitBinRend_out ); static int16_t get_render_frame_size_ms( IVAS_RENDER_FRAMESIZE render_framesize ); +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static int16_t get_render_frame_size_samples( Decoder_Struct *st_ivas ); +static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct* st_ivas ); +#endif static void update_voip_rendered20ms( IVAS_DEC_HANDLE hIvasDec, const int16_t nSamplesRendered ); @@ -637,6 +641,13 @@ ivas_error IVAS_DEC_GetRenderFramesize( return IVAS_ERR_OK; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static int16_t get_render_frame_size_samples( Decoder_Struct *st_ivas ) +{ + return (int16_t) ( st_ivas->hDecoderConfig->output_Fs * st_ivas->hDecoderConfig->render_framesize / ( FRAMES_PER_SEC * IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ) ); +} +#endif + /*---------------------------------------------------------------------* * IVAS_DEC_GetGetRenderFramesizeSamples( ) * @@ -653,7 +664,11 @@ ivas_error IVAS_DEC_GetRenderFramesizeSamples( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + *render_framesize = get_render_frame_size_samples( hIvasDec->st_ivas ); +#else *render_framesize = (int16_t) ( hIvasDec->st_ivas->hDecoderConfig->output_Fs * hIvasDec->st_ivas->hDecoderConfig->render_framesize / ( FRAMES_PER_SEC * IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#endif return IVAS_ERR_OK; } @@ -1866,14 +1881,12 @@ static int16_t isar_get_frame_size( static ivas_error isar_render_poses( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ - float **p_head_pose_buf, int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) { Decoder_Struct *st_ivas; float pcmBuf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES * L_FRAME48k]; - int16_t i, j; ivas_error error; ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; int16_t numPoses; @@ -1907,13 +1920,9 @@ static ivas_error isar_render_poses( return IVAS_ERR_OK; } - /* change buffer layout */ - for ( i = 0; i < *nOutSamples; ++i ) + if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) { - for ( j = 0; j < BINAURAL_CHANNELS * numPoses; ++j ) - { - p_head_pose_buf[j][i] = pcmBuf[i * BINAURAL_CHANNELS * numPoses + j]; - } + ivas_TD_RINGBUF_PushInterleaved( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, pcmBuf, *nOutSamples ); } return error; @@ -2057,12 +2066,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; numSamplesPerChannelToOutput = isar_get_frame_size( st_ivas ); - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] = head_pose_buf[i]; - } - - error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ); + error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, nOutSamples, needNewFrame ); if ( error != IVAS_ERR_OK ) { return error; @@ -2072,6 +2076,16 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( return IVAS_ERR_OK; } + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] = head_pose_buf[i]; + } + + if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) + { + ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nOutSamples ); + } + error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); if ( error != IVAS_ERR_OK ) { @@ -3932,14 +3946,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { - /* Move output pointers forward */ - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] = &head_pose_buf[i][*nSamplesRendered]; - } - /* Render head poses from time-scaled transport channels */ - if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) { return error; } @@ -3966,12 +3974,16 @@ ivas_error IVAS_DEC_VoIP_GetSamples ( hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || hIvasDec->st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) { - /* Set pointers to beginning of head pose buffers */ for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) { p_head_pose_buf[i] = head_pose_buf[i]; } + if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) + { + ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nSamplesRendered ); + } + /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); if ( error != IVAS_ERR_OK ) @@ -5186,6 +5198,7 @@ static ivas_error ivas_create_handle_isar( isar_init_split_rend_handles( &hSplitBinRend->splitrend ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP + hSplitBinRend->hMultiBinTdData = NULL; for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) { hSplitBinRend->hMultiBinCldfbData[i] = NULL; @@ -5219,6 +5232,10 @@ static void ivas_destroy_handle_isar( if ( *hSplitBinRend != NULL ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( ( *hSplitBinRend )->hMultiBinTdData != NULL ) + { + ivas_TD_RINGBUF_Close( &( *hSplitBinRend )->hMultiBinTdData ); + } for ( i = 0; i < (int16_t) ( MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS ); ++i ) { if ( ( *hSplitBinRend )->hMultiBinCldfbData[i] != NULL ) @@ -5403,6 +5420,16 @@ static ivas_error ivas_dec_reconfig_split_rend( return IVAS_ERR_OK; } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP +static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct* st_ivas ) +{ + + return st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || + st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || + st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC || + st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC_ROOM; +} +#endif /*-------------------------------------------------------------------* * ivas_dec_init_split_rend() @@ -5424,6 +5451,9 @@ static ivas_error ivas_dec_init_split_rend( pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; cldfb_in_flag = 0; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + cldfb_in_flag = ivas_dec_split_rend_cldfb_in( st_ivas ); +#else if ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC || @@ -5431,6 +5461,7 @@ static ivas_error ivas_dec_init_split_rend( { cldfb_in_flag = 1; } +#endif #ifdef FIX_1119_SPLIT_RENDERING_VOIP ISAR_PRE_REND_GetMultiBinPoseData( &st_ivas->hRenderConfig->split_rend_config, &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, ( st_ivas->hHeadTrackData != NULL ) ? st_ivas->hHeadTrackData->sr_pose_pred_axis : DEFAULT_AXIS ); @@ -5449,6 +5480,14 @@ static ivas_error ivas_dec_init_split_rend( } } } + else + { + error = ivas_TD_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinTdData, get_render_frame_size_samples( st_ivas ), num_poses * BINAURAL_CHANNELS ); + if ( error != IVAS_ERR_OK ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); + } + } #else /* note: this is intra-frame heap memory */ if ( ( st_ivas->hSplitBinRend->hMultiBinCldfbData = (ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA_HANDLE) malloc( sizeof( ISAR_DEC_SPLIT_REND_MULTI_BIN_CLDFB_DATA ) ) ) == NULL ) -- GitLab From a2a0096b0d0e8aa46745040c842f722ab0a4c9f4 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 29 Aug 2025 10:16:00 +0200 Subject: [PATCH 089/340] Formatting --- lib_dec/lib_dec.c | 14 +++++++------- lib_rend/ivas_td_ring_buffer.c | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 3b3a439357..f9f3121d35 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -131,7 +131,7 @@ static void ivas_destroy_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *hSplit static int16_t get_render_frame_size_ms( IVAS_RENDER_FRAMESIZE render_framesize ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP static int16_t get_render_frame_size_samples( Decoder_Struct *st_ivas ); -static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct* st_ivas ); +static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct *st_ivas ); #endif static void update_voip_rendered20ms( IVAS_DEC_HANDLE hIvasDec, const int16_t nSamplesRendered ); @@ -1881,8 +1881,8 @@ static int16_t isar_get_frame_size( static ivas_error isar_render_poses( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ - int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ - bool *needNewFrame /* o : indication that the decoder needs a new frame */ + int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ + bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) { Decoder_Struct *st_ivas; @@ -5421,13 +5421,13 @@ static ivas_error ivas_dec_reconfig_split_rend( } #ifdef FIX_1119_SPLIT_RENDERING_VOIP -static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct* st_ivas ) +static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct *st_ivas ) { return st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || - st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || - st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC || - st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC_ROOM; + st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || + st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC || + st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC_ROOM; } #endif diff --git a/lib_rend/ivas_td_ring_buffer.c b/lib_rend/ivas_td_ring_buffer.c index 2604fd9539..6381a00e90 100644 --- a/lib_rend/ivas_td_ring_buffer.c +++ b/lib_rend/ivas_td_ring_buffer.c @@ -275,10 +275,10 @@ void ivas_TD_RINGBUF_Push( * Push samples with a constant value onto the back of the TD ring buffer. *---------------------------------------------------------------------*/ -void ivas_TD_RINGBUF_PushConstant( - TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ - const float value, /* i : Value to push */ - const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ +void ivas_TD_RINGBUF_PushConstant( + TD_RINGBUF_HANDLE h, /* i/o: Ring buffer handle */ + const float value, /* i : Value to push */ + const uint32_t num_samples_per_channel /* i : Number of samples per channel to push */ ) { ivas_td_ringbuf_push_interleaved( h, &value, num_samples_per_channel, 0 ); -- GitLab From 5c74a6b28ba5e39d9afe9feb15c21d096a7ed90b Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 29 Aug 2025 14:07:42 +0200 Subject: [PATCH 090/340] Increase timeout for renderer sanitizer jobs --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bca6cb6c64..7a02bd69e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -550,7 +550,7 @@ renderer-smoke-test: - .rules-merge-request-to-main needs: ["build-codec-linux-cmake"] stage: test - timeout: "1 hour" + timeout: "90 minutes" artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week -- GitLab From e26011eb64ffdc39e4001a770e924ee833dd287b Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 29 Aug 2025 15:03:23 +0200 Subject: [PATCH 091/340] Clean up in tests/split_rendering/test_voip_be_splitrend_vs_binaural.py --- .../test_voip_be_splitrend_vs_binaural.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py index dd9c4f317c..fcd6cb231f 100644 --- a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -42,7 +42,6 @@ from tests.test_be_for_jbm_neutral_dly_profile import ( INPUT_FILES, get_options_cod, ) -from split_rendering.isar_bstool import IsarBitstream from pyaudio3dtools import audioarray, audiofile IN_FORMATS = [ @@ -67,7 +66,7 @@ def test_voip_be_splitrend_vs_binaural( delay_profile, dut_encoder_frontend, dut_decoder_frontend, - bitrate=128000, + ivas_bitrate=128000, ): with TemporaryDirectory() as tmp_dir: tmp_dir = Path(tmp_dir) @@ -81,7 +80,7 @@ def test_voip_be_splitrend_vs_binaural( dtx = False wav_in = TESTV_DIR / INPUT_FILES[in_format] dut_encoder_frontend.run( - bitrate, + ivas_bitrate, sampling_rate_khz, wav_in, bitstream_file, @@ -92,13 +91,16 @@ def test_voip_be_splitrend_vs_binaural( def run_decoder(out_format): options = [] - # Head trajectory must be static due to the slight time shift between audio outputs of BINAURAL/SPLIT_PCM - head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") + # With CLDFB pose correction (default with BINAURAL_SPLIT_PCM), a 20 ms audio frame is + # rendered with only one head position (first of the 4 per frame). If we want to compare + # the output from BINAURAL_SPLIT_PCM to output from BINAURAL, the head trajectory must + # be static. + head_traj = Path(SCRIPTS_DIR / "trajectories/const000.csv") options.extend(["-T", str(head_traj)]) wav_out = ( tmp_dir - / f"{in_format}-{bitrate}-{out_format}-dly{delay_profile_id}.wav" + / f"{in_format}-{ivas_bitrate}-{out_format}-dly{delay_profile_id}.wav" ).absolute() trace_out = wav_out.with_suffix(".trace") @@ -122,7 +124,7 @@ def test_voip_be_splitrend_vs_binaural( return wav_out, trace_out, isar_md_file wav_out_bin, trace_out_bin, _ = run_decoder("BINAURAL") - wav_out_sr, trace_out_sr, isar_md_out_sr = run_decoder("BINAURAL_SPLIT_PCM") + wav_out_sr, trace_out_sr, _ = run_decoder("BINAURAL_SPLIT_PCM") # Note regarding delay alignment: both output audio files contain the same decoder delay. # -- GitLab From f461fe5ea8afcb2d482ccdc20b7c675e1b831680 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 29 Aug 2025 17:02:52 +0200 Subject: [PATCH 092/340] Fix usan problems in CLDFB_RINGBUF_GetByIdx --- lib_dec/cldfb_ring_buffer.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib_dec/cldfb_ring_buffer.c b/lib_dec/cldfb_ring_buffer.c index c6be06b1ea..959929d572 100644 --- a/lib_dec/cldfb_ring_buffer.c +++ b/lib_dec/cldfb_ring_buffer.c @@ -153,10 +153,7 @@ void CLDFB_RINGBUF_Pop( CLDFB_RINGBUF_HANDLE h, float *real, float *imag, uint16 h->read_pos = 0; } - if ( h->is_full ) - { - h->is_full = 0; - } + h->is_full = 0; return; } @@ -181,17 +178,29 @@ void CLDFB_RINGBUF_GetByIdx( CLDFB_RINGBUF_HANDLE h, float **p_real, float **p_i { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; int32_t num_floats = (int32_t) total_size( h ); - uint32_t offset; + uint32_t offset, uidx; assert( -num_floats <= idx && idx <= num_floats ); if ( idx >= 0 ) { - offset = ( h->read_pos + idx ) % h->capacity; + offset = h->read_pos + idx; + if ( h->capacity <= offset ) + { + offset -= h->capacity; + } } else { - offset = ( h->write_pos + h->capacity + idx ) % h->capacity; + uidx = (uint32_t) -idx; + if ( uidx <= h->write_pos ) + { + offset = h->write_pos - uidx; + } + else + { + offset = h->write_pos + h->capacity - uidx; + } } *p_real = &h->real[offset]; -- GitLab From 7668943b3c92f859dfe88a800a290b68dbe7159c Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 1 Sep 2025 12:30:58 +0200 Subject: [PATCH 093/340] Combined orientation data: use the same indexing for helper head poses as for the main pose This fixes an out-of-bounds array access in split rendering --- lib_rend/ivas_dirac_dec_binaural_functions.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 33328e365c..7b2630fd67 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -854,11 +854,19 @@ static void ivas_dirac_dec_binaural_internal( mvr2r( st_ivas->hDiracDecBin[0]->ChCrossIm, hDiracDecBin->ChCrossIm, hSpatParamRendCom->num_freq_bands ); ivas_dirac_dec_binaural_formulate_target_covariance_matrices( hDiracDecBin, hSpatParamRendCom, &config_data, Rmat_local, subframe, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + hCombinedOrientationData && hCombinedOrientationData->enableCombinedOrientation[hCombinedOrientationData->subframe_idx] > 0, +#else hCombinedOrientationData && hCombinedOrientationData->enableCombinedOrientation[subframe] > 0, +#endif subFrameTotalEne, IIReneLimiter, st_ivas->hMasaIsmData ); ivas_dirac_dec_binaural_determine_processing_matrices( hDiracDecBin, hSpatParamRendCom, &config_data, max_band_decorr, Rmat_local, subframe, +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + hCombinedOrientationData && hCombinedOrientationData->enableCombinedOrientation[hCombinedOrientationData->subframe_idx] > 0, +#else hCombinedOrientationData && hCombinedOrientationData->enableCombinedOrientation[subframe] > 0, +#endif nchanSeparateChannels, st_ivas->hMasaIsmData ); -- GitLab From 69eb1fe9f2b960bb7192001ce78cc47870024b77 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 1 Sep 2025 13:25:44 +0200 Subject: [PATCH 094/340] [tmp] Remove new test cases in test_split_rendering.py Those were causing CI to fail at the comparison stage, as the main branch has no corresponding test outputs to compare to. The new test cases will be merged later via a separate MR. --- tests/split_rendering/test_split_rendering.py | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index 8f4d71a406..cac6c9b949 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -37,7 +37,6 @@ from tests.split_rendering.utils import * """ Ambisonics """ -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_AMBI) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_AMBI) @@ -55,7 +54,6 @@ def test_ambisonics_full_chain_split( bitrate, render_config, trajectory, - delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -75,7 +73,6 @@ def test_ambisonics_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -117,7 +114,6 @@ def test_ambisonics_external_split( """ Multichannel """ -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MC) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MC) @@ -135,7 +131,6 @@ def test_multichannel_full_chain_split( bitrate, render_config, trajectory, - delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -155,7 +150,6 @@ def test_multichannel_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -197,7 +191,6 @@ def test_multichannel_external_split( """ ISM """ -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_ISM) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_ISM) @@ -215,7 +208,6 @@ def test_ism_full_chain_split( bitrate, render_config, trajectory, - delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -235,7 +227,6 @@ def test_ism_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -277,7 +268,6 @@ def test_ism_external_split( """ MASA """ -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MASA) @@ -295,7 +285,6 @@ def test_masa_full_chain_split( bitrate, render_config, trajectory, - delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -315,7 +304,6 @@ def test_masa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -357,7 +345,6 @@ def test_masa_external_split( """ OMASA """ -# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OMASA) @@ -375,7 +362,6 @@ def test_omasa_full_chain_split( bitrate, render_config, trajectory, - delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -395,7 +381,6 @@ def test_omasa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -437,7 +422,6 @@ def test_omasa_external_split( """ OSBA """ -# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OSBA) @@ -455,7 +439,6 @@ def test_osba_full_chain_split( bitrate, render_config, trajectory, - delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -475,7 +458,6 @@ def test_osba_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -567,7 +549,6 @@ full_chain_split_pcm_params = [ ] -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("in_fmt,bitrate,render_config", full_chain_split_pcm_params) def test_full_chain_split_pcm( record_property, @@ -581,7 +562,6 @@ def test_full_chain_split_pcm( in_fmt, bitrate, render_config, - delay_profile, ): trajectory = SPLIT_REND_HR_TRAJECTORIES_TO_TEST[0] post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") @@ -603,7 +583,6 @@ def test_full_chain_split_pcm( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -689,10 +668,9 @@ def test_framing_combinations_external_split( ) -@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_FRAMING) -@pytest.mark.parametrize("in_fmt", ["5_1", "FOA"]) +@pytest.mark.parametrize("in_fmt", ["5_1"]) @pytest.mark.parametrize("pre_rend_fr", SPLIT_RENDERER_PRE_FRAMINGS) @pytest.mark.parametrize("post_rend_fr", SPLIT_RENDERER_POST_FRAMINGS) def test_framing_combinations_full_chain_split( @@ -709,7 +687,6 @@ def test_framing_combinations_full_chain_split( trajectory, post_rend_fr, pre_rend_fr, - delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -731,5 +708,4 @@ def test_framing_combinations_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, - delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) -- GitLab From 1b771d88f2951b1c0f6dc1973bc0ec6978db0df2 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 1 Sep 2025 13:37:52 +0200 Subject: [PATCH 095/340] Revert "[tmp] Remove new test cases in test_split_rendering.py" This reverts commit 69eb1fe9f2b960bb7192001ce78cc47870024b77. --- tests/split_rendering/test_split_rendering.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index cac6c9b949..8f4d71a406 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -37,6 +37,7 @@ from tests.split_rendering.utils import * """ Ambisonics """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_AMBI) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_AMBI) @@ -54,6 +55,7 @@ def test_ambisonics_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -73,6 +75,7 @@ def test_ambisonics_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -114,6 +117,7 @@ def test_ambisonics_external_split( """ Multichannel """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MC) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MC) @@ -131,6 +135,7 @@ def test_multichannel_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -150,6 +155,7 @@ def test_multichannel_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -191,6 +197,7 @@ def test_multichannel_external_split( """ ISM """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_ISM) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_ISM) @@ -208,6 +215,7 @@ def test_ism_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -227,6 +235,7 @@ def test_ism_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -268,6 +277,7 @@ def test_ism_external_split( """ MASA """ +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_MASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_MASA) @@ -285,6 +295,7 @@ def test_masa_full_chain_split( bitrate, render_config, trajectory, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -304,6 +315,7 @@ def test_masa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -345,6 +357,7 @@ def test_masa_external_split( """ OMASA """ +# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OMASA) @@ -362,6 +375,7 @@ def test_omasa_full_chain_split( bitrate, render_config, trajectory, + delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -381,6 +395,7 @@ def test_omasa_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -422,6 +437,7 @@ def test_omasa_external_split( """ OSBA """ +# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OSBA) @@ -439,6 +455,7 @@ def test_osba_full_chain_split( bitrate, render_config, trajectory, + delay_profile=None, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -458,6 +475,7 @@ def test_osba_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -549,6 +567,7 @@ full_chain_split_pcm_params = [ ] +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("in_fmt,bitrate,render_config", full_chain_split_pcm_params) def test_full_chain_split_pcm( record_property, @@ -562,6 +581,7 @@ def test_full_chain_split_pcm( in_fmt, bitrate, render_config, + delay_profile, ): trajectory = SPLIT_REND_HR_TRAJECTORIES_TO_TEST[0] post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") @@ -583,6 +603,7 @@ def test_full_chain_split_pcm( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) @@ -668,9 +689,10 @@ def test_framing_combinations_external_split( ) +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_FRAMING) -@pytest.mark.parametrize("in_fmt", ["5_1"]) +@pytest.mark.parametrize("in_fmt", ["5_1", "FOA"]) @pytest.mark.parametrize("pre_rend_fr", SPLIT_RENDERER_PRE_FRAMINGS) @pytest.mark.parametrize("post_rend_fr", SPLIT_RENDERER_POST_FRAMINGS) def test_framing_combinations_full_chain_split( @@ -687,6 +709,7 @@ def test_framing_combinations_full_chain_split( trajectory, post_rend_fr, pre_rend_fr, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -708,4 +731,5 @@ def test_framing_combinations_full_chain_split( get_ssnr=get_ssnr, get_odg=get_odg, get_odg_bin=get_odg_bin, + delay_profile=SCRIPTS_DIR / "dly_error_profiles" / f"{delay_profile}.dat" if delay_profile else None, ) -- GitLab From 460279d98e38aa2477627b89112a474efe5c2da9 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 2 Sep 2025 08:44:46 +0200 Subject: [PATCH 096/340] Use 512 kbps, 3 DOF as default config for split rendering in test_be_for_jbm_neutral_dly_profile.py --- tests/test_be_for_jbm_neutral_dly_profile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index 8642bc5658..b6f333b984 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory from pathlib import Path from .constants import TESTV_DIR, SCRIPTS_DIR -from .split_rendering.constants import HR_TRAJECTORY_DIR +from .split_rendering.constants import HR_TRAJECTORY_DIR, RENDER_CFG_DIR sys.path.append(str(SCRIPTS_DIR)) from pyaudio3dtools import audiofile, audioarray @@ -151,7 +151,9 @@ def get_options_dec( ): options = [] - if output_format == "BINAURAL_SPLIT_PCM": + if "BINAURAL_SPLIT" in output_format.upper(): + options.extend(["-render_config", str(RENDER_CFG_DIR / "split_renderer_config_3dof_512k_default.txt")]) + options.extend(["-om", str(output_file.with_suffix(".isarmd"))]) if is_split_rend(output_format): -- GitLab From 887ffd0cf20aa77456600fc73f18b44c393db6da Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 2 Sep 2025 08:45:45 +0200 Subject: [PATCH 097/340] Minor improvements in test_be_for_jbm_neutral_dly_profile.py --- tests/test_be_for_jbm_neutral_dly_profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_be_for_jbm_neutral_dly_profile.py b/tests/test_be_for_jbm_neutral_dly_profile.py index b6f333b984..dee54b7d94 100644 --- a/tests/test_be_for_jbm_neutral_dly_profile.py +++ b/tests/test_be_for_jbm_neutral_dly_profile.py @@ -90,14 +90,13 @@ TESTCASES_NO_DTX = [ # BINAURAL_SPLIT_CODED with LCLD ["HOA3", 128000, "BINAURAL_SPLIT_CODED"], ["OSBA_ISM4_FOA", 128000, "BINAURAL_SPLIT_CODED"], - ] DLY_PROFILE = SCRIPTS_DIR.joinpath("dly_error_profiles/dly_error_profile_0.dat") JBM_NEUTRAL_DELAY_MS = 60 def is_split_rend(format) -> bool: - return format in ["BINAURAL_SPLIT_CODED", "BINAURAL_SPLIT_PCM"] + return format.upper() in ["BINAURAL_SPLIT_CODED", "BINAURAL_SPLIT_PCM"] def get_options_cod(in_format, dtx): @@ -154,6 +153,7 @@ def get_options_dec( if "BINAURAL_SPLIT" in output_format.upper(): options.extend(["-render_config", str(RENDER_CFG_DIR / "split_renderer_config_3dof_512k_default.txt")]) + if output_format.upper() == "BINAURAL_SPLIT_PCM": options.extend(["-om", str(output_file.with_suffix(".isarmd"))]) if is_split_rend(output_format): -- GitLab From bfbc2e2739ca8fa8044a21c7f1ef5f08fb8b9ac4 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 4 Sep 2025 18:31:37 +1000 Subject: [PATCH 098/340] Code comments Signed-off-by: Ripinder Singh --- Workspace_msvc/lib_util.vcxproj | 2 - Workspace_msvc/lib_util.vcxproj.filters | 6 - apps/decoder.c | 13 +- apps/encoder.c | 8 +- lib_com/bitstream.c | 52 --- lib_com/common_api_types.h | 9 +- lib_com/ivas_cnst.h | 23 -- lib_com/ivas_error.h | 7 +- lib_com/ivas_prot.h | 4 - lib_enc/lib_enc.c | 19 -- lib_enc/lib_enc.h | 6 - lib_util/ivas_bpool.c | 2 +- lib_util/ivas_rtp_payload.c | 46 +-- lib_util/ivas_rtp_payload.h | 403 ------------------------ lib_util/ivas_rtp_pi_data.c | 58 +++- lib_util/ivas_rtp_rom.c | 64 ---- 16 files changed, 84 insertions(+), 638 deletions(-) delete mode 100644 lib_util/ivas_rtp_payload.h delete mode 100644 lib_util/ivas_rtp_rom.c diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 94f22c71ca..7c10648b72 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -112,7 +112,6 @@ - @@ -145,7 +144,6 @@ - diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index f81b34b6da..ed062708ad 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -88,9 +88,6 @@ util_c - - util_c - util_c @@ -189,9 +186,6 @@ util_h - - util_h - diff --git a/apps/decoder.c b/apps/decoder.c index 8cd444ec9a..a9f5818606 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -568,7 +568,7 @@ static void do_object_editing( IVAS_EDITABLE_PARAMETERS *editableParameters, Obj #ifdef IVAS_RTPDUMP static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE *phIvasDec, int16_t *pcmBuf ); -static int restartDecoder( +static ivas_error restartDecoder( IVAS_DEC_HANDLE *phIvasDec, IVAS_DEC_MODE codec, DecArguments *arg, @@ -580,7 +580,7 @@ static int restartDecoder( if ( phIvasDec == NULL ) { - return -1; + return IVAS_ERR_UNEXPECTED_NULL_POINTER; } if ( NULL != *phIvasDec ) @@ -665,12 +665,11 @@ static int restartDecoder( } } - return 0; + return IVAS_ERR_OK; cleanup: IVAS_DEC_Close( phIvasDec ); - - return -1; + return error; } #else static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); @@ -3742,14 +3741,14 @@ static ivas_error decodeVoIP( if ( ivasRtp.restartNeeded ) { IVAS_DEC_MODE newCodecInPacket = ( ivasRtp.codecId == IVAS_RTP_EVS ) ? IVAS_DEC_MODE_EVS : IVAS_DEC_MODE_IVAS; - int err = restartDecoder( + error = restartDecoder( &hIvasDec, newCodecInPacket, &arg, NULL, /* ToDo : Provide rendererConfig */ NULL /* ToDo : Provide LS Custom Data */ ); - if ( err < 0 ) + if ( error != IVAS_ERR_OK ) { fprintf( stderr, "\nFailed to restart decoder from %d to %d\n", arg.decMode, newCodecInPacket ); goto cleanup; diff --git a/apps/encoder.c b/apps/encoder.c index 9fb3b60013..4235e3d788 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -640,7 +640,7 @@ int main( packCfg.maxFramesPerPacket = arg.numFramesPerPacket; /* Open the output file for RTPDump writing */ - error = IvasRtpFile_Open ( arg.outputBitstreamFilename, true, &hWriter ); + error = IvasRtpFile_Open( arg.outputBitstreamFilename, true, &hWriter ); if ( error != IVAS_ERR_OK ) { fprintf( stderr, "could not open: %s\n", arg.outputBitstreamFilename ); @@ -876,7 +876,7 @@ int main( /* Push Encoded Stream to */ if ( ( error = IVAS_RTP_PACK_PushFrame( hPack, - IVAS_isImmersiveFormat( hIvasEnc ) ? IVAS_RTP_IVAS : IVAS_RTP_EVS, + arg.inputFormat != IVAS_ENC_INPUT_MONO ? IVAS_RTP_IVAS : IVAS_RTP_EVS, #ifdef RTP_S4_251135_CR26253_0016_REV1 NULL, #endif /* RTP_S4_251135_CR26253_0016_REV1 */ @@ -939,7 +939,7 @@ int main( goto cleanup; } - if ( ( error = IvasRtpFile_Write ( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) + if ( ( error = IvasRtpFile_Write( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); goto cleanup; @@ -1053,7 +1053,7 @@ cleanup: goto cleanup; } - if ( ( error = IvasRtpFile_Write ( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) + if ( ( error = IvasRtpFile_Write( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); goto cleanup; diff --git a/lib_com/bitstream.c b/lib_com/bitstream.c index 22fbcde10e..18bfba0d18 100644 --- a/lib_com/bitstream.c +++ b/lib_com/bitstream.c @@ -163,58 +163,6 @@ Word16 rate2EVSmode( return rate2AMRWB_IOmode( brate ); } -#ifdef IVAS_RTPDUMP -/*-------------------------------------------------------------------* - * rate2IVASmode() - * - * Lookup IVAS mode. Note: FRAME_NO_DATA frames should be looked up with rate2EVSmode(). - *-------------------------------------------------------------------*/ -Word16 rate2IVASmode( - const Word32 brate /* i : bitrate */ -) -{ - switch ( brate ) - { - /* IVAS modes */ - case IVAS_SID_5k2: - return IVAS_TOC_SID; - case IVAS_13k2: - return IVAS_TOC_13200; - case IVAS_16k4: - return IVAS_TOC_16400; - case IVAS_24k4: - return IVAS_TOC_24400; - case IVAS_32k: - return IVAS_TOC_32000; - case IVAS_48k: - return IVAS_TOC_48000; - case IVAS_64k: - return IVAS_TOC_64000; - case IVAS_80k: - return IVAS_TOC_80000; - case IVAS_96k: - return IVAS_TOC_96000; - case IVAS_128k: - return IVAS_TOC_128000; - case IVAS_160k: - return IVAS_TOC_160000; - case IVAS_192k: - return IVAS_TOC_192000; - case IVAS_256k: - return IVAS_TOC_256000; - case IVAS_384k: - return IVAS_TOC_384000; - case IVAS_512k: - return IVAS_TOC_512000; - default: - break; - } - - return -1; -} - -#endif - /*-------------------------------------------------------------------* * ind_list_realloc() * diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index d711365703..03209f3e26 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -36,9 +36,6 @@ /* options.h needed for debugging/development features * It should be stripped for delivery along with debugging switches */ #include "options.h" -#ifdef IVAS_RTPDUMP -#include -#endif #include #include #include "ivas_error.h" @@ -219,7 +216,7 @@ typedef struct _IVAS_JBM_TRACE_DATA #ifdef IVAS_RTPDUMP typedef struct { - bool PIdataPresent; + int16_t PIdataPresent; char *sceneOrientation; char *deviceOrientationCompensated; char *deviceOrientationUncompensated; @@ -230,9 +227,9 @@ typedef struct typedef struct { - bool sceneOrientationSaved; + int16_t sceneOrientationSaved; IVAS_QUATERNION sceneOrientationQuat; - bool deviceOrientationSaved; + int16_t deviceOrientationSaved; IVAS_QUATERNION deviceOrientationQuat; } PI_DATA_DEPACKER_STATE; #endif diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index f8a2ca10fd..155fa08eb2 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -1758,29 +1758,6 @@ typedef enum } STEREO_DMX_EVS_PRC; -#ifdef IVAS_RTPDUMP -/* "IVAS bit rate" column in table A.3.3.3.2-1 of TS26.253 Annex A */ -typedef enum -{ - IVAS_TOC_13200 = 0x0, // 0000 - IVAS_TOC_16400 = 0x1, // 0001 - IVAS_TOC_24400 = 0x2, // 0010 - IVAS_TOC_32000 = 0x3, // 0011 - IVAS_TOC_48000 = 0x4, // 0100 - IVAS_TOC_64000 = 0x5, // 0101 - IVAS_TOC_80000 = 0x6, // 0110 - IVAS_TOC_96000 = 0x7, // 0111 - IVAS_TOC_128000 = 0x8, // 1000 - IVAS_TOC_160000 = 0x9, // 1001 - IVAS_TOC_192000 = 0xA, // 1010 - IVAS_TOC_256000 = 0xB, // 1011 - IVAS_TOC_384000 = 0xC, // 1100 - IVAS_TOC_512000 = 0xD, // 1101 - IVAS_TOC_RESERVED = 0xE, // 1110 - IVAS_TOC_SID = 0xF // 1111 -} IVAS_TOC_BITRATE_INDEX; - -#endif #endif /* clang-format on */ /* IVAS_CNST_H */ diff --git a/lib_com/ivas_error.h b/lib_com/ivas_error.h index 24b576d696..5a3d3fa787 100644 --- a/lib_com/ivas_error.h +++ b/lib_com/ivas_error.h @@ -153,10 +153,9 @@ typedef enum /*----------------------------------------* * rtp errors * *----------------------------------------*/ - IVAS_ERR_UNDERFLOW = 0x7000, - IVAS_ERR_PI_DATA_WITH_NO_INPUT_FRAME, - IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, - IVAS_ERR_UNPACK_PI_DATA, + IVAS_ERR_RTP_UNDERFLOW = 0x7000, + IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, + IVAS_ERR_RTP_UNPACK_PI_DATA, IVAS_ERR_RTP_UNSUPPORTED_FRAME, /*----------------------------------------* diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index 80232e5800..262d30ca99 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -806,10 +806,6 @@ void ivas_apply_non_diegetic_panning( ); #ifdef IVAS_RTPDUMP -Word16 rate2IVASmode( - const Word32 brate /* i : bitrate */ -); - void QuaternionProduct( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, diff --git a/lib_enc/lib_enc.c b/lib_enc/lib_enc.c index b2ea192118..705f3364e4 100644 --- a/lib_enc/lib_enc.c +++ b/lib_enc/lib_enc.c @@ -2417,25 +2417,6 @@ static void init_encoder_config( return; } -#ifdef IVAS_RTPDUMP - - -/*---------------------------------------------------------------------* - * IVAS_isImmersiveFormat() - * - * Check if the used format is immersive or mono - *---------------------------------------------------------------------*/ - -bool IVAS_isImmersiveFormat( - const IVAS_ENC_HANDLE hIvasEnc ) -{ - if ( hIvasEnc->st_ivas->hEncoderConfig->ivas_format == MONO_FORMAT ) - { - return false; - } - return true; -} -#endif #ifdef DEBUGGING diff --git a/lib_enc/lib_enc.h b/lib_enc/lib_enc.h index 478133db9f..af9f68e07b 100644 --- a/lib_enc/lib_enc.h +++ b/lib_enc/lib_enc.h @@ -383,12 +383,6 @@ void IVAS_ENC_PrintDisclaimer( void ); -#ifdef IVAS_RTPDUMP -/*! r: true if ivas format is not mono */ -bool IVAS_isImmersiveFormat( - const IVAS_ENC_HANDLE hIvasEnc /* i : IVAS encoder handle */ -); -#endif #ifdef DEBUGGING int32_t IVAS_ENC_GetNoCLipping( IVAS_ENC_HANDLE hIvasEnc, /* i : IVAS encoder handle */ diff --git a/lib_util/ivas_bpool.c b/lib_util/ivas_bpool.c index 2bf707cea6..827422fac3 100644 --- a/lib_util/ivas_bpool.c +++ b/lib_util/ivas_bpool.c @@ -117,7 +117,7 @@ ivas_error BPOOL_GetBuffer( BPOOL_HANDLE handle, void **dataPtr ) if ( !isFree ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow, no free buffers in pool" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow, no free buffers in pool" ); } *dataPtr = handle->freeBuffers[idx]; diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index f716f31eee..468dce0d17 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -249,7 +249,7 @@ static ivas_error PackRtpHeader( buf->length = 0; if ( packedLength > buf->capacity ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient output buffer for RTP header packing" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient output buffer for RTP header packing" ); } /* @@ -335,7 +335,7 @@ static ivas_error UnpackRtpPacket( if ( packet->length < expectedSize ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Insufficient input buffer for RTP header unpacking" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Insufficient input buffer for RTP header unpacking" ); } /* @@ -381,7 +381,7 @@ static ivas_error UnpackRtpPacket( expectedSize += ( header->CC ) * 4 + ( header->extension ? 4 : 0 ); if ( packet->length < expectedSize ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "CC indicated but insufficient input buffer" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "CC indicated but insufficient input buffer" ); } /* CSRC Identifiers */ @@ -404,7 +404,7 @@ static ivas_error UnpackRtpPacket( expectedSize += header->numExtUnits * 4; if ( packet->length < expectedSize ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Extension Header indicated but insufficient input buffer" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Extension Header indicated but insufficient input buffer" ); } header->extData = realloc( header->extData, header->numExtUnits * 4 ); @@ -1010,16 +1010,16 @@ ivas_error IVAS_RTP_PACK_PushPiData( return IVAS_ERR_OK; } -#define WRITE_BYTE_PAYLOAD_OR_EXIT( payload, byte ) \ - if ( payload->length < payload->capacity ) \ - { \ - uint8_t _byte = ( byte ); \ - payload->buffer[payload->length++] = _byte; \ - } \ - else \ - { \ - error = IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); \ - goto err_exit; \ +#define WRITE_BYTE_PAYLOAD_OR_EXIT( payload, byte ) \ + if ( payload->length < payload->capacity ) \ + { \ + uint8_t _byte = ( byte ); \ + payload->buffer[payload->length++] = _byte; \ + } \ + else \ + { \ + error = IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); \ + goto err_exit; \ } ivas_error IVAS_RTP_PACK_GetPayload( @@ -1088,7 +1088,7 @@ ivas_error IVAS_RTP_PACK_GetPayload( } else { - error = IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + error = IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); goto err_exit; } payload->length += frameLength; @@ -1465,7 +1465,7 @@ static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBy } else { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during ToC SR byte" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during ToC SR byte" ); } #else /* Reserved bit not expected */ @@ -1529,7 +1529,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes if ( nBytes + 1 >= payload->length ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during expected PI Header read" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during expected PI Header read" ); } piHeader0 = payload->buffer[nBytes++]; @@ -1544,7 +1544,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes piSize += byte; if ( nBytes >= payload->length ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during reading piSize" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during reading piSize" ); } } while ( byte == 255 ); @@ -1552,7 +1552,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes { if ( piSize > 0 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "NO_PI_DATA should be 0 sized" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "NO_PI_DATA should be 0 sized" ); } /* Do not add a node for NO_DATA */ @@ -1577,7 +1577,7 @@ static ivas_error parsePIData( IVAS_RTP_UNPACK_HANDLE hUnpack, uint32_t rtpTimes } else { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during reading pi data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during reading pi data" ); } if ( PM == PI_HEADER_PM_LAST ) @@ -1669,7 +1669,7 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( } else { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during expected frame bits" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "Underflow during expected frame bits" ); } /* Add to frames FiFo */ @@ -1772,7 +1772,7 @@ ivas_error IVAS_RTP_UNPACK_PullFrame( /* Check if a node is available in FiFo */ if ( node == NULL ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "No more frames in unpack fifo" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "No more frames in unpack fifo" ); } length = ( node->toc.auNumBits + 7 ) / 8; @@ -1835,7 +1835,7 @@ ivas_error IVAS_RTP_UNPACK_PullNextPiData( /* Check if a node is available in FiFo */ if ( node == NULL ) { - return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "No more pi data in unpack fifo" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNDERFLOW, "No more pi data in unpack fifo" ); } pi = (IVAS_PIDATA_GENERIC *) &node->data; diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h deleted file mode 100644 index 92cb0e8e71..0000000000 --- a/lib_util/ivas_rtp_payload.h +++ /dev/null @@ -1,403 +0,0 @@ -/****************************************************************************************************** - - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. - -*******************************************************************************************************/ - -#ifndef IVAS_RTP_PAYLOAD_H -#define IVAS_RTP_PAYLOAD_H - -#pragma once -#include -#include -#include -#include "common_api_types.h" -#include "ivas_cnst.h" -#include "rtpdump.h" - -#ifdef IVAS_RTPDUMP - -#ifdef __cplusplus -extern "C" -{ -#endif - - /* clang-format off */ - -static const int32_t IVASmode2rate[16] = { - 13200, - 16400, - 24400, - 32000, - 48000, - 64000, - 80000, - 96000, - 128000, - 160000, - 192000, - 256000, - 384000, - 512000, - -1, /* Reserved */ - 5200 /* SID */ -}; - -typedef enum -{ - PF0 = 0, - PF1 = 1 -} PI_PF; - -typedef enum -{ - PM_RESERVED = 0, - PM_NOT_LAST = 1, - PM_LAST = 2, - PM_GENERAL = 3 -} PI_PM; - -typedef enum -{ - PI_SCENE_ORIENTATION = 0, - PI_DEVICE_ORIENTATION_COMPENSATED = 1, - PI_DEVICE_ORIENTATION_UNCOMPENSATED = 2, - PI_ACOUSTIC_ENVIRONMENT = 3, - PI_NO_DATA = 31 -} PI_TYPE; - -typedef enum -{ - PI_SCENE_ORIENTATION_SIZE_BYTES = 8, - PI_DEVICE_ORIENTATION_COMPENSATED_SIZE_BYTES = 8, - PI_DEVICE_ORIENTATION_UNCOMPENSATED_SIZE_BYTES = 8, - PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES = 1, - PI_ACOUSTIC_ENVIRONMENT_ONLY_LATE_REVERB_SIZE_BYTES = 5, - PI_ACOUSTIC_ENVIRONMENT_LATE_REVERB_AND_EARLY_REFLECTIONS_SIZE_BYTES = 8, - PI_NO_DATA_SIZE_BYTES = 0 -} PI_SIZE_BYTES; - -typedef struct -{ - IVAS_QUATERNION sceneOrientationQuat; - IVAS_QUATERNION deviceOrientationQuat; - bool sceneOrientationPresent; - bool deviceOrientationPresent; -} PI_DATA_PACKER; - -typedef struct -{ - uint16_t w, x, y, z; -} IVAS_QUATERNION_Q15; - -typedef enum -{ - IVAS_CMR_13k2 = 0xF0, // 1111 0000 - IVAS_CMR_16k4 = 0xF1, // 1111 0001 - IVAS_CMR_24k4 = 0xF2, // 1111 0010 - IVAS_CMR_32k = 0xF3, // 1111 0011 - IVAS_CMR_48k = 0xF4, // 1111 0100 - IVAS_CMR_64k = 0xF5, // 1111 0101 - IVAS_CMR_80k = 0xF6, // 1111 0110 - IVAS_CMR_96k = 0xF7, // 1111 0111 - IVAS_CMR_128k = 0xF8, // 1111 1000 - IVAS_CMR_160k = 0xF9, // 1111 1001 - IVAS_CMR_192k = 0xFA, // 1111 1010 - IVAS_CMR_256k = 0xFB, // 1111 1011 - IVAS_CMR_384k = 0xFC, // 1111 1100 - IVAS_CMR_512k = 0xFD, // 1111 1101 - IVAS_CMR_RESERVED = 0xFE, // 1111 1110 - IVAS_CMR_NO_REQ = 0xFF // 1111 1111 -} IVAS_INITIAL_E_BYTE; - -typedef enum -{ - IVAS_BW_REQ_WB = 0x80, // 1000 0000 - IVAS_BW_REQ_SWB = 0x81, // 1000 0001 - IVAS_BW_REQ_FB = 0x82, // 1000 0010 - IVAS_BW_NO_REQ = 0x83, // 1000 0011 - IVAS_FMT_REQ_STEREO = 0xA0, // 1010 0000 - IVAS_FMT_REQ_SBA = 0xA1, // 1010 0001 - IVAS_FMT_REQ_MASA = 0xA2, // 1010 0010 - IVAS_FMT_REQ_ISM = 0xA3, // 1010 0011 - IVAS_FMT_REQ_MC = 0xA4, // 1010 0100 - IVAS_FMT_REQ_OMASA = 0xA5, // 1010 0101 - IVAS_FMT_REQ_OSBA = 0xA6, // 1010 0110 - IVAS_FMT_NO_REQ = 0xA7, // 1010 0111 - IVAS_PI_INDICATION = 0xC0 // 1100 0000 -} IVAS_SUBSEQUENT_E_BYTE; - -typedef enum -{ - WB_REQUEST, - SWB_REQUEST, - FB_REQUEST, - NO_BANDWIDTH_REQUEST -} IVAS_BANDWIDTH_REQUEST; - -typedef enum -{ - STEREO_REQUEST, - SBA_REQUEST, - MASA_REQUEST, - ISM_REQUEST, - MC_REQUEST, - OMASA_REQUEST, - OSBA_REQUEST, - NO_FORMAT_REQUEST -} IVAS_FORMAT_REQUEST; - -typedef enum -{ - E_BYTE = 0x80, // 1xxx xxxx - IVAS_E_CMR = 0xF0, // 1111 xxxx - IVAS_E_BW_REQUEST = 0x80, // 100x xxxx - IVAS_E_FMT_REQUEST = 0xA0, // 101x xxxx - IVAS_E_PI_INDICATION = 0xC0, // 110x xxxx - IVAS_E_RESERVED = 0xE0 // 111x xxxx -} IVAS_E_BYTE_INDICATIONS; - -typedef struct -{ - RTPDUMP_HANDLE rtpdump; - RTPDUMP_RTPPACKET rtpPacket; - uint32_t timeoffset_ms; - uint16_t frameIndex; - bool frameFollowing; - PI_DATA_DEPACKER_STATE PIdataDepackerState; -} IVAS_RTPDUMP_DEPACKER; - -typedef struct -{ - RTPDUMP_HANDLE rtpdump; - RTPDUMP_RTPPACKET rtpPacket; - uint32_t timeoffset_ms; - PI_DATA_PACKER piDataPacker; - bool piDataPresent; - int32_t writingIndex; -} IVAS_RTPDUMP_PACKER; - -typedef enum -{ - IVAS_RTPDUMP_DEPACKER_NO_ERROR = 0, - IVAS_RTPDUMP_DEPACKER_EOF = -1, - IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR = 1, - IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR -} IVAS_RTPDUMP_DEPACKER_ERROR; - -typedef enum -{ - IVAS_RTPDUMP_PACKER_NO_ERROR = 0, - IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR = 1 -} IVAS_RTPDUMP_PACKER_ERROR; - - -/*-----------------------------------------------------------------------* - * ivasPayload_unpackFrame() - * - * Parse a single IVAS frame from the payload. Also read the IVAS payload header - * and parse the PI data associated with the IVAS frame. - *-----------------------------------------------------------------------*/ - -bool ivasPayload_unpackFrame( - char *payload, /* i : RTP payload data */ - uint16_t payloadSizeBytes, /* i : RTP payload size in bytes */ - uint16_t frameIndex, /* i : Index for the data frame in the payload */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - bool *frameFollowing, /* o : (F) bit in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeInBits, /* o : Data frame size in bits */ - PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_open() - * - * Open IVAS rtpdump depacker. - *-----------------------------------------------------------------------*/ - -IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_open( - IVAS_RTPDUMP_DEPACKER *self, /* o : IVAS rtpdump depacker handle */ - FILE *file /* i : Input file containing the rtpdump */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_readNextFrame() - * - * Unpack next frame from the RTP packet. If the packet is depleted, read the next RTP packet from the rtpdump. - *-----------------------------------------------------------------------*/ - -IVAS_RTPDUMP_DEPACKER_ERROR IVAS_RTPDUMP_DEPACKER_readNextFrame( - IVAS_RTPDUMP_DEPACKER *self, /* i/o: IVAS rtpdump depacker handle */ - uint16_t *rtpSequenceNumber, /* o : RTP sequence number of the read packet */ - uint32_t *rtpTimeStamp, /* o : RTP timestamp for the unpacked frame */ - uint32_t *rcvTime_ms, /* o : Time offset in milliseconds for the unpacked frame */ - bool *evsIvasModeBit, /* o : "EVS/IVAS mode bit" in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the "EVS mode bit" separating EVS Primary and AMR-WB IO modes (see A.2.2.1.2 in TS26.445) */ - uint16_t *bitrateIndex, /* o : Bitrate index (BR) in the ToC byte (see A.3.3.3.2 in TS26.253 for IVAS or A.2.2.1.2 in TS26.445 for EVS) */ - bool *ivasIndicatorBit, /* o : "IVAS indicator" bit in the ToC byte (see A.3.3.3.2 in TS26.253). - * In EVS mode, this is the Q-bit (see A.2.2.1.2 in TS26.445) */ - bool *isGoodFrame, /* o : Good frame indicator (set by the Q-bit in AMR-WB IO mode, otherwise always set to true) */ - unsigned char **frame, /* o : Data frame indicated by frameIndex */ - uint16_t *frameSizeBits /* o : Data frame size in bits */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_DEPACKER_close() - * - * Close IVAS rtpdump depacker. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_DEPACKER_close( - IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_open() - * - * Open IVAS rtpdump packer. - *-----------------------------------------------------------------------*/ - -IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_open( - IVAS_RTPDUMP_PACKER **rtpdumpPacker, /* o : IVAS rtpdump packer handle */ - FILE *file /* i : Output file for the rtpdump stream */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeEbytes() - * - * Write E-bytes (CMR and subsequent E-bytes) to the RTP packet. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_writeEbytes( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet for bidirectional signalling */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS). - * Immersive bitrate CMRs follow A.3.3.3.3.2 in TS26.253. Mono bitrate CMRs follow A.2.2.1.1 in TS26.445. */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet for bidirectional signalling */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet for bidirectional signalling */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeToc() - * - * Write a single ToC byte to the RTP packet. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_writeToc( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - int32_t frameBitrate, /* i : Bitrate of the written frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS). - * Immersive format ToCs follow A.3.3.2 in TS26.253. Mono format ToCs follow A.2.2.1.2 in TS26.445.*/ - bool someFrameFollowing /* i : Flag to indicate if another frame follows this frame in the same RTP packet. */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writeNextFrame() - * - * Write IVAS payload header, a single frame data and optional PI data to an RTP packet. - *-----------------------------------------------------------------------*/ - -IVAS_RTPDUMP_PACKER_ERROR IVAS_RTPDUMP_PACKER_writeNextFrame( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - const uint16_t *bitStream, /* i : Bitstream containing the data frame to be written */ - int32_t numBits, /* i : Number of bits contained in the data frame */ - bool isImmersiveFormat, /* i : Flag to indicate if the frame is in immersive format (IVAS) or in mono format (EVS) */ - int32_t requestedBitrate, /* i : Requested bitrate to be written as CMR in the packet */ - bool requestImmersiveFormatBitrate, /* i : Flag to indicate if the requested bitrate is for immersive modes (IVAS) or mono mode (EVS) */ - IVAS_BANDWIDTH_REQUEST ivasBandwidthRequest, /* i : Requested bandwidth to be written as subsequent E-byte in the packet */ - IVAS_FORMAT_REQUEST ivasFormatRequest /* i : Requested format to be written as subsequent E-byte in the packet */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_writePIdata() - * - * Write PI data section (PI headers and PI frame data) to the RTP packet. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_writePIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_determinePIpresence() - * - * Check if there is PI data present for the current frame. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_determinePIpresence( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_resetPIdata() - * - * Reset PI data packer handle. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_resetPIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ -); - - -/*-----------------------------------------------------------------------* - * IVAS_RTPDUMP_PACKER_close() - * - * Close IVAS rtpdump packer. - *-----------------------------------------------------------------------*/ - -void IVAS_RTPDUMP_PACKER_close( - IVAS_RTPDUMP_PACKER **self /* i : IVAS rtpdump packer handle */ -); - - - /* clang-format on */ - -#ifdef __cplusplus -} -#endif - -#endif - -#endif /* IVAS_RTP_PAYLOAD_H */ diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index d3e8774281..f006b82a88 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -30,6 +30,7 @@ *******************************************************************************************************/ +#include #include "ivas_rtp_pi_data.h" #include "ivas_error_utils.h" #include "ivas_rtp_internal.h" @@ -94,7 +95,7 @@ static ivas_error packNoPiData( const IVAS_PIDATA_GENERIC *piData, uint8_t *buff /* NO_PI_DATA is just PI header with no data */ if ( maxDataBytes < 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in PI data buffer for NO_PI_DATA" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in PI data buffer for NO_PI_DATA" ); } buffer[nBytes++] = ( IVAS_PI_NO_DATA ); /* PF/PM populated during final packing */ @@ -110,7 +111,7 @@ static ivas_error unpackNoPiData( const uint8_t *buffer, uint32_t numDataBytes, if ( numDataBytes != 0 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "NO_PI_DATA must be 0 byte" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "NO_PI_DATA must be 0 byte" ); } piData->size = sizeof( IVAS_PIDATA_NO_DATA ); @@ -141,7 +142,7 @@ static ivas_error packOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t *b /* Orientation data is 8 bytes, header is 2 bytes */ if ( maxDataBytes < 8 + 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Orientation PI data" ); } buffer[nBytes++] = ( orientation->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ @@ -162,7 +163,7 @@ static ivas_error unpackOrientation( const uint8_t *buffer, uint32_t numDataByte /* Orientation data is 8 bytes */ if ( numDataBytes != 8 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); } piData->size = sizeof( IVAS_PIDATA_ORIENTATION ); @@ -275,7 +276,7 @@ static ivas_error unpackAcousticEnvironment( const uint8_t *buffer, uint32_t num /* Acooustic Env data is either 1, 5 or 8 bytes */ if ( numDataBytes != 1 && numDataBytes != 5 && numDataBytes != 8 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack PI data of type Acoustic Environment" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack PI data of type Acoustic Environment" ); } piData->size = sizeof( IVAS_PIDATA_ACOUSTIC_ENV ); @@ -338,7 +339,7 @@ static ivas_error packAudioDescription( const IVAS_PIDATA_GENERIC *piData, uint8 /* Audio Description data is max 5 bytes, 2 bytes header */ if ( maxDataBytes < packedSize + 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in Audio Description PI data buffer" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in Audio Description PI data buffer" ); } buffer[nBytes++] = ( IVAS_PI_AUDIO_DESCRIPTION ); /* PF/PM populated during final packing */ @@ -365,7 +366,7 @@ static ivas_error unpackAudioDescription( const uint8_t *buffer, uint32_t numDat /* Audio Description data is max 5 bytes */ if ( numDataBytes > ( IVAS_PI_MAX_OBJECTS + 1 ) ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); } audioDesc->size = sizeof( IVAS_PIDATA_AUDIO_DESC ); @@ -394,7 +395,7 @@ static ivas_error packDynamicSuppression( const IVAS_PIDATA_GENERIC *piData, uin /* Dynamic Audio Suppression data is 2 bytes, 2 bytes header */ if ( maxDataBytes < 2 + 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in DAS PI data buffer" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in DAS PI data buffer" ); } buffer[nBytes++] = ( IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION ); /* PF/PM populated during final packing */ @@ -416,7 +417,7 @@ static ivas_error unpackDynamicSuppression( const uint8_t *buffer, uint32_t numD /* Dynamic Suppression data is 2 bytes */ if ( numDataBytes != 2 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack DAS PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack DAS PI data" ); } das->size = sizeof( IVAS_PIDATA_AUDIO_DESC ); @@ -449,7 +450,7 @@ static ivas_error packListenerPosition( const IVAS_PIDATA_GENERIC *piData, uint8 /* Position data is 6 bytes, header is 2 bytes */ if ( maxDataBytes < 6 + 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack LISTENER POSITION PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack LISTENER POSITION PI data" ); } buffer[nBytes++] = ( listener->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ @@ -468,7 +469,7 @@ static ivas_error unpackListenerPosition( const uint8_t *buffer, uint32_t numDat /* Position data is 6 bytes */ if ( numDataBytes != 6 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack LISTENER POSITION PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack LISTENER POSITION PI data" ); } listener->size = sizeof( IVAS_PIDATA_LISTENER_POSITION ); @@ -500,7 +501,7 @@ static ivas_error packDiegetic( const IVAS_PIDATA_GENERIC *piData, uint8_t *buff /* Diegetic data is 1 bytes, header is 2 bytes */ if ( maxDataBytes < 1 + 2 ) { - return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Diegetic Type PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space to pack Diegetic Type PI data" ); } /* Valid bits must be based on active bits defined for the input format */ @@ -527,7 +528,7 @@ static ivas_error unpackDiegetic( const uint8_t *buffer, uint32_t numDataBytes, /* Diegetic data is 1 bytes */ if ( numDataBytes != 1 ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Incorrect size to unpack Diegetic PI data" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Diegetic PI data" ); } diegetic->size = sizeof( IVAS_PIDATA_DIEGETIC ); @@ -710,7 +711,7 @@ ivas_error PI_UnPackData( uint8_t piDataType, uint32_t piSize, const uint8_t *pi /* Sanitize maximum sizes for each PI Type */ if ( piSize > maxPiDataSize[piDataType] ) { - return IVAS_ERROR( IVAS_ERR_UNPACK_PI_DATA, "Max size for PI Data type exceeded" ); + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Max size for PI Data type exceeded" ); } error = unpackPiDataFuntions[piDataType]( piDataBuffer, piSize, piData ); @@ -722,4 +723,33 @@ ivas_error PI_UnPackData( uint8_t piDataType, uint32_t piSize, const uint8_t *pi return IVAS_ERR_OK; } +/* PIDATA Tables */ +const float mapRT60[1u << NBITS_RT60] = { + 0.01f, 0.0126f, 0.0159f, 0.02f, 0.0252f, 0.0317f, 0.04f, 0.0504f, + 0.0635f, 0.08f, 0.1008f, 0.1270f, 0.16f, 0.2016f, 0.2540f, 0.32f, + 0.4032f, 0.5080f, 0.64f, 0.8063f, 1.0159f, 1.28f, 1.6127f, 2.0319f, + 2.56f, 3.2254f, 4.0637f, 5.12f, 6.4508f, 8.1275f, 10.24f, 12.9016f +}; + +const float mapDSR[1u << NBITS_DSR] = { + -20.f, -21.f, -22.f, -23.f, -24.f, -25.f, -26.f, -27.f, + -28.f, -29.f, -30.f, -31.f, -32.f, -33.f, -34.f, -35.f, + -36.f, -37.f, -38.f, -39.f, -40.f, -41.f, -42.f, -43.f, + -44.f, -45.f, -46.f, -47.f, -48.f, -49.f, -50.f, -51.f, + -52.f, -53.f, -54.f, -55.f, -56.f, -57.f, -58.f, -59.f, + -60.f, -61.f, -62.f, -63.f, -64.f, -65.f, -66.f, -67.f, + -68.f, -69.f, -70.f, -71.f, -72.f, -73.f, -74.f, -75.f, + -76.f, -77.f, -78.f, -79.f, -80.f, -81.f, -82.f, -83.f +}; + +const float mapRoomDims[1u << NBITS_DIM] = { + 0.5f, 0.707f, 1.f, 1.4141f, 2, 2.8282f, 4.f, 5.6568f, + 8.f, 11.314f, 16.f, 22.627f, 32.f, 45.255f, 64.f, 90.51f +}; + +const float mapAbsorbtion[1u << NBITS_ABS] = { + 0.0800f, 0.1656f, 0.3430f, 0.7101f +}; + + #endif /* IVAS_RTPDUMP */ diff --git a/lib_util/ivas_rtp_rom.c b/lib_util/ivas_rtp_rom.c deleted file mode 100644 index 016e6ce34e..0000000000 --- a/lib_util/ivas_rtp_rom.c +++ /dev/null @@ -1,64 +0,0 @@ -/****************************************************************************************************** - - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. - -*******************************************************************************************************/ - -#include "ivas_rtp_internal.h" - -#ifdef IVAS_RTPDUMP - -const float mapRT60[1u << NBITS_RT60] = { - 0.01f, 0.0126f, 0.0159f, 0.02f, 0.0252f, 0.0317f, 0.04f, 0.0504f, - 0.0635f, 0.08f, 0.1008f, 0.1270f, 0.16f, 0.2016f, 0.2540f, 0.32f, - 0.4032f, 0.5080f, 0.64f, 0.8063f, 1.0159f, 1.28f, 1.6127f, 2.0319f, - 2.56f, 3.2254f, 4.0637f, 5.12f, 6.4508f, 8.1275f, 10.24f, 12.9016f -}; - -const float mapDSR[1u << NBITS_DSR] = { - -20.f, -21.f, -22.f, -23.f, -24.f, -25.f, -26.f, -27.f, - -28.f, -29.f, -30.f, -31.f, -32.f, -33.f, -34.f, -35.f, - -36.f, -37.f, -38.f, -39.f, -40.f, -41.f, -42.f, -43.f, - -44.f, -45.f, -46.f, -47.f, -48.f, -49.f, -50.f, -51.f, - -52.f, -53.f, -54.f, -55.f, -56.f, -57.f, -58.f, -59.f, - -60.f, -61.f, -62.f, -63.f, -64.f, -65.f, -66.f, -67.f, - -68.f, -69.f, -70.f, -71.f, -72.f, -73.f, -74.f, -75.f, - -76.f, -77.f, -78.f, -79.f, -80.f, -81.f, -82.f, -83.f -}; - -const float mapRoomDims[1u << NBITS_DIM] = { - 0.5f, 0.707f, 1.f, 1.4141f, 2, 2.8282f, 4.f, 5.6568f, - 8.f, 11.314f, 16.f, 22.627f, 32.f, 45.255f, 64.f, 90.51f -}; - -const float mapAbsorbtion[1u << NBITS_ABS] = { - 0.0800f, 0.1656f, 0.3430f, 0.7101f -}; - -#endif /* IVAS_RTPDUMP */ -- GitLab From 6b196ffc73ce9c29d27f39a46ea016b85fdf45a1 Mon Sep 17 00:00:00 2001 From: vaclav Date: Wed, 10 Sep 2025 12:02:40 +0200 Subject: [PATCH 099/340] update --- Workspace_msvc/lib_dec.vcxproj | 1 - Workspace_msvc/lib_dec.vcxproj.filters | 3 - Workspace_msvc/lib_rend.vcxproj | 1 + Workspace_msvc/lib_rend.vcxproj.filters | 3 + lib_dec/lib_dec.c | 69 ++++++++++++++----- .../ivas_cldfb_ring_buffer.c | 25 ++++--- lib_rend/lib_rend.c | 3 +- 7 files changed, 70 insertions(+), 35 deletions(-) rename {lib_dec => lib_rend}/ivas_cldfb_ring_buffer.c (92%) diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index a8639d192c..3c7ea3e597 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -206,7 +206,6 @@ - diff --git a/Workspace_msvc/lib_dec.vcxproj.filters b/Workspace_msvc/lib_dec.vcxproj.filters index e081cb0870..8eddbb60a0 100644 --- a/Workspace_msvc/lib_dec.vcxproj.filters +++ b/Workspace_msvc/lib_dec.vcxproj.filters @@ -515,9 +515,6 @@ decoder_all_c - - decoder_ivas_c - diff --git a/Workspace_msvc/lib_rend.vcxproj b/Workspace_msvc/lib_rend.vcxproj index 854c99a979..1d55ed1942 100644 --- a/Workspace_msvc/lib_rend.vcxproj +++ b/Workspace_msvc/lib_rend.vcxproj @@ -138,6 +138,7 @@ + diff --git a/Workspace_msvc/lib_rend.vcxproj.filters b/Workspace_msvc/lib_rend.vcxproj.filters index 2d1d7d46c0..f290958973 100644 --- a/Workspace_msvc/lib_rend.vcxproj.filters +++ b/Workspace_msvc/lib_rend.vcxproj.filters @@ -119,6 +119,9 @@ rend_c + + rend_c + diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 8fa078af78..8a437c4c4f 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -130,8 +130,8 @@ static ivas_error ivas_create_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *h static void ivas_destroy_handle_isar( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE *hSplitBinRend_out ); static int16_t get_render_frame_size_ms( IVAS_RENDER_FRAMESIZE render_framesize ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP -static int16_t get_render_frame_size_samples( Decoder_Struct *st_ivas ); -static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct *st_ivas ); +static int16_t get_render_frame_size_samples( const DECODER_CONFIG_HANDLE hDecoderConfig ); +static int16_t ivas_dec_split_rend_cldfb_in( const RENDERER_TYPE renderer_type ); #endif static void update_voip_rendered20ms( IVAS_DEC_HANDLE hIvasDec, const int16_t nSamplesRendered ); @@ -641,13 +641,23 @@ ivas_error IVAS_DEC_GetRenderFramesize( return IVAS_ERR_OK; } + #ifdef FIX_1119_SPLIT_RENDERING_VOIP -static int16_t get_render_frame_size_samples( Decoder_Struct *st_ivas ) +/*---------------------------------------------------------------------* + * get_render_frame_size_samples( ) + * + * + *---------------------------------------------------------------------*/ + +static int16_t get_render_frame_size_samples( + const DECODER_CONFIG_HANDLE hDecoderConfig /* i : configuration structure */ +) { - return (int16_t) ( st_ivas->hDecoderConfig->output_Fs * st_ivas->hDecoderConfig->render_framesize / ( FRAMES_PER_SEC * IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ) ); + return (int16_t) ( hDecoderConfig->output_Fs * hDecoderConfig->render_framesize / ( FRAMES_PER_SEC * IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ) ); } #endif + /*---------------------------------------------------------------------* * IVAS_DEC_GetGetRenderFramesizeSamples( ) * @@ -665,7 +675,7 @@ ivas_error IVAS_DEC_GetRenderFramesizeSamples( } #ifdef FIX_1119_SPLIT_RENDERING_VOIP - *render_framesize = get_render_frame_size_samples( hIvasDec->st_ivas ); + *render_framesize = get_render_frame_size_samples( hIvasDec->st_ivas->hDecoderConfig ); #else *render_framesize = (int16_t) ( hIvasDec->st_ivas->hDecoderConfig->output_Fs * hIvasDec->st_ivas->hDecoderConfig->render_framesize / ( FRAMES_PER_SEC * IVAS_MAX_PARAM_SPATIAL_SUBFRAMES ) ); #endif @@ -673,6 +683,7 @@ ivas_error IVAS_DEC_GetRenderFramesizeSamples( return IVAS_ERR_OK; } + /*---------------------------------------------------------------------* * IVAS_DEC_GetGetRenderFramesizeMs( ) * @@ -1904,6 +1915,7 @@ static ivas_error isar_render_poses( ) { float pcmBuf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES * L_FRAME48k]; + Decoder_Struct *st_ivas; ivas_error error; int16_t i, j, numPoses; @@ -1914,7 +1926,9 @@ static ivas_error isar_render_poses( *needNewFrame = false; - numPoses = hIvasDec->st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; + st_ivas = hIvasDec->st_ivas; + + numPoses = st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; /* init flush buffer for rate switch if not already initizalized */ if ( hIvasDec->flushbuffer == NULL ) @@ -1939,9 +1953,9 @@ static ivas_error isar_render_poses( return IVAS_ERR_OK; } - if ( !ivas_dec_split_rend_cldfb_in( hIvasDec->st_ivas ) ) + if ( !ivas_dec_split_rend_cldfb_in( st_ivas->renderer_type ) ) { - ivas_TD_RINGBUF_PushInterleaved( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, pcmBuf, *nOutSamples ); + ivas_TD_RINGBUF_PushInterleaved( st_ivas->hSplitBinRend->hMultiBinTdData, pcmBuf, *nOutSamples ); } /* change buffer layout */ @@ -3792,6 +3806,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples } #endif + /* make sure that the FIFO after decoder/scaler contains at least one sound card frame (i.e. 20ms) */ while ( *nSamplesRendered < nSamplesPerChannel ) { @@ -4003,7 +4018,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( hIvasDec->hasDecodedFirstGoodFrame && splitRendBits != NULL ) { - if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) + if ( !ivas_dec_split_rend_cldfb_in( st_ivas->renderer_type ) ) { ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nSamplesRendered ); } @@ -4083,7 +4098,7 @@ ivas_error IVAS_DEC_VoIP_GetSamples( /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_GetSplitBinauralBitstream( ) * - * Main function to decode one split-rendering frame in VoIP + * Main function to decode one split-rendering frame in VoIP *---------------------------------------------------------------------*/ /*! r: error code */ @@ -5456,17 +5471,33 @@ static ivas_error ivas_dec_reconfig_split_rend( return IVAS_ERR_OK; } + #ifdef FIX_1119_SPLIT_RENDERING_VOIP -static int16_t ivas_dec_split_rend_cldfb_in( Decoder_Struct *st_ivas ) -{ +/*-------------------------------------------------------------------* + * ivas_dec_split_rend_cldfb_in() + * + * + *-------------------------------------------------------------------*/ - return st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || - st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || - st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC || - st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC_ROOM; +static int16_t ivas_dec_split_rend_cldfb_in( + const RENDERER_TYPE renderer_type /* i : renderer type */ +) +{ + if ( renderer_type == RENDERER_BINAURAL_FASTCONV || + renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || + renderer_type == RENDERER_BINAURAL_PARAMETRIC || + renderer_type == RENDERER_BINAURAL_PARAMETRIC_ROOM ) + { + return 1; + } + else + { + return 0; + } } #endif + /*-------------------------------------------------------------------* * ivas_dec_init_split_rend() * @@ -5488,7 +5519,7 @@ static ivas_error ivas_dec_init_split_rend( cldfb_in_flag = 0; #ifdef FIX_1119_SPLIT_RENDERING_VOIP - cldfb_in_flag = ivas_dec_split_rend_cldfb_in( st_ivas ); + cldfb_in_flag = ivas_dec_split_rend_cldfb_in( st_ivas->renderer_type ); #else if ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM || @@ -5501,6 +5532,7 @@ static ivas_error ivas_dec_init_split_rend( #ifdef FIX_1119_SPLIT_RENDERING_VOIP ISAR_PRE_REND_GetMultiBinPoseData( &st_ivas->hRenderConfig->split_rend_config, &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, ( st_ivas->hHeadTrackData != NULL ) ? st_ivas->hHeadTrackData->sr_pose_pred_axis : DEFAULT_AXIS ); + num_poses = st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses; assert( num_poses <= MAX_HEAD_ROT_POSES ); @@ -5517,8 +5549,7 @@ static ivas_error ivas_dec_init_split_rend( } else { - error = ivas_TD_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinTdData, get_render_frame_size_samples( st_ivas ), num_poses * BINAURAL_CHANNELS ); - if ( error != IVAS_ERR_OK ) + if ( ( error = ivas_TD_RINGBUF_Open( &st_ivas->hSplitBinRend->hMultiBinTdData, get_render_frame_size_samples( st_ivas->hDecoderConfig ), num_poses * BINAURAL_CHANNELS ) ) != IVAS_ERR_OK ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for split rendering structure" ); } diff --git a/lib_dec/ivas_cldfb_ring_buffer.c b/lib_rend/ivas_cldfb_ring_buffer.c similarity index 92% rename from lib_dec/ivas_cldfb_ring_buffer.c rename to lib_rend/ivas_cldfb_ring_buffer.c index ec4a30be0f..5d1d2a055c 100644 --- a/lib_dec/ivas_cldfb_ring_buffer.c +++ b/lib_rend/ivas_cldfb_ring_buffer.c @@ -47,12 +47,12 @@ *---------------------------------------------------------------------*/ /*---------------------------------------------------------------------* - * ivas_CLDFB_RINGBUF_IsEmpty() + * ivas_cldfb_ringbuf_IsEmpty() * * Returns 1 if the ring buffer is empty, or 0 otherwise. *---------------------------------------------------------------------*/ -static int16_t ivas_CLDFB_RINGBUF_IsEmpty( +static int16_t ivas_cldfb_ringbuf_IsEmpty( ISAR_CLDFB_RINGBUF_HANDLE h ) { return (int16_t) ( h->read_pos == h->write_pos && !h->is_full ); @@ -60,12 +60,12 @@ static int16_t ivas_CLDFB_RINGBUF_IsEmpty( /*---------------------------------------------------------------------* - * ivas_CLDFB_RINGBUF_IsFull() + * ivas_cldfb_ringbuf_IsFull() * * Returns 1 if the ring buffer is full, or 0 otherwise. *---------------------------------------------------------------------*/ -static int16_t ivas_CLDFB_RINGBUF_IsFull( +static int16_t ivas_cldfb_ringbuf_IsFull( ISAR_CLDFB_RINGBUF_HANDLE h ) { return h->is_full; @@ -167,7 +167,7 @@ void ivas_CLDFB_RINGBUF_Push( const int16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !ivas_CLDFB_RINGBUF_IsFull( h ) ); + assert( !ivas_cldfb_ringbuf_IsFull( h ) ); mvr2r( real, &h->real[h->write_pos], num_bands ); mvr2r( imag, &h->imag[h->write_pos], num_bands ); @@ -200,7 +200,7 @@ void ivas_CLDFB_RINGBUF_Pop( const int16_t num_bands ) { assert( num_bands <= CLDFB_NO_CHANNELS_MAX ); - assert( !ivas_CLDFB_RINGBUF_IsEmpty( h ) ); + assert( !ivas_cldfb_ringbuf_IsEmpty( h ) ); if ( real != NULL ) { @@ -223,11 +223,16 @@ void ivas_CLDFB_RINGBUF_Pop( } -/* Returns total number of buffered samples (including number of channels) */ -static uint32_t ivas_CLDFB_RINGBUF_total_size( +/*---------------------------------------------------------------------* + * ivas_cldfb_ringbuf_total_size() + * + * Returns total number of buffered samples (including number of channels) + *---------------------------------------------------------------------*/ + +static uint32_t ivas_cldfb_ringbuf_total_size( ISAR_CLDFB_RINGBUF_HANDLE h ) { - if ( ivas_CLDFB_RINGBUF_IsFull( h ) ) + if ( ivas_cldfb_ringbuf_IsFull( h ) ) { return h->capacity; } @@ -260,7 +265,7 @@ void ivas_CLDFB_RINGBUF_GetByIdx( const int16_t col_idx ) { int32_t idx = col_idx * CLDFB_NO_CHANNELS_MAX; - int32_t num_floats = (int32_t) ivas_CLDFB_RINGBUF_total_size( h ); + int32_t num_floats = (int32_t) ivas_cldfb_ringbuf_total_size( h ); uint32_t offset, uidx; assert( -num_floats <= idx && idx <= num_floats ); diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index a0d737fce2..8a16ff77b8 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -1480,7 +1480,7 @@ static ivas_error alignInputDelay( int32_t numSamplesToPush, numSamplesToPop; uint32_t ringBufferSize, preDelay; #ifdef FIX_1119_SPLIT_RENDERING_VOIP - int32_t i; + int16_t i; const float *p_read_channels[MAX_INPUT_CHANNELS]; float *p_write_channels[MAX_INPUT_CHANNELS]; #endif @@ -7864,7 +7864,6 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( IVAS_REND_AudioBufferConfig *pSplitEncBufConfig; ISAR_SPLIT_REND_CONFIG_HANDLE pSplitRendConfig; ISAR_SPLIT_REND_BITS_DATA bits; - #ifdef FIX_1119_SPLIT_RENDERING_VOIP float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; -- GitLab From 75b97ed518dc9cf96b36afee5dfe89c50bb1e303 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Wed, 10 Sep 2025 21:28:13 +1000 Subject: [PATCH 100/340] code review Format, fixcmdline for self_test.prn is not followed by number Signed-off-by: Ripinder Singh --- lib_com/ivas_error.h | 16 + lib_util/ivas_bpool.h | 3 +- lib_util/ivas_queue.h | 2 +- lib_util/ivas_rtp_api.h | 1064 +++++++++++++++++----------------- lib_util/ivas_rtp_file.c | 2 +- lib_util/ivas_rtp_file.h | 14 +- lib_util/ivas_rtp_pi_data.c | 16 +- scripts/config/self_test.prm | 18 +- 8 files changed, 569 insertions(+), 566 deletions(-) diff --git a/lib_com/ivas_error.h b/lib_com/ivas_error.h index 66fa262d37..4a5249934c 100644 --- a/lib_com/ivas_error.h +++ b/lib_com/ivas_error.h @@ -296,6 +296,22 @@ static inline const char *ivas_error_to_string( ivas_error error_code ) { return "data error"; } + if ( ( error_code & 0x7000 ) == 0x7000 ) + { + switch ( error_code ) + { + case IVAS_ERR_RTP_UNDERFLOW: + return "RTP Undeflow in reading frame/packet"; + case IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE: + return "Output buffer size is insufficient"; + case IVAS_ERR_RTP_UNPACK_PI_DATA: + return "Unpacking PI data failure"; + case IVAS_ERR_RTP_UNSUPPORTED_FRAME: + return "Unsupported RTP frame"; + default: + return "rtp error"; + } + } return "Unknown error"; } diff --git a/lib_util/ivas_bpool.h b/lib_util/ivas_bpool.h index ae8f114127..30cf4962b5 100644 --- a/lib_util/ivas_bpool.h +++ b/lib_util/ivas_bpool.h @@ -33,9 +33,8 @@ #ifndef IVAS_BPOOL_H #define IVAS_BPOOL_H -#include #include -#include "ivas_error.h" +#include "common_api_types.h" /* Forward declaraiton of opaque buffer pool handle */ typedef struct BPOOL *BPOOL_HANDLE; diff --git a/lib_util/ivas_queue.h b/lib_util/ivas_queue.h index 9f2a9a856c..1c6b77504e 100644 --- a/lib_util/ivas_queue.h +++ b/lib_util/ivas_queue.h @@ -34,7 +34,7 @@ #define IVAS_QUEUE_H #include -#include "ivas_error.h" +#include "common_api_types.h" typedef struct NODE { diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 29ed397beb..06c56cd70f 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -33,47 +33,39 @@ #ifndef IVAS_RTP_API_H #define IVAS_RTP_API_H -#pragma once #include #include -#include -#include "options.h" -#include "ivas_error.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - /* - * +-----------------------+---------------------+--------------------+----------+ - * | RTP Header (+ HDREXT) | payload header | frame data | PI data | - * +-----------------------+---------------------+--------------------+----------+ - * \--------------------\ /------------------------------/ - * IVAS payload - * - * This api provides a mechanism to generate/unpack the IVAS payload. The RTP Header - * and header extension fields must be handled by caller. - * - * IVAS General Payload structure - * =============================== - * - * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 - * H H H H F H - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |1| T | D |1| ET1 |x x x x|1| ET2 |x x x x|0|1|0 1| BR |1| ET3 |x x x x|… - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * \--------------/\--------------/\--------------/\--------------/\--------------/ - * Initial E byte Subsqnt Ebyte1 Subsqnt Ebyte2 ToC1 Subsqnt Ebyte3 - * - * H F - * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ - * …|0|0|0 1| BR | IVAS frame 1 ... | IVAS frame 2 ... |PI data| - * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ - * \--------------/ - * ToC2 - * - */ +#include "common_api_types.h" + +/* + * +-----------------------+---------------------+--------------------+----------+ + * | RTP Header (+ HDREXT) | payload header | frame data | PI data | + * +-----------------------+---------------------+--------------------+----------+ + * \--------------------\ /------------------------------/ + * IVAS payload + * + * This api provides a mechanism to generate/unpack the IVAS payload. The RTP Header + * and header extension fields must be handled by caller. + * + * IVAS General Payload structure + * =============================== + * + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * H H H H F H + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1| T | D |1| ET1 |x x x x|1| ET2 |x x x x|0|1|0 1| BR |1| ET3 |x x x x|… + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \--------------/\--------------/\--------------/\--------------/\--------------/ + * Initial E byte Subsqnt Ebyte1 Subsqnt Ebyte2 ToC1 Subsqnt Ebyte3 + * + * H F + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * …|0|0|0 1| BR | IVAS frame 1 ... | IVAS frame 2 ... |PI data| + * +-+-+-+-+-+-+-+-+-------------------------- ---+-------------------- ---+-------+ + * \--------------/ + * ToC2 + * + */ #define IVAS_MAX_FRAMES_PER_RTP_PACKET ( 8 ) /* Max supported frames per RTP packet */ @@ -88,516 +80,512 @@ extern "C" #define NO_BITRATE_REQ ( 0u ) /* If no bitrate is requested from remote */ - /* IVAS Codec Types */ - typedef enum - { - IVAS_RTP_EVS, /* EVS */ - IVAS_RTP_IVAS /* IVAS */ - } IVAS_RTP_CODEC; - - /* IVAS Bandwidth Requests */ - typedef enum - { - IVAS_BANDWIDTH_NB, /* Narrowband */ - IVAS_BANDWIDTH_WB, /* Wideband*/ - IVAS_BANDWIDTH_SWB, /* SuperWideband*/ - IVAS_BANDWIDTH_FB, /* Fullband */ - IVAS_BANDWIDTH_NO_REQ, /* No Preference */ - } IVAS_RTP_BANDWIDTH; - - /* Channel Aware Coding */ - typedef enum - { - IVAS_RTP_CA_LO_O2, /* FER=LO, OFFSET=2 */ - IVAS_RTP_CA_LO_O3, /* FER=LO, OFFSET=3 */ - IVAS_RTP_CA_LO_O5, /* FER=LO, OFFSET=5 */ - IVAS_RTP_CA_LO_O7, /* FER=LO, OFFSET=7 */ - IVAS_RTP_CA_HI_O2, /* FER=HI, OFFSET=2 */ - IVAS_RTP_CA_HI_O3, /* FER=HI, OFFSET=3 */ - IVAS_RTP_CA_HI_O5, /* FER=HI, OFFSET=5 */ - IVAS_RTP_CA_HI_O7, /* FER=HI, OFFSET=7 */ - IVAS_RTP_CA_NO_REQ /* No request */ - } IVAS_RTP_CA_MODE; - - /* Coded Format Requests */ - typedef enum - { - IVAS_FMT_STEREO, /* Stereo */ - IVAS_FMT_SBA, /* Scene Based Audio */ - IVAS_FMT_MASA, /* Metadata Assisted Spatial Audio */ - IVAS_FMT_ISM, /* Object Based Audio */ - IVAS_FMT_MC, /* Multichannel Audio */ - IVAS_FMT_OMASA, /* Object + MASA */ - IVAS_FMT_OSBA, /* Object + SBA */ - IVAS_FMT_NO_REQ, /* No preference */ - } IVAS_RTP_FORMAT; +/* IVAS Codec Types */ +typedef enum +{ + IVAS_RTP_EVS, /* EVS */ + IVAS_RTP_IVAS /* IVAS */ +} IVAS_RTP_CODEC; + +/* IVAS Bandwidth Requests */ +typedef enum +{ + IVAS_BANDWIDTH_NB, /* Narrowband */ + IVAS_BANDWIDTH_WB, /* Wideband*/ + IVAS_BANDWIDTH_SWB, /* SuperWideband*/ + IVAS_BANDWIDTH_FB, /* Fullband */ + IVAS_BANDWIDTH_NO_REQ, /* No Preference */ +} IVAS_RTP_BANDWIDTH; + +/* Channel Aware Coding */ +typedef enum +{ + IVAS_RTP_CA_LO_O2, /* FER=LO, OFFSET=2 */ + IVAS_RTP_CA_LO_O3, /* FER=LO, OFFSET=3 */ + IVAS_RTP_CA_LO_O5, /* FER=LO, OFFSET=5 */ + IVAS_RTP_CA_LO_O7, /* FER=LO, OFFSET=7 */ + IVAS_RTP_CA_HI_O2, /* FER=HI, OFFSET=2 */ + IVAS_RTP_CA_HI_O3, /* FER=HI, OFFSET=3 */ + IVAS_RTP_CA_HI_O5, /* FER=HI, OFFSET=5 */ + IVAS_RTP_CA_HI_O7, /* FER=HI, OFFSET=7 */ + IVAS_RTP_CA_NO_REQ /* No request */ +} IVAS_RTP_CA_MODE; + +/* Coded Format Requests */ +typedef enum +{ + IVAS_FMT_STEREO, /* Stereo */ + IVAS_FMT_SBA, /* Scene Based Audio */ + IVAS_FMT_MASA, /* Metadata Assisted Spatial Audio */ + IVAS_FMT_ISM, /* Object Based Audio */ + IVAS_FMT_MC, /* Multichannel Audio */ + IVAS_FMT_OMASA, /* Object + MASA */ + IVAS_FMT_OSBA, /* Object + SBA */ + IVAS_FMT_NO_REQ, /* No preference */ +} IVAS_RTP_FORMAT; #ifdef RTP_S4_251135_CR26253_0016_REV1 - /* Coded Subformat Requests */ - typedef enum - { - IVAS_SUBFMT_FOA_PLANAR, - IVAS_SUBFMT_HOA2_PLANAR, - IVAS_SUBFMT_HOA3_PLANAR, - IVAS_SUBFMT_FOA, - IVAS_SUBFMT_HOA2, - IVAS_SUBFMT_HOA3, - IVAS_SUBFMT_MASA1, - IVAS_SUBFMT_MASA2, - IVAS_SUBFMT_ISM1, - IVAS_SUBFMT_ISM2, - IVAS_SUBFMT_ISM3, - IVAS_SUBFMT_ISM4, - IVAS_SUBFMT_ISM1_EXTENDED_METADATA, - IVAS_SUBFMT_ISM2_EXTENDED_METADATA, - IVAS_SUBFMT_ISM3_EXTENDED_METADATA, - IVAS_SUBFMT_ISM4_EXTENDED_METADATA, - IVAS_SUBFMT_MC_5_1, - IVAS_SUBFMT_MC_7_1, - IVAS_SUBFMT_MC_5_1_2, - IVAS_SUBFMT_MC_5_1_4, - IVAS_SUBFMT_MC_7_1_4, - IVAS_SUBFMT_RESERVED_21, - IVAS_SUBFMT_RESERVED_22, - IVAS_SUBFMT_RESERVED_23, - IVAS_SUBFMT_RESERVED_24, - IVAS_SUBFMT_RESERVED_25, - IVAS_SUBFMT_RESERVED_26, - IVAS_SUBFMT_RESERVED_27, - IVAS_SUBFMT_RESERVED_28, - IVAS_SUBFMT_RESERVED_29, - IVAS_SUBFMT_RESERVED_30, - IVAS_SUBFMT_RESERVED_31, - IVAS_SUBFMT_OMASA_ISM1_1TC, - IVAS_SUBFMT_OMASA_ISM2_1TC, - IVAS_SUBFMT_OMASA_ISM3_1TC, - IVAS_SUBFMT_OMASA_ISM4_1TC, - IVAS_SUBFMT_OMASA_ISM1_2TC, - IVAS_SUBFMT_OMASA_ISM2_2TC, - IVAS_SUBFMT_OMASA_ISM3_2TC, - IVAS_SUBFMT_OMASA_ISM4_2TC, - IVAS_SUBFMT_OSBA_ISM1_FOA_PLANAR, - IVAS_SUBFMT_OSBA_ISM2_FOA_PLANAR, - IVAS_SUBFMT_OSBA_ISM3_FOA_PLANAR, - IVAS_SUBFMT_OSBA_ISM4_FOA_PLANAR, - IVAS_SUBFMT_OSBA_ISM1_FOA, - IVAS_SUBFMT_OSBA_ISM2_FOA, - IVAS_SUBFMT_OSBA_ISM3_FOA, - IVAS_SUBFMT_OSBA_ISM4_FOA, - IVAS_SUBFMT_OSBA_ISM1_HOA2_PLANAR, - IVAS_SUBFMT_OSBA_ISM2_HOA2_PLANAR, - IVAS_SUBFMT_OSBA_ISM3_HOA2_PLANAR, - IVAS_SUBFMT_OSBA_ISM4_HOA2_PLANAR, - IVAS_SUBFMT_OSBA_ISM1_HOA2, - IVAS_SUBFMT_OSBA_ISM2_HOA2, - IVAS_SUBFMT_OSBA_ISM3_HOA2, - IVAS_SUBFMT_OSBA_ISM4_HOA2, - IVAS_SUBFMT_OSBA_ISM1_HOA3_PLANAR, - IVAS_SUBFMT_OSBA_ISM2_HOA3_PLANAR, - IVAS_SUBFMT_OSBA_ISM3_HOA3_PLANAR, - IVAS_SUBFMT_OSBA_ISM4_HOA3_PLANAR, - IVAS_SUBFMT_OSBA_ISM1_HOA3, - IVAS_SUBFMT_OSBA_ISM2_HOA3, - IVAS_SUBFMT_OSBA_ISM3_HOA3, - IVAS_SUBFMT_OSBA_ISM4_HOA3, - IVAS_SUBFMT_NO_REQ - } IVAS_RTP_SUBFORMAT; - - /* Split Rendering Requests */ - typedef struct - { - uint32_t valid : 1; /* is split rendering request valid */ - uint32_t diegetic : 1; /* enabling diegetic support for Split Rendering */ - uint32_t yaw : 1; /* transmission metadata for correction around yaw axis */ - uint32_t pitch : 1; /* transmission metadata for correction around pitch axis */ - uint32_t roll : 1; /* transmission metadata for correction around roll axis */ - uint32_t reserved : 27; /* reserved */ - } IVAS_RTP_SPLITRENDER; +/* Coded Subformat Requests */ +typedef enum +{ + IVAS_SUBFMT_FOA_PLANAR, + IVAS_SUBFMT_HOA2_PLANAR, + IVAS_SUBFMT_HOA3_PLANAR, + IVAS_SUBFMT_FOA, + IVAS_SUBFMT_HOA2, + IVAS_SUBFMT_HOA3, + IVAS_SUBFMT_MASA1, + IVAS_SUBFMT_MASA2, + IVAS_SUBFMT_ISM1, + IVAS_SUBFMT_ISM2, + IVAS_SUBFMT_ISM3, + IVAS_SUBFMT_ISM4, + IVAS_SUBFMT_ISM1_EXTENDED_METADATA, + IVAS_SUBFMT_ISM2_EXTENDED_METADATA, + IVAS_SUBFMT_ISM3_EXTENDED_METADATA, + IVAS_SUBFMT_ISM4_EXTENDED_METADATA, + IVAS_SUBFMT_MC_5_1, + IVAS_SUBFMT_MC_7_1, + IVAS_SUBFMT_MC_5_1_2, + IVAS_SUBFMT_MC_5_1_4, + IVAS_SUBFMT_MC_7_1_4, + IVAS_SUBFMT_RESERVED_21, + IVAS_SUBFMT_RESERVED_22, + IVAS_SUBFMT_RESERVED_23, + IVAS_SUBFMT_RESERVED_24, + IVAS_SUBFMT_RESERVED_25, + IVAS_SUBFMT_RESERVED_26, + IVAS_SUBFMT_RESERVED_27, + IVAS_SUBFMT_RESERVED_28, + IVAS_SUBFMT_RESERVED_29, + IVAS_SUBFMT_RESERVED_30, + IVAS_SUBFMT_RESERVED_31, + IVAS_SUBFMT_OMASA_ISM1_1TC, + IVAS_SUBFMT_OMASA_ISM2_1TC, + IVAS_SUBFMT_OMASA_ISM3_1TC, + IVAS_SUBFMT_OMASA_ISM4_1TC, + IVAS_SUBFMT_OMASA_ISM1_2TC, + IVAS_SUBFMT_OMASA_ISM2_2TC, + IVAS_SUBFMT_OMASA_ISM3_2TC, + IVAS_SUBFMT_OMASA_ISM4_2TC, + IVAS_SUBFMT_OSBA_ISM1_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_FOA_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_FOA, + IVAS_SUBFMT_OSBA_ISM2_FOA, + IVAS_SUBFMT_OSBA_ISM3_FOA, + IVAS_SUBFMT_OSBA_ISM4_FOA, + IVAS_SUBFMT_OSBA_ISM1_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_HOA2_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_HOA2, + IVAS_SUBFMT_OSBA_ISM2_HOA2, + IVAS_SUBFMT_OSBA_ISM3_HOA2, + IVAS_SUBFMT_OSBA_ISM4_HOA2, + IVAS_SUBFMT_OSBA_ISM1_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM2_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM3_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM4_HOA3_PLANAR, + IVAS_SUBFMT_OSBA_ISM1_HOA3, + IVAS_SUBFMT_OSBA_ISM2_HOA3, + IVAS_SUBFMT_OSBA_ISM3_HOA3, + IVAS_SUBFMT_OSBA_ISM4_HOA3, + IVAS_SUBFMT_NO_REQ +} IVAS_RTP_SUBFORMAT; + +/* Split Rendering Requests */ +typedef struct +{ + uint32_t valid : 1; /* is split rendering request valid */ + uint32_t diegetic : 1; /* enabling diegetic support for Split Rendering */ + uint32_t yaw : 1; /* transmission metadata for correction around yaw axis */ + uint32_t pitch : 1; /* transmission metadata for correction around pitch axis */ + uint32_t roll : 1; /* transmission metadata for correction around roll axis */ + uint32_t reserved : 27; /* reserved */ +} IVAS_RTP_SPLITRENDER; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ - /* Remote Requests Types (Keys) */ - typedef enum - { - IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODEC */ - IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ - IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ - IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ - IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ +/* Remote Requests Types (Keys) */ +typedef enum +{ + IVAS_REQUEST_CODEC, /* Request codec type, value of type IVAS_RTP_CODEC */ + IVAS_REQUEST_BITRATE, /* Request bitrate, value of type uint32_t in kbps */ + IVAS_REQUEST_BANDWIDTH, /* Request bandwidth, value of type IVAS_RTP_BANDWIDTH */ + IVAS_REQUEST_FORMAT, /* Request format, value of type IVAS_RTP_FORMAT */ + IVAS_REQUEST_CA_MODE, /* Request channel awareness, value of type IVAS_RTP_CA_MODE */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ - IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_REQUEST_MAX /* Max number of requests */ - } IVAS_RTP_REQUEST_TYPE; - - /* Remote Request Values */ - typedef union - { - uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ - IVAS_RTP_CODEC codec; /* codec id when request type is IVAS_REQUEST_CODEC */ - IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ - IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ - IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ + IVAS_REQUEST_SUBFORMAT, /* Request subFormat, value of type IVAS_RTP_SUBFORMAT */ + IVAS_REQUEST_SR_CONFIG, /* Request spit rendering, value of type IVAS_RTP_SPLITRENDER */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_REQUEST_MAX /* Max number of requests */ +} IVAS_RTP_REQUEST_TYPE; + +/* Remote Request Values */ +typedef union +{ + uint32_t bitrate; /* bitrate in kbps when request type is IVAS_REQUEST_BITRATE */ + IVAS_RTP_CODEC codec; /* codec id when request type is IVAS_REQUEST_CODEC */ + IVAS_RTP_BANDWIDTH bandwidth; /* badwidth when request type is IVAS_REQUEST_BANDWIDTH */ + IVAS_RTP_FORMAT formatType; /* format type when request type is IVAS_REQUEST_FORMAT */ + IVAS_RTP_CA_MODE caMode; /* channel aware mode when request type is IVAS_REQUEST_CA_MODE */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ - IVAS_RTP_SPLITRENDER srConfig; /* split rendering config when request type is IVAS_REQUEST_SR_CONFIG */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - } IVAS_RTP_REQUEST_VALUE; - - /* Template for pi data types, all defined pi data follow this template - * for example scene orientation pi data can be represented as :- - * - * typedef struct { - * size_t size; // sizeof(IVAS_PIDATA_SCENE_ORIENTATION) - * uint32_t piDataType; // IVAS_PI_SCENE_ORIENTATION - * float w, x, y, z; // pi data of scene orientation in quaternions - * } IVAS_PIDATA_SCENE_ORIENTATION; - * - */ - typedef struct - { - size_t size; /* size of this structure */ - uint32_t piDataType; /* IVAS PI data type */ - uint8_t data[1]; /* Variable length array */ - } IVAS_PIDATA_GENERIC; - - /* Generic data buffer for sending/receiving coded frames / rtp payloads - * data buffer is owned and initialized by caller, rtp api will ensure - * buffer write does not exceed capacity. - */ - typedef struct - { - size_t capacity; /* allocated size of the data buffer */ - size_t length; /* length of the initialized data in the buffer */ - uint8_t *buffer; /* pointer to the payload buffer */ - } IVAS_DATA_BUFFER; + IVAS_RTP_SUBFORMAT subFormatType; /* sub-format type when request type is IVAS_REQUEST_SUBFORMAT */ + IVAS_RTP_SPLITRENDER srConfig; /* split rendering config when request type is IVAS_REQUEST_SR_CONFIG */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ +} IVAS_RTP_REQUEST_VALUE; + +/* Template for pi data types, all defined pi data follow this template + * for example scene orientation pi data can be represented as :- + * + * typedef struct { + * size_t size; // sizeof(IVAS_PIDATA_SCENE_ORIENTATION) + * uint32_t piDataType; // IVAS_PI_SCENE_ORIENTATION + * float w, x, y, z; // pi data of scene orientation in quaternions + * } IVAS_PIDATA_SCENE_ORIENTATION; + * + */ +typedef struct +{ + size_t size; /* size of this structure */ + uint32_t piDataType; /* IVAS PI data type */ + uint8_t data[1]; /* Variable length array */ +} IVAS_PIDATA_GENERIC; + +/* Generic data buffer for sending/receiving coded frames / rtp payloads + * data buffer is owned and initialized by caller, rtp api will ensure + * buffer write does not exceed capacity. + */ +typedef struct +{ + size_t capacity; /* allocated size of the data buffer */ + size_t length; /* length of the initialized data in the buffer */ + uint8_t *buffer; /* pointer to the payload buffer */ +} IVAS_DATA_BUFFER; #ifdef RTP_S4_251135_CR26253_0016_REV1 - typedef enum - { - IVAS_SR_TRANSPORT_LCLD, - IVAS_SR_TRANSPORT_LC3PLUS - } IVAS_RTP_SR_TRANSPORT; - - typedef struct - { - bool valid; /* Valid Split Rendering Info for/in the ToC */ - bool diegetic; /* SR content digetic */ - IVAS_RTP_SR_TRANSPORT codec; /* SR Transport Codec used*/ - } IVAS_RTP_SR_INFO; +typedef enum +{ + IVAS_SR_TRANSPORT_LCLD, + IVAS_SR_TRANSPORT_LC3PLUS +} IVAS_RTP_SR_TRANSPORT; + +typedef struct +{ + bool valid; /* Valid Split Rendering Info for/in the ToC */ + bool diegetic; /* SR content digetic */ + IVAS_RTP_SR_TRANSPORT codec; /* SR Transport Codec used*/ +} IVAS_RTP_SR_INFO; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ - /**********************************************/ - /* IVAS RTP PACKER API */ - /**********************************************/ - - /* Forward declaration of rtp pack/unpack handle types */ - typedef struct IVAS_RTP_PACK *IVAS_RTP_PACK_HANDLE; /* rtp packer handle type */ - - /* Initial configuration for rtp packer - * - maxFramesPerPacket is used to define if more than one frame should be packed - * in the same rtp packet. If zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET. - */ - typedef struct - { - uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ - } IVAS_RTP_PACK_CONFIG; - - /* Open an instance of the RTP packer and return a handle to rtp packer on success - * error code is retured on failure and handle is set to NULL - */ - ivas_error IVAS_RTP_PACK_Open( - IVAS_RTP_PACK_HANDLE *phIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ - const IVAS_RTP_PACK_CONFIG *config /* i : pointer to initial config for RTP Packer */ - ); - - /* Close and free an existing instance of rtp packer */ - void IVAS_RTP_PACK_Close( - IVAS_RTP_PACK_HANDLE *phIvasPack /* i/o : pointer to an IVAS rtp packer handle to be closed */ - ); - - /* Update the RTP Header structure */ - ivas_error IVAS_RTP_PACK_UpdateHeader( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ - uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ - uint8_t numCC, /* i : numCC indicates no. of contributing sources */ - uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ - uint16_t extHeaderId, /* i : extension header ID */ - uint16_t numExtensionBytes, /* i : length of the extension data */ - uint8_t *extData /* i : extension data pointer */ - ); - - /* Add requests for remote sender using a key value pair api - * each key must be provided with a corresponding value type - * - * Cross validation of some key,value pairs will not be done - * in this API. E.g. Codec ID and supported bitrates/bandwidths - * will not be performed at this level. - */ - ivas_error IVAS_RTP_PACK_PushRemoteRequest( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_RTP_REQUEST_TYPE reqType, /* i : remote request type */ - IVAS_RTP_REQUEST_VALUE reqValue /* i : value of the requested type */ - ); - - /* Push a single IVAS/EVS frame to rtp packer - * - * If multiple frames per RTP packet are desired, multiple frames must be explicitly - * pushed before a call to IVAS_RTP_PACK_GetPayload to generate a rtp payload. - * - * It is possible to have variable frames per packet until maxFramesPerPacket frames - * if IVAS_RTP_PACK_GetPayload is invoked asyncronously w.r.t this api. - * - */ - ivas_error IVAS_RTP_PACK_PushFrame( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ +/**********************************************/ +/* IVAS RTP PACKER API */ +/**********************************************/ + +/* Forward declaration of rtp pack/unpack handle types */ +typedef struct IVAS_RTP_PACK *IVAS_RTP_PACK_HANDLE; /* rtp packer handle type */ + +/* Initial configuration for rtp packer + * - maxFramesPerPacket is used to define if more than one frame should be packed + * in the same rtp packet. If zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET. + */ +typedef struct +{ + uint32_t maxFramesPerPacket; /* maximum no of frame per packet desired during the session */ +} IVAS_RTP_PACK_CONFIG; + +/* Open an instance of the RTP packer and return a handle to rtp packer on success + * error code is retured on failure and handle is set to NULL + */ +ivas_error IVAS_RTP_PACK_Open( + IVAS_RTP_PACK_HANDLE *phIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + const IVAS_RTP_PACK_CONFIG *config /* i : pointer to initial config for RTP Packer */ +); + +/* Close and free an existing instance of rtp packer */ +void IVAS_RTP_PACK_Close( + IVAS_RTP_PACK_HANDLE *phIvasPack /* i/o : pointer to an IVAS rtp packer handle to be closed */ +); + +/* Update the RTP Header structure */ +ivas_error IVAS_RTP_PACK_UpdateHeader( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ + uint8_t numCC, /* i : numCC indicates no. of contributing sources */ + uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ + uint16_t extHeaderId, /* i : extension header ID */ + uint16_t numExtensionBytes, /* i : length of the extension data */ + uint8_t *extData /* i : extension data pointer */ +); + +/* Add requests for remote sender using a key value pair api + * each key must be provided with a corresponding value type + * + * Cross validation of some key,value pairs will not be done + * in this API. E.g. Codec ID and supported bitrates/bandwidths + * will not be performed at this level. + */ +ivas_error IVAS_RTP_PACK_PushRemoteRequest( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE reqType, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE reqValue /* i : value of the requested type */ +); + +/* Push a single IVAS/EVS frame to rtp packer + * + * If multiple frames per RTP packet are desired, multiple frames must be explicitly + * pushed before a call to IVAS_RTP_PACK_GetPayload to generate a rtp payload. + * + * It is possible to have variable frames per packet until maxFramesPerPacket frames + * if IVAS_RTP_PACK_GetPayload is invoked asyncronously w.r.t this api. + * + */ +ivas_error IVAS_RTP_PACK_PushFrame( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info (NULL if absent) */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ - ); - - /* Get the number of frames in the FiFo currently */ - uint32_t IVAS_RTP_PACK_GetNumFrames( - IVAS_RTP_PACK_HANDLE hIvasPack /* i/o : IVAS rtp packer handle */ - ); - - /* Push single PI data to rtp packer - * - * Provide PI data for a current RTP packet. All PI data is locally cached in the packer - * and set to the rtp payload with policy defined in initial configuration during call to - * IVAS_RTP_PACK_GetPayload. - * - */ - ivas_error IVAS_RTP_PACK_PushPiData( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ - ); - - /* Generate a rtp payload using available pushed frames - * - * Available remote requests, pi data and frames will be packed into a rtp payload. The - * capacity field of payload is used to limits the maximum RTP packet size. - * - */ - ivas_error IVAS_RTP_PACK_GetPayload( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_DATA_BUFFER *payload, /* o : encapsulated rtp payload */ - uint32_t *numFramesInPayload /* o : no. of frames in payload */ - ); - - /* Generate a rtp packet using available pushed frames - * - * Available remote requests, pi data and frames will be packed into a rtp packet. If no - * frame is pushed before call to this api, NO_DATA_FRAME will be generated - * Takes care of updates to the RTP Header - * - */ - ivas_error IVAS_RTP_PACK_GetPacket( - IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ - IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ - uint32_t *numFramesInPacket /* o : no. of frames in packet */ - ); - - /**********************************************/ - /* IVAS RTP UNPACKER API */ - /**********************************************/ - - /* Forward declaration of rtp unpack handle types */ - typedef struct IVAS_RTP_UNPACK *IVAS_RTP_UNPACK_HANDLE; /* rtp unpacker handle type */ - - /* Initial configuration for rtp unpacker - * - maxFramesPerPacket is used to define maximum supported frames per rtp packet - * to allow for internal memory allocaton, if zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET - */ - - typedef struct - { - uint32_t maxFramesPerPacket; /* maximum no of frame per packet expected during the session */ - } IVAS_RTP_UNPACK_CONFIG; - - /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success - * error code is retured on failure and handle is set to NULL - */ - ivas_error IVAS_RTP_UNPACK_Open( - IVAS_RTP_UNPACK_HANDLE *phIvasUnpack, /* i/o : rtp unpacker handle */ - const IVAS_RTP_UNPACK_CONFIG *config /* i : initial configuration for rtp unpacker */ - ); - - /* Close and free an existing instance of rtp unpacker */ - void IVAS_RTP_UNPACK_Close( - IVAS_RTP_UNPACK_HANDLE *phIvasUnpack /* i/o : IVAS rtp unpacker handle */ - ); - - /* Push a received rtp Ivas Payload to unpacker to extract number of frames, pi data and - * any remote request present in the payload. Caller must extract RTP header and header - * extension and feed Ivas Payload alongwith RTP Timestamp and sequence number. - * - * In case of DTX transmission modes, the number of frames in packet will be reduced by - * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall - * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. - * - * It is important to ensure IVAS_RTP_UNPACK_PushPayload, IVAS_RTP_UNPACK_PullFrame and - * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread - * safe manner else a race condition can arise if new packet is pushed while frames/pidata - * are still being pulled out. The IVAS_RTP_UNPACK_PushPayload will gererate an error if - * new paylod is pushed before all frames/pidata are pulled out. - * - * Example usage : - - * ================== - * err = IVAS_RTP_UNPACK_PushPayload(hIvasUnpack, payload, rtpTs, seqNum, - * &nFrames, &nPiData, &reqBitmap); - * if (err != IVAS_ERR_OK) { return err; } - * - * // Read the frames in payload and feed to decoder - * while (nFrames-- > 0) { - * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); - * if (err != IVAS_ERR_OK) { return err; } - * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); - * if (err != IVAS_ERR_OK) { return err; } - * } - * - * // Read PI Data - * while (nPiData-- > 0) { - * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); - * if (err != IVAS_ERR_OK) { return err; } - * // handle pi data based on fwd/rev pi data types - * handlePIData(&piData, piTs) - * } - * - * // Read remote requests - * for (req = 0; req < IVAS_REQUEST_MAX; req++) { - * if (reqBitmap & (1u << req)) { - * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); - * if (err != IVAS_ERR_OK) { return err; } - * switch(req) { - * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; - * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; - * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; - * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; - * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; - * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; - * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; - * } - * } - * } - * - */ - ivas_error IVAS_RTP_UNPACK_PushPayload( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ - uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ - uint16_t sequenceNumber, /* i : sequence number from rtp header */ - uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ - uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ - uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ - ); - - /* Push a received rtp Ivas Packet to unpacker to extract number of frames, pi data and - * any remote request present in the Packet. - * - * In case of DTX transmission modes, the number of frames in packet will be reduced by - * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall - * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. - * - * It is important to ensure IVAS_RTP_UNPACK_PushPacket, IVAS_RTP_UNPACK_PullFrame and - * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread - * safe manner else a race condition can arise if new packet is pushed while frames/pidata - * are still being pulled out. The IVAS_RTP_UNPACK_PushPacket will gererate an error if - * new packet is pushed before all frames/pidata are pulled out. - * - * Example usage : - - * ================== - * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, &nFrames, &nPiData, &reqBitmap); - * if (err != IVAS_ERR_OK) { return err; } - * - * // Read the frames in packet and feed to decoder - * while (nFrames-- > 0) { - * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); - * if (err != IVAS_ERR_OK) { return err; } - * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); - * if (err != IVAS_ERR_OK) { return err; } - * } - * - * // Read PI Data - * while (nPiData-- > 0) { - * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); - * if (err != IVAS_ERR_OK) { return err; } - * // handle pi data based on fwd/rev pi data types - * handlePIData(&piData, piTs) - * } - * - * // Read remote requests - * for (req = 0; req < IVAS_REQUEST_MAX; req++) { - * if (reqBitmap & (1u << req)) { - * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); - * if (err != IVAS_ERR_OK) { return err; } - * switch(req) { - * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; - * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; - * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; - * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; - * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; - * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; - * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; - * } - * } - * } - */ - ivas_error IVAS_RTP_UNPACK_PushPacket( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - const IVAS_DATA_BUFFER *packet, /* i : received rtp Packet */ - uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ - uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ - uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ - ); - - /* Fetch requests from sender using a key value pair api - * each key must be provided with a corresponding value storage type - * - * On call to IVAS_RTP_UNPACK_PushPayload(), remoteRequestBitmap can be used - * an indicator of new request available this frame - * - */ - ivas_error IVAS_RTP_UNPACK_GetRequest( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp packer handle */ - IVAS_RTP_REQUEST_TYPE type, /* i : remote request type */ - IVAS_RTP_REQUEST_VALUE *value /* o : pointer of the requested type */ - ); - - /* Extract a single IVAS/EVS frame from provided rtp payload alongwith rtp timestamp - * and sequence number - * - * If multiple frames per RTP packet are available, multiple calls to IVAS_RTP_UNPACK_PullFrame - * are needed. - */ - ivas_error IVAS_RTP_UNPACK_PullFrame( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ + IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info (NULL if absent) */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ +); + +/* Get the number of frames in the FiFo currently */ +uint32_t IVAS_RTP_PACK_GetNumFrames( + IVAS_RTP_PACK_HANDLE hIvasPack /* i/o : IVAS rtp packer handle */ +); + +/* Push single PI data to rtp packer + * + * Provide PI data for a current RTP packet. All PI data is locally cached in the packer + * and set to the rtp payload with policy defined in initial configuration during call to + * IVAS_RTP_PACK_GetPayload. + * + */ +ivas_error IVAS_RTP_PACK_PushPiData( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + const IVAS_PIDATA_GENERIC *data /* i : pointer to the PIData stucture */ +); + +/* Generate a rtp payload using available pushed frames + * + * Available remote requests, pi data and frames will be packed into a rtp payload. The + * capacity field of payload is used to limits the maximum RTP packet size. + * + */ +ivas_error IVAS_RTP_PACK_GetPayload( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *payload, /* o : encapsulated rtp payload */ + uint32_t *numFramesInPayload /* o : no. of frames in payload */ +); + +/* Generate a rtp packet using available pushed frames + * + * Available remote requests, pi data and frames will be packed into a rtp packet. If no + * frame is pushed before call to this api, NO_DATA_FRAME will be generated + * Takes care of updates to the RTP Header + * + */ +ivas_error IVAS_RTP_PACK_GetPacket( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ + IVAS_DATA_BUFFER *packet, /* o : encapsulated rtp packet */ + uint32_t *numFramesInPacket /* o : no. of frames in packet */ +); + +/**********************************************/ +/* IVAS RTP UNPACKER API */ +/**********************************************/ + +/* Forward declaration of rtp unpack handle types */ +typedef struct IVAS_RTP_UNPACK *IVAS_RTP_UNPACK_HANDLE; /* rtp unpacker handle type */ + +/* Initial configuration for rtp unpacker + * - maxFramesPerPacket is used to define maximum supported frames per rtp packet + * to allow for internal memory allocaton, if zero, will use IVAS_MAX_FRAMES_PER_RTP_PACKET + */ + +typedef struct +{ + uint32_t maxFramesPerPacket; /* maximum no of frame per packet expected during the session */ +} IVAS_RTP_UNPACK_CONFIG; + +/* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success + * error code is retured on failure and handle is set to NULL + */ +ivas_error IVAS_RTP_UNPACK_Open( + IVAS_RTP_UNPACK_HANDLE *phIvasUnpack, /* i/o : rtp unpacker handle */ + const IVAS_RTP_UNPACK_CONFIG *config /* i : initial configuration for rtp unpacker */ +); + +/* Close and free an existing instance of rtp unpacker */ +void IVAS_RTP_UNPACK_Close( + IVAS_RTP_UNPACK_HANDLE *phIvasUnpack /* i/o : IVAS rtp unpacker handle */ +); + +/* Push a received rtp Ivas Payload to unpacker to extract number of frames, pi data and + * any remote request present in the payload. Caller must extract RTP header and header + * extension and feed Ivas Payload alongwith RTP Timestamp and sequence number. + * + * In case of DTX transmission modes, the number of frames in packet will be reduced by + * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall + * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. + * + * It is important to ensure IVAS_RTP_UNPACK_PushPayload, IVAS_RTP_UNPACK_PullFrame and + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread + * safe manner else a race condition can arise if new packet is pushed while frames/pidata + * are still being pulled out. The IVAS_RTP_UNPACK_PushPayload will gererate an error if + * new paylod is pushed before all frames/pidata are pulled out. + * + * Example usage : - + * ================== + * err = IVAS_RTP_UNPACK_PushPayload(hIvasUnpack, payload, rtpTs, seqNum, + * &nFrames, &nPiData, &reqBitmap); + * if (err != IVAS_ERR_OK) { return err; } + * + * // Read the frames in payload and feed to decoder + * while (nFrames-- > 0) { + * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); + * if (err != IVAS_ERR_OK) { return err; } + * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); + * if (err != IVAS_ERR_OK) { return err; } + * } + * + * // Read PI Data + * while (nPiData-- > 0) { + * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); + * if (err != IVAS_ERR_OK) { return err; } + * // handle pi data based on fwd/rev pi data types + * handlePIData(&piData, piTs) + * } + * + * // Read remote requests + * for (req = 0; req < IVAS_REQUEST_MAX; req++) { + * if (reqBitmap & (1u << req)) { + * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); + * if (err != IVAS_ERR_OK) { return err; } + * switch(req) { + * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; + * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; + * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; + * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; + * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; + * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; + * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; + * } + * } + * } + * + */ +ivas_error IVAS_RTP_UNPACK_PushPayload( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *payload, /* i : received rtp payload */ + uint32_t timestamp, /* i : timestamp in RTP Clock @ 16KHz from rtp header */ + uint16_t sequenceNumber, /* i : sequence number from rtp header */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ +); + +/* Push a received rtp Ivas Packet to unpacker to extract number of frames, pi data and + * any remote request present in the Packet. + * + * In case of DTX transmission modes, the number of frames in packet will be reduced by + * the number of NO_DATA frame received. All PullFrame calls for non NO_DATA frames shall + * be reported with timestamp jump indicating missing/NO_DATA IVAS frames. + * + * It is important to ensure IVAS_RTP_UNPACK_PushPacket, IVAS_RTP_UNPACK_PullFrame and + * IVAS_RTP_UNPACK_PullNextPiData API are invoked in same thread context or in a thread + * safe manner else a race condition can arise if new packet is pushed while frames/pidata + * are still being pulled out. The IVAS_RTP_UNPACK_PushPacket will gererate an error if + * new packet is pushed before all frames/pidata are pulled out. + * + * Example usage : - + * ================== + * err = IVAS_RTP_UNPACK_PushPacket(hIvasUnpack, packet, &nFrames, &nPiData, &reqBitmap); + * if (err != IVAS_ERR_OK) { return err; } + * + * // Read the frames in packet and feed to decoder + * while (nFrames-- > 0) { + * err = IVAS_RTP_UNPACK_PullFrame(hIvasUnpack, &recCodecId, &srInfo, &frame, &frameTs, &seqNum, &SpeechLostIndicated); + * if (err != IVAS_ERR_OK) { return err; } + * err = IVAS_DEC_VoIP_FeedFrame(hIvasDec, frame.buffer, frame.length, seqNum, frameTs, rcvTime, isGoodFrame); + * if (err != IVAS_ERR_OK) { return err; } + * } + * + * // Read PI Data + * while (nPiData-- > 0) { + * err = IVAS_RTP_UNPACK_PullNextPiData(hIvasUnpack, &piData, &piTs); + * if (err != IVAS_ERR_OK) { return err; } + * // handle pi data based on fwd/rev pi data types + * handlePIData(&piData, piTs) + * } + * + * // Read remote requests + * for (req = 0; req < IVAS_REQUEST_MAX; req++) { + * if (reqBitmap & (1u << req)) { + * err = IVAS_RTP_UNPACK_GetRequest(hIvasUnpack, req, &value); + * if (err != IVAS_ERR_OK) { return err; } + * switch(req) { + * case IVAS_REQUEST_CODEC : handleCodec(value.codec); break; + * case IVAS_REQUEST_BITRATE : handleBitrate(value.bitrate); break; + * case IVAS_REQUEST_BANDWIDTH : handleBandwidth(value.bandwidth); break; + * case IVAS_REQUEST_FORMAT : handleFormat(value.formatType); break; + * case IVAS_REQUEST_SUBFORMAT : handleSubFormat(value.subFormatType); break; + * case IVAS_REQUEST_CA_MODE : handleCAModevalue.caMode); break; + * case IVAS_REQUEST_SR_CONFIG : handleSRConfig(value.srConfig); break; + * } + * } + * } + */ +ivas_error IVAS_RTP_UNPACK_PushPacket( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + const IVAS_DATA_BUFFER *packet, /* i : received rtp Packet */ + uint32_t *numFramesInPacket, /* o : number of IVAS/EVS frames in rtp packet */ + uint32_t *numPiDataInPacket, /* o : number of PI data received in rtp packet */ + uint32_t *remoteRequestBitmap /* o : bitmap of available request in this packet */ +); + +/* Fetch requests from sender using a key value pair api + * each key must be provided with a corresponding value storage type + * + * On call to IVAS_RTP_UNPACK_PushPayload(), remoteRequestBitmap can be used + * an indicator of new request available this frame + * + */ +ivas_error IVAS_RTP_UNPACK_GetRequest( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp packer handle */ + IVAS_RTP_REQUEST_TYPE type, /* i : remote request type */ + IVAS_RTP_REQUEST_VALUE *value /* o : pointer of the requested type */ +); + +/* Extract a single IVAS/EVS frame from provided rtp payload alongwith rtp timestamp + * and sequence number + * + * If multiple frames per RTP packet are available, multiple calls to IVAS_RTP_UNPACK_PullFrame + * are needed. + */ +ivas_error IVAS_RTP_UNPACK_PullFrame( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_RTP_CODEC *receivedCodecId, /* o : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ - int16_t *frameSizeInBits, /* o : exact frame size in bits (AMRWB IO) */ - uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ - uint16_t *sequenceNumber, /* o : sequence number from rtp header */ - bool *speechLostIndicated, /* o : Is current frame indicated as Lost */ - bool *isAMRWB_IOmode /* o : Is AMRWB_IO mode EVS frame */ - ); - - /* Pull a single PI data from rtp unpacker instance for current packet - * Each Pi data is accompanied with a corresponding timestamp - */ - ivas_error IVAS_RTP_UNPACK_PullNextPiData( - IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ - IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ - size_t capacity, /* i : capacity of pi data buffer in bytes */ - uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ - ); - -#ifdef __cplusplus -} -#endif + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering Info */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_DATA_BUFFER *frameBuffer, /* o : packed frame bitstream for IVAS/EVS */ + int16_t *frameSizeInBits, /* o : exact frame size in bits (AMRWB IO) */ + uint32_t *timestamp, /* o : timestamp in RTP Clock @ 16KHz */ + uint16_t *sequenceNumber, /* o : sequence number from rtp header */ + bool *speechLostIndicated, /* o : Is current frame indicated as Lost */ + bool *isAMRWB_IOmode /* o : Is AMRWB_IO mode EVS frame */ +); + +/* Pull a single PI data from rtp unpacker instance for current packet + * Each Pi data is accompanied with a corresponding timestamp + */ +ivas_error IVAS_RTP_UNPACK_PullNextPiData( + IVAS_RTP_UNPACK_HANDLE hIvasUnpack, /* i/o : IVAS rtp unpacker handle */ + IVAS_PIDATA_GENERIC *data, /* o : output data buffer for the Pi data */ + size_t capacity, /* i : capacity of pi data buffer in bytes */ + uint32_t *timestamp /* o : timestamp in RTP Clock @ 16KHz */ +); #endif /* IVAS_RTP_API_H */ diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 0112fdfb54..0851a998f6 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -77,7 +77,7 @@ ivas_error IvasRtpFile_Close( ( *phReader )->f_rtpstream = NULL; } free( *phReader ); - *phReader = NULL; + *phReader = NULL; } return IVAS_ERR_OK; diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 47d545208a..3156c34cb2 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -33,29 +33,29 @@ #ifndef IVAS_RTP_FILE_H #define IVAS_RTP_FILE_H -#include #include -#include "ivas_error.h" +#include +#include "common_api_types.h" typedef struct IVAS_RTP_FILE *IVAS_RTP_FILE_HANDLE; -ivas_error IvasRtpFile_Open ( +ivas_error IvasRtpFile_Open( const char *filePath, /* i : path to CA config file */ bool isFileWriter, /* i : instance is a file writer else reader */ IVAS_RTP_FILE_HANDLE *phRtpFile /* o : pointer to an IVAS file reader handle */ ); -ivas_error IvasRtpFile_Close ( - IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ +ivas_error IvasRtpFile_Close( + IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ ); -ivas_error IvasRtpFile_Write ( +ivas_error IvasRtpFile_Write( IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ const uint8_t *packet, /* i : pointer to packed rtp packet */ size_t numBytes /* i : length of the packet in bytes */ ); -ivas_error IvasRtpFile_Read ( +ivas_error IvasRtpFile_Read( IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ uint8_t *packet, /* o : buffer where packet is to be written */ size_t *numBytes, /* o : length of the packet in bytes */ diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index f006b82a88..e7dcde8998 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -555,7 +555,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #ifdef RTP_S4_251135_CR26253_0016_REV1 packAudioDescription, /* AUDIO_DESCRIPTION */ #else - packUnsupportedData, /* AUDIO_DESCRIPTION */ + packUnsupportedData, /* AUDIO_DESCRIPTION */ #endif /* RTP_S4_251135_CR26253_0016_REV1 */ packUnsupportedData, /* ISM_NUM */ packUnsupportedData, /* ISM_ID */ @@ -567,7 +567,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #ifdef RTP_S4_251135_CR26253_0016_REV1 packDiegetic, /* DIEGETIC_TYPE */ #else - packUnsupportedData, /* DIEGETIC_TYPE */ + packUnsupportedData, /* DIEGETIC_TYPE */ #endif packUnsupportedData, /* RESERVED13 */ packUnsupportedData, /* RESERVED14 */ @@ -579,11 +579,11 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ packOrientation, /* AUDIO_FOCUS_DIRECTION */ #else - packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ - packUnsupportedData, /* HEAD_ORIENTATION */ - packUnsupportedData, /* LISTENER_POSITION */ - packUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ - packUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ + packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ + packUnsupportedData, /* HEAD_ORIENTATION */ + packUnsupportedData, /* LISTENER_POSITION */ + packUnsupportedData, /* DYNAMIC_AUDIO_SUPPRESSION */ + packUnsupportedData, /* AUDIO_FOCUS_DIRECTION */ #endif packUnsupportedData, /* PI_LATENCY */ packUnsupportedData, /* R_ISM_ID */ @@ -591,7 +591,7 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { #ifdef RTP_S4_251135_CR26253_0016_REV1 packOrientation, /* R_ISM_ORIENTATION */ #else - packUnsupportedData, /* R_ISM_ORIENTATION */ + packUnsupportedData, /* R_ISM_ORIENTATION */ #endif packUnsupportedData, /* R_ISM_POSITION */ packUnsupportedData, /* R_ISM_DIRECTION */ diff --git a/scripts/config/self_test.prm b/scripts/config/self_test.prm index 7f857487b9..7bfe33e4de 100644 --- a/scripts/config/self_test.prm +++ b/scripts/config/self_test.prm @@ -307,7 +307,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT.tst // stereo bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, EXT out, rtpdump -../IVAS_cod -rtpdump -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit +../IVAS_cod -rtpdump 3 -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit ../IVAS_dec -VOIP_hf_only=1 EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT_rtpdump.tst @@ -584,7 +584,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec BINAURAL_ROOM_REVERB 48 bit testv/stv4ISM48n.wav_BINAURAL_ROOM_REVERB_128000_48-48.tst // 4 ISM with metadata bitrate switching from 24.4 kbps to 512 kbps, 48 kHz in, 48 kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case00_3000_q.csv -dtx -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_24k4_512k.bin 48 testv/stv4ISM48s.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case00_3000_q.csv -dtx -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_24k4_512k.bin 48 testv/stv4ISM48s.wav bit ../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv4ISM48s.wav_brate_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst @@ -965,7 +965,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec -render_config testv/rend_config_recreation.cfg BINAURAL_ROOM_REVERB 48 bit testv/stv3OA48c.wav_BINAURAL_ROOM_REVERB_96000_48-48_custom_configuration.tst // SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit ../IVAS_dec -VOIP_hf_only=1 FOA 48 bit testv/stvFOA48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst @@ -1248,7 +1248,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec -render_config testv/rend_config_combined.cfg -t testv/headrot.csv BINAURAL_ROOM_REVERB 48 bit testv/stv1MASA1TC48c.wav_BINAURAL_ROOM_REVERB_256000_48-48_Headrot_custom_config.tst // MASA 2dir 2TC bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot_case03_3000_q.csv -device_orientation testv/headrot.csv -dtx -masa 2 testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv2MASA2TC48c.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case03_3000_q.csv -device_orientation testv/headrot.csv -dtx -masa 2 testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv2MASA2TC48c.wav bit ../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv2MASA2TC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst @@ -1574,7 +1574,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec EXT 48 bit testv/stv714MC48c.wav_sw_48-48_EXT.tst // Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit ../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_BINAURAL_rtpdump_PIdata.tst @@ -1587,7 +1587,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48.tst // Stereo downmix to bit-exact EVS at 24400 kbps, 48kHz in, 48kHz out, rtpdump -../IVAS_cod -rtpdump -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit +../IVAS_cod -rtpdump 3 -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit ../IVAS_dec -VOIP_hf_only=1 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48_rtpdump.tst @@ -1600,7 +1600,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec -non_diegetic_pan 80 STEREO 48 bit testv/stv1ISM48s.pcm_ISM_32000_48-48_STEREO_NON-DIEGETIC-PAN_80.tst // EVS at 13.2 kbps, 48kHz in, 48kHz out, STEREO out, rtpdump -../IVAS_cod -rtpdump 13200 48 testv/stv48c.wav bit +../IVAS_cod -rtpdump 3 13200 48 testv/stv48c.wav bit ../IVAS_dec -VOIP_hf_only=1 48 bit testv/stv48c.pcm_EVS_13200_48-48_STEREO_rtpdump.tst @@ -1880,7 +1880,7 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec EXT 48 bit testv/stvOMASA_4ISM_2MASA2TC48c.wav_EXT_sw_48-48.tst // OMASA 2Dir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case00_3000_q.csv -ism_masa 3 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_160k_omasatechs_3ism.bin 48 testv/stvOMASA_3ISM_2MASA2TC48c.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case00_3000_q.csv -ism_masa 3 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_160k_omasatechs_3ism.bin 48 testv/stvOMASA_3ISM_2MASA2TC48c.wav bit ../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOMASA_3ISM_2MASA2TC48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst @@ -2024,7 +2024,7 @@ networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit ../IVAS_dec -no_delay_cmp -Tracefile tracefile_dec -VOIP BINAURAL_ROOM_REVERB 48 netsimoutput testv/stvOSBA_2ISM_2OA32c.wav_BINAURAL_brsw_32-48_JBM5.tst // OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit ../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst -- GitLab From 9255c1265fad88780dab22b8147a015e66d60ec9 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 12 Sep 2025 15:41:23 +0200 Subject: [PATCH 101/340] Fix incorrectly merged changes from 3bdd36d26dd83ffc46add1ce012533bf1d7f6d9f --- lib_dec/lib_dec.c | 51 +++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 8a437c4c4f..008c033210 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1909,7 +1909,6 @@ static int16_t isar_get_frame_size( static ivas_error isar_render_poses( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ const int16_t nSamplesAsked, /* i : number of samples wanted by the caller */ - float **p_head_pose_buf, /* o : PCM buffer with head-pose data */ int16_t *nOutSamples, /* o : number of samples per channel written to output buffer */ bool *needNewFrame /* o : indication that the decoder needs a new frame */ ) @@ -1917,7 +1916,7 @@ static ivas_error isar_render_poses( float pcmBuf[BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES * L_FRAME48k]; Decoder_Struct *st_ivas; ivas_error error; - int16_t i, j, numPoses; + int16_t numPoses; if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL ) { @@ -1958,15 +1957,6 @@ static ivas_error isar_render_poses( ivas_TD_RINGBUF_PushInterleaved( st_ivas->hSplitBinRend->hMultiBinTdData, pcmBuf, *nOutSamples ); } - /* change buffer layout */ - for ( i = 0; i < *nOutSamples; ++i ) - { - for ( j = 0; j < BINAURAL_CHANNELS * numPoses; ++j ) - { - p_head_pose_buf[j][i] = pcmBuf[i * BINAURAL_CHANNELS * numPoses + j]; - } - } - return error; } @@ -2100,12 +2090,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( pcm_out_flag = ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; numSamplesPerChannelToOutput = isar_get_frame_size( st_ivas ); - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] = head_pose_buf[i]; - } - - if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, p_head_pose_buf, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, numSamplesPerChannelToOutput, nOutSamples, needNewFrame ) ) != IVAS_ERR_OK ) { return error; } @@ -2115,6 +2100,16 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( return IVAS_ERR_OK; } + for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) + { + p_head_pose_buf[i] = head_pose_buf[i]; + } + + if ( !ivas_dec_split_rend_cldfb_in( st_ivas->renderer_type ) ) + { + ivas_TD_RINGBUF_PopChannels( st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nOutSamples ); + } + if ( ( error = isar_generate_metadata_and_bitstream( st_ivas, p_head_pose_buf, splitRendBits ) ) != IVAS_ERR_OK ) { return error; @@ -3771,9 +3766,6 @@ ivas_error IVAS_DEC_VoIP_GetSamples int16_t result; ivas_error error; uint8_t nOutChannels; -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - int16_t i; -#endif if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->hVoIP == NULL ) { @@ -3980,23 +3972,11 @@ ivas_error IVAS_DEC_VoIP_GetSamples #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( splitRendBits != NULL ) { - /* Move output pointers forward */ - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] += *nSamplesRendered; - } - /* Render head poses from time-scaled transport channels */ - if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, p_head_pose_buf, &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) + if ( ( error = isar_render_poses( hIvasDec, nSamplesToRender, &nSamplesRendered_loop, &tmp ) ) != IVAS_ERR_OK ) { return error; } - - /* Set pointers back to the beginning of head pose buffers */ - for ( i = 0; i < BINAURAL_CHANNELS * MAX_HEAD_ROT_POSES; ++i ) - { - p_head_pose_buf[i] -= *nSamplesRendered; - } } else { @@ -5254,6 +5234,7 @@ static ivas_error ivas_create_handle_isar( isar_init_split_rend_handles( &hSplitBinRend->splitrend ); #ifdef FIX_1119_SPLIT_RENDERING_VOIP + hSplitBinRend->hMultiBinTdData = NULL; for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) { hSplitBinRend->hMultiBinCldfbData[i] = NULL; @@ -5287,6 +5268,10 @@ static void ivas_destroy_handle_isar( if ( *hSplitBinRend != NULL ) { #ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( ( *hSplitBinRend )->hMultiBinTdData != NULL ) + { + ivas_TD_RINGBUF_Close( &( *hSplitBinRend )->hMultiBinTdData ); + } for ( i = 0; i < MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS; ++i ) { if ( ( *hSplitBinRend )->hMultiBinCldfbData[i] != NULL ) -- GitLab From a87ea2fdddba44b98972b3d1189bfefe076954ed Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Fri, 12 Sep 2025 15:56:16 +0200 Subject: [PATCH 102/340] Apply formatting patch --- lib_rend/lib_rend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 8a16ff77b8..2fea668620 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7953,7 +7953,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From 9f446259e52e3620d339a85e6457292b39f48bad Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Tue, 16 Sep 2025 10:55:06 +0200 Subject: [PATCH 103/340] Pop from ring buffers in one unified place --- lib_dec/lib_dec.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 11c645bd08..ab29c22adc 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -1933,6 +1933,7 @@ static ivas_error isar_render_poses( static ivas_error isar_generate_metadata_and_bitstream( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ float **p_head_pose_buf, + int16_t nSamples, /* i : duration of audio (in samples per channel) for which metadata should be generated */ ISAR_SPLIT_REND_BITS_DATA *splitRendBits /* o : output split rendering bits */ ) { @@ -1943,10 +1944,10 @@ static ivas_error isar_generate_metadata_and_bitstream( ISAR_DEC_SPLIT_REND_WRAPPER_HANDLE hSplitBinRend; int16_t max_band; int16_t pcm_out_flag; - int16_t td_input; + int16_t cldfb_in_flag; int16_t ro_md_flag; IVAS_QUATERNION Quaternion; - int16_t i, j, num_poses, num_cldfb_slots; + int16_t i, j, num_poses, num_cldfb_slots, n_samples_in_cldfb_slot; float *p_Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; float *p_Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX]; @@ -1958,12 +1959,15 @@ static ivas_error isar_generate_metadata_and_bitstream( max_band = (int16_t) ( ( BINAURAL_MAXBANDS * output_Fs ) / 48000 ); pcm_out_flag = ( output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ? 1 : 0; - td_input = st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC && st_ivas->renderer_type != RENDERER_BINAURAL_PARAMETRIC_ROOM && st_ivas->renderer_type != RENDERER_STEREO_PARAMETRIC; + cldfb_in_flag = ivas_dec_split_rend_cldfb_in( st_ivas ); - if ( !td_input ) + if ( cldfb_in_flag ) { + n_samples_in_cldfb_slot = NS2SA( st_ivas->hDecoderConfig->output_Fs, CLDFB_SLOT_NS ); + assert( nSamples % n_samples_in_cldfb_slot == 0 ); + num_cldfb_slots = nSamples / n_samples_in_cldfb_slot; + num_poses = hSplitBinRend->splitrend.multiBinPoseData.num_poses; - num_cldfb_slots = (int16_t) hIvasDec->st_ivas->hDecoderConfig->render_framesize * JBM_CLDFB_SLOTS_IN_SUBFRAME; for ( i = 0; i < (int16_t) ( BINAURAL_CHANNELS * num_poses ); ++i ) { @@ -1988,6 +1992,11 @@ static ivas_error isar_generate_metadata_and_bitstream( } } } + else + { + ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, nSamples ); + } + if ( st_ivas->hBinRendererTd != NULL ) { @@ -2019,7 +2028,7 @@ static ivas_error isar_generate_metadata_and_bitstream( splitRendBits, p_Cldfb_RealBuffer_Binaural, p_Cldfb_ImagBuffer_Binaural, - max_band, p_head_pose_buf, 1, !td_input, pcm_out_flag, ro_md_flag ) ) != IVAS_ERR_OK ) + max_band, p_head_pose_buf, 1, cldfb_in_flag, pcm_out_flag, ro_md_flag ) ) != IVAS_ERR_OK ) { return error; } @@ -2083,12 +2092,7 @@ ivas_error IVAS_DEC_GetSplitBinauralBitstream( p_head_pose_buf[i] = head_pose_buf[i]; } - if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) - { - ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nOutSamples ); - } - - error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); + error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, *nOutSamples, splitRendBits ); if ( error != IVAS_ERR_OK ) { return error; @@ -3981,13 +3985,8 @@ ivas_error IVAS_DEC_VoIP_GetSamples p_head_pose_buf[i] = head_pose_buf[i]; } - if ( !ivas_dec_split_rend_cldfb_in( st_ivas ) ) - { - ivas_TD_RINGBUF_PopChannels( hIvasDec->st_ivas->hSplitBinRend->hMultiBinTdData, p_head_pose_buf, *nSamplesRendered ); - } - /* Analyse head poses over entire frame, generate ISAR metadata and maybe encode if split coded */ - error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, splitRendBits ); + error = isar_generate_metadata_and_bitstream( hIvasDec, p_head_pose_buf, *nSamplesRendered, splitRendBits ); if ( error != IVAS_ERR_OK ) { return error; -- GitLab From 3d72a4bd319d4c7fac565cca03295019f3e724f3 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 17 Sep 2025 18:24:20 +0300 Subject: [PATCH 104/340] Apply diegetic PI data in rendering --- apps/decoder.c | 9 +++++++++ lib_dec/ivas_objectRenderer_internal.c | 12 ++++++++++++ lib_dec/lib_dec.c | 25 ++++++++++++++++++++++++ lib_dec/lib_dec.h | 7 +++++++ lib_rend/ivas_objectRenderer.c | 27 ++++++++++++++++++++++++++ lib_rend/ivas_prot_rend.h | 6 ++++++ lib_rend/ivas_rotation.c | 25 ++++++++++++++++++++++++ lib_rend/ivas_stat_rend.h | 4 ++++ 8 files changed, 115 insertions(+) diff --git a/apps/decoder.c b/apps/decoder.c index f2f0e31b3d..a28d5d6023 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -534,6 +534,15 @@ static ivas_error IVAS_RTP_ApplyPiData( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, } break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_DIEGETIC_TYPE: + { + DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); + IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); + } + break; + +#endif default: { fprintf( stderr, "Unhandled PI data of type : %s\n", PiDataNames[piDataType] ); diff --git a/lib_dec/ivas_objectRenderer_internal.c b/lib_dec/ivas_objectRenderer_internal.c index ff47b1b9a7..353c15d43c 100644 --- a/lib_dec/ivas_objectRenderer_internal.c +++ b/lib_dec/ivas_objectRenderer_internal.c @@ -206,14 +206,26 @@ ivas_error ivas_td_binaural_renderer_sf( hIsmMetaData[nS] = &ismMetaData[nS]; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, hIsmMetaData, + ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPI : NULL, + ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) +#else if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, hIsmMetaData ) ) != IVAS_ERR_OK ) +#endif { return error; } } else { +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, st_ivas->hIsmMetaData, + ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPI : NULL, + ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) +#else if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, st_ivas->hIsmMetaData ) ) != IVAS_ERR_OK ) +#endif { return error; } diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 4d27cb168e..b46def86e4 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3827,6 +3827,31 @@ void IVAS_DEC_resetExternalOrientations( } +#endif +#ifdef RTP_S4_251135_CR26253_0016_REV1 +/*---------------------------------------------------------------------* + * IVAS_DEC_setDiegeticInput( ) + * + * Set isDiegeticInput flag for combined orientation handle based on PI data. + *---------------------------------------------------------------------*/ + +void IVAS_DEC_setDiegeticInputPI( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const bool *diegeticPIValues /* i : diegetic values for the input stream */ +) +{ + if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) + { + int8_t i; + for ( i = 0; i < 4; i++ ) + { + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; + } + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPISet = true; + } +} + + #endif /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_IsEmpty( ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 2e98d9e0f1..caed7bd54b 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -349,6 +349,13 @@ void IVAS_DEC_resetExternalOrientations( IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ ); +#endif +#ifdef RTP_S4_251135_CR26253_0016_REV1 +void IVAS_DEC_setDiegeticInputPI( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const bool *diegeticPIValues /* i : diegetic values for the input stream */ +); + #endif /* Setter functions - apply changes to decoder configuration */ diff --git a/lib_rend/ivas_objectRenderer.c b/lib_rend/ivas_objectRenderer.c index f0fb90fe4e..c467838971 100644 --- a/lib_rend/ivas_objectRenderer.c +++ b/lib_rend/ivas_objectRenderer.c @@ -363,7 +363,13 @@ ivas_error ivas_td_binaural_renderer_unwrap( if ( subframe_idx == ism_md_subframe_update ) { /* Update object position(s) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = TDREND_Update_object_positions( hBinRendererTd, num_src, ivas_format, hIsmMetaData, + ( hCombinedOrientationData != NULL ) ? hCombinedOrientationData->isDiegeticInputPI : NULL, + ( hCombinedOrientationData != NULL ) ? hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) +#else if ( ( error = TDREND_Update_object_positions( hBinRendererTd, num_src, ivas_format, hIsmMetaData ) ) != IVAS_ERR_OK ) +#endif { return error; } @@ -531,7 +537,13 @@ ivas_error TDREND_Update_object_positions( BINAURAL_TD_OBJECT_RENDERER_HANDLE hBinRendererTd, /* i/o: TD Renderer handle */ const int16_t num_src, /* i : number of sources to render */ const IVAS_FORMAT in_format, /* i : Format of input sources */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + const ISM_METADATA_HANDLE *hIsmMetaData, /* i : Input metadata for ISM objects */ + const bool *isDiegeticInputPI, /* i : diegetic values for the input streams from the PI data */ + const bool isDiegeticInputPISet /* i : is diegetic values obtained from PI data */ +#else const ISM_METADATA_HANDLE *hIsmMetaData /* i : Input metadata for ISM objects */ +#endif ) { #ifndef NONBE_1377_REND_DIRATT_CONF @@ -544,6 +556,9 @@ ivas_error TDREND_Update_object_positions( #ifndef NONBE_1377_REND_DIRATT_CONF DirAtten_p = hBinRendererTd->DirAtten_p; #endif +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool isDiegeticSrcPI; +#endif /* For each source, write the frame data to the source object*/ for ( nS = 0; nS < num_src; nS++ ) @@ -571,7 +586,19 @@ ivas_error TDREND_Update_object_positions( return error; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + isDiegeticSrcPI = false; + if ( isDiegeticInputPISet && isDiegeticInputPI != NULL ) + { + if ( isDiegeticInputPI[nS] ) + { + isDiegeticSrcPI = true; + } + } + if ( hIsmMetaData[nS]->non_diegetic_flag || !isDiegeticSrcPI ) +#else if ( hIsmMetaData[nS]->non_diegetic_flag ) +#endif { Pos[0] = 0; Pos[1] = hIsmMetaData[nS]->azimuth / 90.f; diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 937923c338..e3d205cd25 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -691,7 +691,13 @@ ivas_error TDREND_Update_object_positions( BINAURAL_TD_OBJECT_RENDERER_HANDLE hBinRendererTd, /* i/o: TD Renderer handle */ const int16_t num_src, /* i : number of sources to render */ const IVAS_FORMAT in_format, /* i : Format of input sources */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + const ISM_METADATA_HANDLE *hIsmMetaData, /* i : Input metadata for ISM objects */ + const bool *isDiegeticInputPI, /* i : diegetic values for the input streams from the PI data */ + const bool isDiegeticInputPISet /* i : is diegetic values obtained from PI data */ +#else const ISM_METADATA_HANDLE *hIsmMetaData /* i : Input metadata for ISM objects */ +#endif ); void BSplineModelEvalDealloc( diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 9a1d74581d..05fb92cd18 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -862,6 +862,14 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->subframe_idx = 0; ( *hCombinedOrientationData )->subframe_size = (int16_t) ( fs / ( FRAMES_PER_SEC * MAX_PARAM_SPATIAL_SUBFRAMES ) ); ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + + for ( i = 0; i < 4; i++ ) + { + ( *hCombinedOrientationData )->isDiegeticInputPI[i] = true; + } + ( *hCombinedOrientationData )->isDiegeticInputPISet = false; +#endif return IVAS_ERR_OK; } @@ -1023,10 +1031,27 @@ ivas_error combine_external_and_head_orientations( else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { /* Head rotation only */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) + { + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = identity; + } + } + else + { + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + } + } +#else for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; } +#endif } if ( hExtOrientationData != NULL ) diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index 69fcb30fb8..fb8bf44b83 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -710,6 +710,10 @@ typedef struct ivas_combined_orientation_struct int16_t cur_subframe_samples_rendered; int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool isDiegeticInputPI[5]; + bool isDiegeticInputPISet; +#endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; /*----------------------------------------------------------------------------------* -- GitLab From 6f3ec62e2d85c7250d7cb6c0acb690c749c3223c Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Thu, 18 Sep 2025 08:06:15 +0200 Subject: [PATCH 105/340] Add missing error code handling from IVAS_REND_SetObjectIDs --- apps/renderer.c | 8 + lib_com/options.h | 2 +- scripts/testv/headrot.csv | 5371 +------------------------------------ 3 files changed, 10 insertions(+), 5371 deletions(-) diff --git a/apps/renderer.c b/apps/renderer.c index 917a6f3a90..cf5a7e33d2 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -1229,7 +1229,15 @@ int main( masaIds[i] = 0u; } +#ifdef FIX_1377_HANDLE_ERROR_CODE + if ( ( error = IVAS_REND_SetObjectIDs( hIvasRend ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nIVAS_REND_SetObjectIDs: %s\n", ivas_error_to_string( error ) ); + goto cleanup; + } +#else IVAS_REND_SetObjectIDs( hIvasRend ); +#endif for ( i = 0; i < args.inConfig.numMultiChannelBuses; ++i ) { diff --git a/lib_com/options.h b/lib_com/options.h index 77f6098c21..693f3610f9 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -163,7 +163,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define TMP_FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add error check for unsupported config: split rendering with VoIP mode */ - +#define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ /* #################### End BE switches ################################## */ diff --git a/scripts/testv/headrot.csv b/scripts/testv/headrot.csv index b298d22c89..9970bb2db0 100644 --- a/scripts/testv/headrot.csv +++ b/scripts/testv/headrot.csv @@ -2728,5373 +2728,4 @@ 0.707107,0.000000,0.000000,0.707107 0.707107,0.000000,0.000000,0.707107 0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.0000 \ No newline at end of file -- GitLab From 32ed6759b3e174fa2793c2433d7a04d2725bb37f Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Thu, 18 Sep 2025 09:13:47 +0200 Subject: [PATCH 106/340] Restore headrot.csv --- scripts/testv/headrot.csv | 5371 ++++++++++++++++++++++++++++++++++++- 1 file changed, 5370 insertions(+), 1 deletion(-) diff --git a/scripts/testv/headrot.csv b/scripts/testv/headrot.csv index 9970bb2db0..b298d22c89 100644 --- a/scripts/testv/headrot.csv +++ b/scripts/testv/headrot.csv @@ -2728,4 +2728,5373 @@ 0.707107,0.000000,0.000000,0.707107 0.707107,0.000000,0.000000,0.707107 0.707107,0.000000,0.000000,0.707107 -0.707107,0.000000,0.0000 \ No newline at end of file +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 +0.707107,0.000000,0.000000,0.707107 -- GitLab From 6f55d5cc3c1b2272e31d6d61fa9599f3a0668000 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 18 Sep 2025 12:52:33 +0300 Subject: [PATCH 107/340] Change head tracking parameter to not be a pointer --- lib_rend/ivas_prot_rend.h | 2 +- lib_rend/ivas_rotation.c | 12 ++++++------ lib_rend/lib_rend.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 6cce3e77f8..0bd66308bc 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1322,7 +1322,7 @@ ivas_error combine_external_and_head_orientations_dec( ); ivas_error combine_external_and_head_orientations_rend( - IVAS_REND_HeadRotData *hHeadTrackData, /* i : head track handle */ + IVAS_REND_HeadRotData hHeadTrackData, /* i : head track data */ EXTERNAL_ORIENTATION_HANDLE hExtOrientationData, /* i : external orientation handle */ COMBINED_ORIENTATION_HANDLE hCombinedOrientationData /* i/o: combined orientation handle */ ); diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 9385bc753e..a8e9acdfcb 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -887,7 +887,7 @@ ivas_error combine_external_and_head_orientations_dec( *------------------------------------------------------------------------*/ ivas_error combine_external_and_head_orientations_rend( - IVAS_REND_HeadRotData *hHeadTrackData, /* i : head track handle */ + IVAS_REND_HeadRotData hHeadTrackData, /* i : head track data */ EXTERNAL_ORIENTATION_HANDLE hExtOrientationData, /* i : external orientation handle */ COMBINED_ORIENTATION_HANDLE hCombinedOrientationData /* i/o: combined orientation handle */ ) @@ -898,14 +898,14 @@ ivas_error combine_external_and_head_orientations_rend( int16_t i; sr_pose_pred_axis = DEFAULT_AXIS; - if ( hHeadTrackData != NULL ) + if ( hHeadTrackData.hOrientationTracker != NULL ) { - if ( hHeadTrackData->headRotEnabled ) + if ( hHeadTrackData.headRotEnabled ) { - headRotQuaternions = hHeadTrackData->headPositions; - listenerPos = hHeadTrackData->Pos; + headRotQuaternions = hHeadTrackData.headPositions; + listenerPos = hHeadTrackData.Pos; } - sr_pose_pred_axis = hHeadTrackData->sr_pose_pred_axis; + sr_pose_pred_axis = hHeadTrackData.sr_pose_pred_axis; } else if ( hExtOrientationData != NULL ) { diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 0f56db1ae3..645772e610 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -5058,7 +5058,7 @@ ivas_error IVAS_REND_CombineHeadAndExternalOrientation( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - return combine_external_and_head_orientations_rend( &hIvasRend->headRotData, hIvasRend->hExternalOrientationData, hIvasRend->hCombinedOrientationData ); + return combine_external_and_head_orientations_rend( hIvasRend->headRotData, hIvasRend->hExternalOrientationData, hIvasRend->hCombinedOrientationData ); } -- GitLab From f214d4658f5c1473723cf710fd4722a8c65a014c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 18 Sep 2025 13:04:40 +0300 Subject: [PATCH 108/340] Revert head tracking parameter back to pointer, move head tracking data readings under headRotEnabled --- lib_rend/ivas_prot_rend.h | 2 +- lib_rend/ivas_rotation.c | 13 +++++-------- lib_rend/lib_rend.c | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 0bd66308bc..6cce3e77f8 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1322,7 +1322,7 @@ ivas_error combine_external_and_head_orientations_dec( ); ivas_error combine_external_and_head_orientations_rend( - IVAS_REND_HeadRotData hHeadTrackData, /* i : head track data */ + IVAS_REND_HeadRotData *hHeadTrackData, /* i : head track handle */ EXTERNAL_ORIENTATION_HANDLE hExtOrientationData, /* i : external orientation handle */ COMBINED_ORIENTATION_HANDLE hCombinedOrientationData /* i/o: combined orientation handle */ ); diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index a8e9acdfcb..b9286d3d96 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -887,7 +887,7 @@ ivas_error combine_external_and_head_orientations_dec( *------------------------------------------------------------------------*/ ivas_error combine_external_and_head_orientations_rend( - IVAS_REND_HeadRotData hHeadTrackData, /* i : head track data */ + IVAS_REND_HeadRotData *hHeadTrackData, /* i : head track handle */ EXTERNAL_ORIENTATION_HANDLE hExtOrientationData, /* i : external orientation handle */ COMBINED_ORIENTATION_HANDLE hCombinedOrientationData /* i/o: combined orientation handle */ ) @@ -898,14 +898,11 @@ ivas_error combine_external_and_head_orientations_rend( int16_t i; sr_pose_pred_axis = DEFAULT_AXIS; - if ( hHeadTrackData.hOrientationTracker != NULL ) + if ( hHeadTrackData->headRotEnabled ) { - if ( hHeadTrackData.headRotEnabled ) - { - headRotQuaternions = hHeadTrackData.headPositions; - listenerPos = hHeadTrackData.Pos; - } - sr_pose_pred_axis = hHeadTrackData.sr_pose_pred_axis; + headRotQuaternions = hHeadTrackData->headPositions; + listenerPos = hHeadTrackData->Pos; + sr_pose_pred_axis = hHeadTrackData->sr_pose_pred_axis; } else if ( hExtOrientationData != NULL ) { diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 645772e610..0f56db1ae3 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -5058,7 +5058,7 @@ ivas_error IVAS_REND_CombineHeadAndExternalOrientation( return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - return combine_external_and_head_orientations_rend( hIvasRend->headRotData, hIvasRend->hExternalOrientationData, hIvasRend->hCombinedOrientationData ); + return combine_external_and_head_orientations_rend( &hIvasRend->headRotData, hIvasRend->hExternalOrientationData, hIvasRend->hCombinedOrientationData ); } -- GitLab From 4d5a719b35ef33bbd2f375c16fb1a0adaf28aab1 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 18 Sep 2025 13:13:57 +0300 Subject: [PATCH 109/340] Add switche for issue 1383 --- lib_com/options.h | 1 + lib_rend/ivas_rotation.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 77f6098c21..27394d6ff3 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -163,6 +163,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define TMP_FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add error check for unsupported config: split rendering with VoIP mode */ +#define FIX_1383_HEAD_TRACK_SANITIZER /* Nok: issue 1383: Fix head tracking struc values reading in renderer */ /* #################### End BE switches ################################## */ diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index b9286d3d96..ad42bd515e 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -898,10 +898,20 @@ ivas_error combine_external_and_head_orientations_rend( int16_t i; sr_pose_pred_axis = DEFAULT_AXIS; +#ifdef FIX_1383_HEAD_TRACK_SANITIZER if ( hHeadTrackData->headRotEnabled ) { headRotQuaternions = hHeadTrackData->headPositions; listenerPos = hHeadTrackData->Pos; +#else + if ( hHeadTrackData != NULL ) + { + if ( hHeadTrackData->headRotEnabled ) + { + headRotQuaternions = hHeadTrackData->headPositions; + listenerPos = hHeadTrackData->Pos; + } +#endif sr_pose_pred_axis = hHeadTrackData->sr_pose_pred_axis; } else if ( hExtOrientationData != NULL ) -- GitLab From a8ff3ed81f31a5b9a6480cdd2d3c0baad132a124 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Thu, 18 Sep 2025 16:40:48 +0200 Subject: [PATCH 110/340] Fix python type hints not compatible with Python 3.9 --- tests/split_rendering/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index ffb14eb55a..1d54d24e08 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -34,7 +34,7 @@ import re import sys from pathlib import Path from tempfile import TemporaryDirectory -from typing import Tuple +from typing import Tuple, Optional import numpy as np import pytest @@ -185,7 +185,7 @@ def run_full_chain_split_rendering( get_ssnr=False, get_odg=False, get_odg_bin=False, - delay_profile: Path | None=None, + delay_profile: Optional[Path]=None, ) -> str: """ Runs the full split rendering chain consisting of -- GitLab From 2ae1d602f5486eb72eeb70b8e375b714bad24936 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Fri, 19 Sep 2025 10:38:44 +0300 Subject: [PATCH 111/340] Fixes issue 1344. --- lib_com/options.h | 1 + lib_rend/ivas_dirac_ana.c | 8 +++++++ lib_rend/ivas_masa_merge.c | 23 ++++++++++++++++++++ lib_rend/ivas_mcmasa_ana.c | 43 ++++++++++++++++++++++++++++++++++++++ lib_rend/ivas_omasa_ana.c | 8 +++++++ lib_rend/ivas_prot_rend.h | 11 ++++++++++ lib_rend/ivas_stat_rend.h | 5 +++++ lib_rend/lib_rend.c | 9 ++++++++ 8 files changed, 108 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..caa8210e61 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -176,6 +176,7 @@ #define NONBE_1244_FIX_SWB_BWE_MEMORY /* VA: issue 1244: fix to SWB BWE memory in case of switching from FB coding - pending a review by Huawei */ #define NONBE_1122_KEEP_EVS_MODE_UNCHANGED /* FhG: Disables fix for issue 1122 in EVS mode to keep BE tests green. This switch should be removed once the 1122 fix is added to EVS via a CR. */ #define NONBE_1328_FIX_NON_LINEARITY /* VA: Fix possible issue when computing bwe_exc_extended and previous frame were almost 0 */ +#define NONBE_1344_REND_MASA_LOW_FS /* Nokia: Issue 1344: Fix sanitizer errors when using IVAS_rend to render MASA with lower sampling rates */ /* ##################### End NON-BE switches ########################### */ diff --git a/lib_rend/ivas_dirac_ana.c b/lib_rend/ivas_dirac_ana.c index f6273106de..973182504a 100644 --- a/lib_rend/ivas_dirac_ana.c +++ b/lib_rend/ivas_dirac_ana.c @@ -235,6 +235,14 @@ void ivas_dirac_ana( /* Estimate MASA parameters from the SBA signals */ ivas_dirac_param_est_ana( hDirAC, data_in_f, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence, input_frame ); +#ifdef NONBE_1344_REND_MASA_LOW_FS + /* Add zeros to higher bands in case of lower sampling rates */ + if ( hDirAC->nbands < MASA_FREQUENCY_BANDS ) + { + ivas_masa_zero_high_bands( hDirAC->nbands, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); + } +#endif + /* Create MASA metadata buffer from the estimated values */ ivas_create_masa_out_meta( hDirAC->hMasaOut, hDirAC->sph_grid16, nchan_transport, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); diff --git a/lib_rend/ivas_masa_merge.c b/lib_rend/ivas_masa_merge.c index 37ee8cc7ef..cd5af80ff1 100644 --- a/lib_rend/ivas_masa_merge.c +++ b/lib_rend/ivas_masa_merge.c @@ -37,6 +37,9 @@ #include "ivas_prot.h" #include "ivas_cnst.h" #include "prot.h" +#ifdef NONBE_1344_REND_MASA_LOW_FS +#include "ivas_rom_com.h" +#endif #include "wmc_auto.h" @@ -326,6 +329,9 @@ ivas_error masaPrerendOpen( { MASA_PREREND_HANDLE hMasaPrerend; int16_t i; +#ifdef NONBE_1344_REND_MASA_LOW_FS + int16_t maxBin; +#endif ivas_error error; error = IVAS_ERR_OK; @@ -336,6 +342,23 @@ ivas_error masaPrerendOpen( return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for MASA prerenderer\n" ) ); } +#ifdef NONBE_1344_REND_MASA_LOW_FS + /* Determine the number of bands and band grouping */ + hMasaPrerend->nbands = MASA_FREQUENCY_BANDS; + mvs2s( MASA_band_grouping_24, hMasaPrerend->band_grouping, 24 + 1 ); + + maxBin = (int16_t) ( input_Fs * INV_CLDFB_BANDWIDTH + 0.5f ); + for ( i = 1; i < hMasaPrerend->nbands + 1; i++ ) + { + if ( hMasaPrerend->band_grouping[i] >= maxBin ) + { + hMasaPrerend->band_grouping[i] = maxBin; + hMasaPrerend->nbands = i; + break; + } + } +#endif + hMasaPrerend->num_Cldfb_instances = numTransports; for ( i = 0; i < hMasaPrerend->num_Cldfb_instances; i++ ) { diff --git a/lib_rend/ivas_mcmasa_ana.c b/lib_rend/ivas_mcmasa_ana.c index 7851921030..6dec9feaa1 100644 --- a/lib_rend/ivas_mcmasa_ana.c +++ b/lib_rend/ivas_mcmasa_ana.c @@ -393,6 +393,14 @@ void ivas_mcmasa_ana( /* Analysis */ ivas_mcmasa_param_est_ana( hMcMasa, data_f, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence, input_frame, nchan_inp ); +#ifdef NONBE_1344_REND_MASA_LOW_FS + /* Add zeros to higher bands in case of lower sampling rates */ + if ( hMcMasa->nbands < MASA_FREQUENCY_BANDS ) + { + ivas_masa_zero_high_bands( hMcMasa->nbands, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); + } +#endif + /* Create MASA metadata buffer from the estimated values */ ivas_create_masa_out_meta( hMcMasa->hMasaOut, hMcMasa->sph_grid16, nchan_transport, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); @@ -1131,3 +1139,38 @@ void ivas_create_masa_out_meta( return; } + + +#ifdef NONBE_1344_REND_MASA_LOW_FS +/*------------------------------------------------------------------------- + * ivas_masa_zero_high_bands() + * + * + *------------------------------------------------------------------------*/ + +void ivas_masa_zero_high_bands( + const int16_t nbands, /* i : Number of frequency bands with estimated values */ + float elevation_m_values[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated elevation */ + float azimuth_m_values[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated azimuth */ + float energyRatio[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated direct-to-total ratio */ + float spreadCoherence[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated spread coherence */ + float surroundingCoherence[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS] /* i/o : Estimated surround coherence */ +) +{ + int16_t sf, band; + + for (sf = 0; sf < MAX_PARAM_SPATIAL_SUBFRAMES; sf++) + { + for ( band = nbands; band < MASA_FREQUENCY_BANDS; band++ ) + { + elevation_m_values[sf][band] = 0.0f; + azimuth_m_values[sf][band] = 0.0f; + energyRatio[sf][band] = 0.0f; + spreadCoherence[sf][band] = 0.0f; + surroundingCoherence[sf][band] = 0.0f; + } + } + + return; +} +#endif diff --git a/lib_rend/ivas_omasa_ana.c b/lib_rend/ivas_omasa_ana.c index e7f58262ed..566099d5b7 100644 --- a/lib_rend/ivas_omasa_ana.c +++ b/lib_rend/ivas_omasa_ana.c @@ -261,6 +261,14 @@ void ivas_omasa_ana( /* Estimate MASA parameters from the objects */ ivas_omasa_param_est_ana( hOMasa, data_in_f, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence, input_frame, nchan_ism ); +#ifdef NONBE_1344_REND_MASA_LOW_FS + /* Add zeros to higher bands in case of lower sampling rates */ + if ( hOMasa->nbands < MASA_FREQUENCY_BANDS ) + { + ivas_masa_zero_high_bands( hOMasa->nbands, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); + } +#endif + /* Create MASA metadata buffer from the estimated values */ ivas_create_masa_out_meta( hOMasa->hMasaOut, hOMasa->sph_grid16, nchan_transport, elevation_m_values, azimuth_m_values, energyRatio, spreadCoherence, surroundingCoherence ); diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 51a1da9933..cca2f34dc6 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1476,6 +1476,17 @@ void ivas_create_masa_out_meta( float surroundingCoherence[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS] /* i : Estimated surround coherence */ ); +#ifdef NONBE_1344_REND_MASA_LOW_FS +void ivas_masa_zero_high_bands( + const int16_t nbands, /* i : Number of frequency bands with estimated values */ + float elevation_m_values[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated elevation */ + float azimuth_m_values[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated azimuth */ + float energyRatio[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated direct-to-total ratio */ + float spreadCoherence[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS], /* i/o : Estimated spread coherence */ + float surroundingCoherence[MAX_PARAM_SPATIAL_SUBFRAMES][MASA_FREQUENCY_BANDS] /* i/o : Estimated surround coherence */ +); +#endif + ivas_error ivas_dirac_ana_open( DIRAC_ANA_HANDLE *hDirACPtr, /* i/o: DIRAC data handle pointer */ int32_t input_Fs diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index 69fcb30fb8..5416e980b5 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -1526,6 +1526,11 @@ typedef struct ivas_dirac_ana_data_structure typedef struct ivas_masa_prerend_data_structure { +#ifdef NONBE_1344_REND_MASA_LOW_FS + int16_t nbands; + int16_t band_grouping[MASA_FREQUENCY_BANDS + 1]; +#endif + /* CLDFB analysis */ int16_t num_Cldfb_instances; HANDLE_CLDFB_FILTER_BANK cldfbAnaEnc[MASA_MAX_TRANSPORT_CHANNELS]; diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 52994cdba1..78bf020db5 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7312,10 +7312,19 @@ static void renderMasaToMasa( } /* Compute channel energy for metadata processing */ +#ifdef NONBE_1344_REND_MASA_LOW_FS + for ( band_m_idx = 0; band_m_idx < masaInput->hMasaPrerend->nbands; band_m_idx++ ) +#else for ( band_m_idx = 0; band_m_idx < MASA_FREQUENCY_BANDS; band_m_idx++ ) +#endif { +#ifdef NONBE_1344_REND_MASA_LOW_FS + brange[0] = masaInput->hMasaPrerend->band_grouping[band_m_idx]; + brange[1] = masaInput->hMasaPrerend->band_grouping[band_m_idx + 1]; +#else brange[0] = MASA_band_grouping_24[band_m_idx]; brange[1] = MASA_band_grouping_24[band_m_idx + 1]; +#endif for ( j = brange[0]; j < brange[1]; j++ ) { for ( i = 0; i < numAnalysisChannels; i++ ) -- GitLab From 45010f473cb44fb18a499aaba0ba6ca898c90cdc Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Fri, 19 Sep 2025 10:47:27 +0300 Subject: [PATCH 112/340] Clang format --- lib_rend/ivas_mcmasa_ana.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/ivas_mcmasa_ana.c b/lib_rend/ivas_mcmasa_ana.c index 6dec9feaa1..2f67607e94 100644 --- a/lib_rend/ivas_mcmasa_ana.c +++ b/lib_rend/ivas_mcmasa_ana.c @@ -1159,7 +1159,7 @@ void ivas_masa_zero_high_bands( { int16_t sf, band; - for (sf = 0; sf < MAX_PARAM_SPATIAL_SUBFRAMES; sf++) + for ( sf = 0; sf < MAX_PARAM_SPATIAL_SUBFRAMES; sf++ ) { for ( band = nbands; band < MASA_FREQUENCY_BANDS; band++ ) { -- GitLab From 6ff5e14582ff89cd64997c5afc1d0bd33016a55f Mon Sep 17 00:00:00 2001 From: vaclav Date: Fri, 19 Sep 2025 10:38:34 +0200 Subject: [PATCH 113/340] issue 1388: fix use-of-uninitialized value in ivas_init_decoder(); under FIX_1388_MSAN_ivas_init_decoder --- lib_com/options.h | 1 + lib_dec/lib_dec.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..1d89aac74e 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_1388_MSAN_ivas_init_decoder /* VA: issue 1388: fix use-of-uninitialized value in ivas_init_decoder() */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0f54199425..c2c9489998 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -857,12 +857,23 @@ ivas_error IVAS_DEC_FeedFrame_Serial( { ivas_error error; +#ifdef FIX_1388_MSAN_ivas_init_decoder + if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL ) + { + return IVAS_ERR_UNEXPECTED_NULL_POINTER; + } + +#endif if ( !hIvasDec->isInitialized ) { /* Once first frame is fed, finish initialization in EVS Mono. * In IVAS mode, initialization is done in ivas_dec(). */ if ( hIvasDec->mode == IVAS_DEC_MODE_EVS ) { +#ifdef FIX_1388_MSAN_ivas_init_decoder + hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = ACELP_8k00; + +#endif if ( ( error = ivas_init_decoder( hIvasDec->st_ivas ) ) != IVAS_ERR_OK ) { return error; @@ -875,10 +886,12 @@ ivas_error IVAS_DEC_FeedFrame_Serial( st->prev_use_partial_copy = 0; hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = hIvasDec->hVoIP->hCurrentDataUnit->dataSize * FRAMES_PER_SEC; } +#ifndef FIX_1388_MSAN_ivas_init_decoder else { hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = ACELP_8k00; } +#endif hIvasDec->isInitialized = true; } } @@ -908,6 +921,7 @@ ivas_error IVAS_DEC_FeedFrame_Serial( bfi = 2; } } + if ( ( error = read_indices( hIvasDec->st_ivas, serial, num_bits, &hIvasDec->prev_ft_speech, &hIvasDec->CNG, bfi ) ) != IVAS_ERR_OK ) { return error; -- GitLab From 677cdfb335ea08cfd415acca77de1ab08c19f5a9 Mon Sep 17 00:00:00 2001 From: vaclav Date: Fri, 19 Sep 2025 11:20:02 +0200 Subject: [PATCH 114/340] issue 1384: fix use-of-uninitialized value in stereo_tcx_core_enc(); under FIX_1384_MSAN_stereo_tcx_core_enc --- lib_com/options.h | 1 + lib_dec/ivas_tcx_core_dec.c | 29 +++++++++++++++++++++++++++-- lib_enc/ivas_tcx_core_enc.c | 5 ++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..93fe135c81 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_1384_MSAN_stereo_tcx_core_enc /* VA: issue 1384: fix use-of-uninitialized value in stereo_tcx_core_enc() */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/ivas_tcx_core_dec.c b/lib_dec/ivas_tcx_core_dec.c index fc66dbc22a..94d5c89409 100644 --- a/lib_dec/ivas_tcx_core_dec.c +++ b/lib_dec/ivas_tcx_core_dec.c @@ -277,7 +277,7 @@ void stereo_tcx_core_dec( } /*--------------------------------------------------------------------------------* - * LPC PARAMETERS + * LPC envelope decoding *--------------------------------------------------------------------------------*/ st->acelp_cfg.midLpc = 0; @@ -288,6 +288,9 @@ void stereo_tcx_core_dec( { int16_t tcx_lpc_cdk; +#ifdef FIX_1384_MSAN_stereo_tcx_core_enc + tcx_lpc_cdk = tcxlpc_get_cdk( st->coder_type ); +#else if ( bfi && st->use_partial_copy && st->rf_frame_type == RF_TCXFD ) { tcx_lpc_cdk = tcxlpc_get_cdk( GENERIC ); @@ -296,6 +299,7 @@ void stereo_tcx_core_dec( { tcx_lpc_cdk = tcxlpc_get_cdk( st->coder_type ); } +#endif mvr2r( st->lsf_old, &lsf[0], M ); mvr2r( st->lsp_old, &lsp[0], M ); @@ -532,6 +536,7 @@ void stereo_tcx_core_dec( if ( st->core == TCX_10_CORE || st->core == TCX_20_CORE ) { +#ifndef FIX_1384_MSAN_stereo_tcx_core_enc if ( st->enablePlcWaveadjust || /* bfi */ ( st->last_total_brate >= HQ_48k && /* recovery */ st->last_codec_mode == MODE2 ) ) @@ -549,6 +554,7 @@ void stereo_tcx_core_dec( } } } +#endif if ( !bfi && st->hTonalMDCTConc != NULL ) { @@ -834,7 +840,11 @@ static void dec_prm_tcx( getTCXMode( st, st, 0 /* <- MCT_flag */ ); /* last_core for error concealment */ +#ifdef FIX_1384_MSAN_stereo_tcx_core_enc + if ( st->element_mode != IVAS_CPE_MDCT ) +#else if ( !st->use_partial_copy && st->element_mode != IVAS_CPE_MDCT ) +#endif { st->last_core_from_bs = get_next_indice( st, 1 ); /* Store decoder memory of last_core */ if ( st->last_core == ACELP_CORE && st->last_core_from_bs != ACELP_CORE ) @@ -860,8 +870,10 @@ static void dec_prm_tcx( } } +#ifndef FIX_1384_MSAN_stereo_tcx_core_enc if ( !st->use_partial_copy ) { +#endif if ( st->element_mode != IVAS_CPE_MDCT ) { getTCXWindowing( st->core, st->last_core, st->element_mode, st->hTcxCfg, st ); @@ -872,8 +884,9 @@ static void dec_prm_tcx( { st->dec_glr_idx = -1; } +#ifndef FIX_1384_MSAN_stereo_tcx_core_enc } - +#endif #ifdef DEBUG_MODE_TCX fprintf( pF, "\t TCX Header: %d bits: %d %d %d %d\n", st->next_bit_pos - start_bit_pos, st->tcxonly, st->core, st->tcxonly ? st->clas_dec : st->hTcxCfg->coder_type, st->hTcxCfg->tcx_curr_overlap_mode ); nbits_tcx = st->next_bit_pos; @@ -895,6 +908,17 @@ static void dec_prm_tcx( * TCX20/10 parameters *--------------------------------------------------------------------------------*/ +#ifdef FIX_1384_MSAN_stereo_tcx_core_enc + getTCXparam( st, st, hm_cfg, param, bits_common, start_bit_pos, NULL, NULL, NULL, -1 ); + + if ( *total_nbbits - bitsRead[0] < ( st->next_bit_pos - start_bit_pos ) ) + { + st->BER_detect = 1; + st->next_bit_pos = start_bit_pos + *total_nbbits - bitsRead[0]; + } + + bitsRead[0] = st->next_bit_pos - start_bit_pos; +#else if ( st->use_partial_copy == 0 ) { getTCXparam( st, st, hm_cfg, param, bits_common, start_bit_pos, NULL, NULL, NULL, -1 ); @@ -910,6 +934,7 @@ static void dec_prm_tcx( bitsRead[0] = st->next_bit_pos - start_bit_pos; } +#endif return; } diff --git a/lib_enc/ivas_tcx_core_enc.c b/lib_enc/ivas_tcx_core_enc.c index 91adac9a0f..a2f96032d0 100644 --- a/lib_enc/ivas_tcx_core_enc.c +++ b/lib_enc/ivas_tcx_core_enc.c @@ -310,7 +310,7 @@ void stereo_tcx_core_enc( } /*--------------------------------------------------------------* - * Envelope Quantization and FDNS + * LPC Envelope Quantization and FDNS *---------------------------------------------------------------*/ if ( !st->enableTcxLpc ) @@ -336,6 +336,9 @@ void stereo_tcx_core_enc( } } +#ifdef FIX_1384_MSAN_stereo_tcx_core_enc + st->acelp_cfg.midLpc = 0; +#endif last_core_orig = st->last_core; for ( n = 0; n < n_subframes; n++ ) { -- GitLab From e41c8c8b629ddb9d015417883377a846ea6185c3 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Sun, 21 Sep 2025 20:43:12 +0200 Subject: [PATCH 115/340] remove more dependencies on st->enablePlcWaveadjust --- lib_dec/ivas_tcx_core_dec.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib_dec/ivas_tcx_core_dec.c b/lib_dec/ivas_tcx_core_dec.c index 94d5c89409..b7e5c11f95 100644 --- a/lib_dec/ivas_tcx_core_dec.c +++ b/lib_dec/ivas_tcx_core_dec.c @@ -398,10 +398,12 @@ void stereo_tcx_core_dec( lsp2a_stab( st->lsp_old, st->old_Aq_12_8, M ); } +#ifndef FIX_1384_MSAN_stereo_tcx_core_enc if ( st->enablePlcWaveadjust && bfi ) { st->hPlcInfo->nbLostCmpt++; } +#endif /*--------------------------------------------------------------------------------* * TD-TCX concealment @@ -679,10 +681,12 @@ void stereo_tcx_core_dec( if ( !bfi ) { +#ifndef FIX_1384_MSAN_stereo_tcx_core_enc if ( st->enablePlcWaveadjust ) { st->hPlcInfo->nbLostCmpt = 0; } +#endif if ( param[1 + NOISE_FILL_RANGES] != 0 ) { -- GitLab From 77af76759ac8e849cd5a82d1352dd7fcda50ee88 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Mon, 22 Sep 2025 14:00:20 +0200 Subject: [PATCH 116/340] use new ivas_CLDFB_RINGBUF_GetByIdx in DEBUGGING code too --- lib_dec/ivas_binRenderer_internal.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index 8cda55017c..840b75e46f 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -1301,6 +1301,9 @@ void ivas_binaural_cldfb( float Cldfb_ImagBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *re, *im; +#endif int16_t slot_idx, subframeIdx, index_slot, idx_in, idx_lfe, maxBand, ch; /* Implement a 5 msec loops */ @@ -1396,8 +1399,14 @@ void ivas_binaural_cldfb( maxBand ); } +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[( pos_idx * BINAURAL_CHANNELS ) + ch], &re, &im, ( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx ); + mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], re, maxBand ); + mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], im, maxBand ); +#else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); +#endif } } } @@ -1445,6 +1454,9 @@ void ivas_binaural_cldfb_sf( float Cldfb_ImagBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + float *re, *im; +#endif int16_t slot_idx, subframeIdx, index_slot, idx_in, idx_lfe, maxBand, ch; int16_t slots_to_render, first_sf, last_sf; int16_t slot_index_start, slot_index_start_cldfb; @@ -1537,8 +1549,14 @@ void ivas_binaural_cldfb_sf( { for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[( pos_idx * BINAURAL_CHANNELS ) + ch], &re, &im, slot_index_start + slot_idx ); + mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], re, maxBand ); + mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], im, maxBand ); +#else mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); +#endif } } } -- GitLab From 281fb519fbbec75c15f9f291d0016c3eba5e9107 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Mon, 22 Sep 2025 18:16:35 +0200 Subject: [PATCH 117/340] [CI] make the prepare_instrumentation capable of dealing with lib_basop; see BASOP MR2260 --- scripts/prepare_instrumentation.sh | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/prepare_instrumentation.sh b/scripts/prepare_instrumentation.sh index 52549c7789..3698b12b26 100755 --- a/scripts/prepare_instrumentation.sh +++ b/scripts/prepare_instrumentation.sh @@ -200,6 +200,13 @@ if [ $? -ne 0 ]; then exit -1 fi +LIB_BASOP=0 +basop_dir="" +if [ -d $targetdir/lib_basop ]; then + LIB_BASOP=1 + basop_dir="$targetdir/lib_basop/*.c" +fi + # strip switches, to remove the macros (turn on extended globing to allow !(pattern*) matching) shopt -s extglob if coan_exists; then @@ -209,13 +216,16 @@ if coan_exists; then coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/apps/*.[hc] if [ $ISAR -eq 0 ]; then - coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_{com,dec,enc,rend,util}/!(wmc_auto*).[hc] + coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_{com,dec,enc,rend,util,debug}/!(wmc_auto*).[hc] else coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_{com,dec,enc,isar,lc3plus,rend,util,debug}/!(wmc_auto*).[hc] if [ $LC3PLUS_FFT -eq 1 ]; then coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_lc3plus/fft/!(wmc_auto*).[hc] fi fi + if [ $LIB_BASOP -eq 1 ]; then + coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_basop/!(wmc_auto*).[hc] + fi coan source --replace --no-transients -E -K --file $ifdef_list $targetdir/lib_lc3plus/!(wmc_auto*).[hc] else $scriptdir/strip_defines_cppp.sh $targetdir $ifdef_list @@ -227,21 +237,22 @@ find $targetdir -name "*.[ch]" -exec sed -i.bak -e "s/\(0x[0-9a-fA-F]*\)UL/\(\(u # run wmc_tool, exit if the command fails set -e +rm -f wmc_tool_output.txt touch wmc_tool_output.txt trap 'echo "Error calling WMC tool: $?"; cat wmc_tool_output.txt' ERR -"$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/encoder.c" "$targetdir/lib_enc/*.c" "$targetdir/lib_com/*.c" >>wmc_tool_output.txt 2>&1 -"$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/decoder.c" "$targetdir/lib_dec/*.c" "$targetdir/lib_rend/*.c" >>wmc_tool_output.txt 2>&1 +"$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/encoder.c" "$targetdir/lib_enc/*.c" "$targetdir/lib_com/*.c" "$basop_dir" >>wmc_tool_output.txt 2>&1 +"$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/decoder.c" "$targetdir/lib_dec/*.c" "$targetdir/lib_rend/*.c" "$basop_dir" >>wmc_tool_output.txt 2>&1 for bak_file in $targetdir/lib_rend/*.bak; do mv "$bak_file" "${bak_file%.*}"; done # restore fresh .c files to avoid time-consuming des-instrumentation of files by the WMC tool if [ $ISAR -eq 0 ]; then - "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/renderer.c" "$targetdir/lib_rend/*.c" >>wmc_tool_output.txt 2>&1 + "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/renderer.c" "$targetdir/lib_rend/*.c" "$basop_dir" >>wmc_tool_output.txt 2>&1 else - "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/renderer.c" "$targetdir/lib_rend/*.c" "$targetdir/lib_lc3plus/*.c" "$lc3plus_fftdir" >>wmc_tool_output.txt 2>&1 + "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/renderer.c" "$targetdir/lib_rend/*.c" "$targetdir/lib_lc3plus/*.c" "$lc3plus_fftdir" "$basop_dir" >>wmc_tool_output.txt 2>&1 for bak_file in $targetdir/lib_lc3plus/*.bak; do mv "$bak_file" "${bak_file%.*}"; done # restore fresh .c files to avoid time-consuming des-instrumentation of files by the WMC tool if [ -n "$lc3plus_fftdir" ]; then for bak_file in $targetdir/lib_lc3plus/fft/*.bak; do mv "$bak_file" "${bak_file%.*}"; done # restore fresh .c files to avoid time-consuming des-instrumentation of files by the WMC tool fi # ISAR post rend - "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/isar_post_rend.c" "$targetdir/lib_isar/*.c" "$targetdir/lib_lc3plus/*.c" "$lc3plus_fftdir" >>wmc_tool_output.txt 2>&1 + "$scriptdir/tools/$system/wmc_tool" $wmc_opt -m "$targetdir/apps/isar_post_rend.c" "$targetdir/lib_isar/*.c" "$targetdir/lib_lc3plus/*.c" "$lc3plus_fftdir" "$basop_dir" >>wmc_tool_output.txt 2>&1 fi trap - ERR set +e -- GitLab From b0ec5f47effb63aaeda6c7a8edb4a88b7dbb3de3 Mon Sep 17 00:00:00 2001 From: vaclav Date: Tue, 23 Sep 2025 14:52:09 +0200 Subject: [PATCH 118/340] address HRTF leftovers --- apps/decoder.c | 2 +- apps/renderer.c | 6 ++--- lib_com/common_api_types.h | 2 +- lib_dec/ivas_binRenderer_internal.c | 8 ++---- lib_dec/lib_dec.c | 4 +-- lib_dec/lib_dec.h | 2 +- lib_rend/ivas_crend.c | 14 +++++------ lib_rend/ivas_stat_rend.h | 5 ++-- lib_rend/lib_rend.c | 38 ++++++++++++++--------------- lib_rend/lib_rend.h | 6 ++--- lib_util/hrtf_file_reader.c | 21 +++++++--------- lib_util/hrtf_file_reader.h | 4 +-- 12 files changed, 52 insertions(+), 60 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 41089d057e..444367122d 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -150,7 +150,7 @@ typedef struct hrtfFileReader *hrtfReader; char *hrtfFileName; - IVAS_DEC_HRTF_HANDLE *hHrtfTD; + IVAS_DEC_HRTF_TD_HANDLE *hHrtfTD; IVAS_DEC_HRTF_STATISTICS_HANDLE *hHrtfStatistics; diff --git a/apps/renderer.c b/apps/renderer.c index cf5a7e33d2..caa1ce00d8 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -682,7 +682,7 @@ int main( IVAS_DEC_HRTF_CREND_HANDLE *hHrtfCrend = NULL; IVAS_DEC_HRTF_FASTCONV_HANDLE *hHrtfFastConv = NULL; IVAS_DEC_HRTF_PARAMBIN_HANDLE *hHrtfParambin = NULL; - IVAS_DEC_HRTF_HANDLE *hHrtfTD = NULL; + IVAS_DEC_HRTF_TD_HANDLE *hHrtfTD = NULL; IVAS_DEC_HRTF_STATISTICS_HANDLE *hHrtfStatistics = NULL; IsmPositionProvider *positionProvider = NULL; LfeRoutingConfig *lfeRoutingConfigs[RENDERER_MAX_MC_INPUTS]; @@ -961,9 +961,9 @@ int main( if ( hrtfFileReader != NULL ) { - if ( ( error = IVAS_REND_GetHrtfHandle( hIvasRend, &hHrtfTD ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_REND_GetHrtfTdHandle( hIvasRend, &hHrtfTD ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nIVAS_REND_GetHrtfHandle failed: %s\n\n", ivas_error_to_string( error ) ); + fprintf( stderr, "\nIVAS_REND_GetHrtfTdHandle failed: %s\n\n", ivas_error_to_string( error ) ); goto cleanup; } diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index f5b13c0dea..80c90d6b31 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -176,7 +176,7 @@ typedef enum typedef struct ivas_masa_metadata_frame_struct *IVAS_MASA_METADATA_HANDLE; typedef struct ivas_masa_decoder_ext_out_meta_struct *IVAS_MASA_DECODER_EXT_OUT_META_HANDLE; -typedef struct ivas_hrtf_TDREND_HRFILT_FiltSet_struct *IVAS_DEC_HRTF_HANDLE; +typedef struct ivas_hrtf_TDREND_HRFILT_FiltSet_struct *IVAS_DEC_HRTF_TD_HANDLE; typedef struct ivas_hrtf_crend_structure *IVAS_DEC_HRTF_CREND_HANDLE; typedef struct ivas_hrtf_fastconv_struct *IVAS_DEC_HRTF_FASTCONV_HANDLE; typedef struct ivas_hrtf_parambin_struct *IVAS_DEC_HRTF_PARAMBIN_HANDLE; diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index 8cda55017c..d6ba1e0c15 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -795,8 +795,6 @@ ivas_error ivas_rend_openCldfbRend( int16_t convBand; ivas_error error; - error = IVAS_ERR_OK; - /*-----------------------------------------------------------------* * prepare library opening *-----------------------------------------------------------------*/ @@ -866,7 +864,7 @@ ivas_error ivas_rend_openCldfbRend( pCldfbRend->hCldfbRend = hBinRenderer; - return error; + return IVAS_ERR_OK; } @@ -885,8 +883,6 @@ ivas_error ivas_binRenderer_open( ivas_error error; const IVAS_ROOM_ACOUSTICS_CONFIG_DATA *pRoomAcoustics; - error = IVAS_ERR_OK; - /*-----------------------------------------------------------------* * prepare library opening *-----------------------------------------------------------------*/ @@ -1045,7 +1041,7 @@ ivas_error ivas_binRenderer_open( /* Copy the handles to main handle */ st_ivas->hBinRenderer = hBinRenderer; - return error; + return IVAS_ERR_OK; } diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0f54199425..95ebbcb1c7 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -2943,8 +2943,8 @@ ivas_error IVAS_DEC_FeedCustomLsData( *---------------------------------------------------------------------*/ ivas_error IVAS_DEC_GetHrtfTDrendHandle( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - IVAS_DEC_HRTF_HANDLE **hHrtfTD /* o : HRTF handle */ + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + IVAS_DEC_HRTF_TD_HANDLE **hHrtfTD /* o : HRTF handle */ ) { if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL || hIvasDec->st_ivas->hHrtfTD == NULL ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 18298954c4..3fff28470d 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -457,7 +457,7 @@ ivas_error IVAS_DEC_FeedCustomLsData( /*! r: error code */ ivas_error IVAS_DEC_GetHrtfTDrendHandle( IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - IVAS_DEC_HRTF_HANDLE **hHrtfTD /* o : HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE **hHrtfTD /* o : HRTF handle */ ); /*! r: error code */ diff --git a/lib_rend/ivas_crend.c b/lib_rend/ivas_crend.c index b120fd6c6a..9134667502 100644 --- a/lib_rend/ivas_crend.c +++ b/lib_rend/ivas_crend.c @@ -104,15 +104,15 @@ ivas_error ivas_Crend_hrtf_init( *------------------------------------------------------------------------*/ static ivas_error ivas_hrtf_open( - HRTFS_HANDLE *hHrtf_out /* o : HRTF handle */ + HRTFS_CREND_HANDLE *hHrtf_out /* o : HRTF handle */ ) { - HRTFS_HANDLE hHrtf; + HRTFS_CREND_HANDLE hHrtf; ivas_error error; if ( *hHrtf_out == NULL ) { - if ( ( hHrtf = (HRTFS_HANDLE) malloc( sizeof( HRTFS_DATA ) ) ) == NULL ) + if ( ( hHrtf = (HRTFS_CREND_HANDLE) malloc( sizeof( HRTFS_CREND_DATA ) ) ) == NULL ) { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for Crend HRTFS Handle\n" ); } @@ -140,7 +140,7 @@ static ivas_error ivas_hrtf_open( *------------------------------------------------------------------------*/ static void ivas_hrtf_close( - HRTFS_HANDLE *hHrtf /* i/o: HRTF handle */ + HRTFS_CREND_HANDLE *hHrtf /* i/o: Crend HRTF handle */ ) { if ( hHrtf == NULL || *hHrtf == NULL ) @@ -172,7 +172,7 @@ static ivas_error ivas_rend_initCrend( int16_t i, j, tmp, tmp2; int16_t nchan_in; IVAS_REND_AudioConfigType inConfigType; - HRTFS_HANDLE hHrtf; + HRTFS_CREND_HANDLE hHrtf; ivas_error error; inConfigType = getAudioConfigType( inConfig ); @@ -1158,13 +1158,11 @@ ivas_error ivas_rend_openCrend( { int16_t i, subframe_length; int32_t max_total_ir_len; - HRTFS_HANDLE hHrtf; + HRTFS_CREND_HANDLE hHrtf; CREND_HANDLE hCrend; ivas_error error; int16_t pos_idx; - error = IVAS_ERR_OK; - if ( ( error = ivas_rend_initCrendWrapper( pCrend, num_poses ) ) != IVAS_ERR_OK ) { return error; diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index 69fcb30fb8..2552ef8ca1 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -1182,7 +1182,7 @@ typedef struct ivas_hrtf_crend_structure uint16_t *pIndex_frequency_max_dyn[MAX_INTERN_CHANNELS][BINAURAL_CHANNELS]; uint16_t *pIndex_frequency_max_diffuse_dyn[BINAURAL_CHANNELS]; -} HRTFS_DATA, *HRTFS_HANDLE, HRTFS_CREND_DATA, *HRTFS_CREND_HANDLE; // VE: all instance of HRTFS_DATAand *HRTFS_HANDLE should be renamed to HRTFS_CREND_DATA and *HRTFS_CREND_HANDLE +} HRTFS_CREND_DATA, *HRTFS_CREND_HANDLE; /* Main Crend structure */ typedef struct ivas_crend_state_t @@ -1209,7 +1209,7 @@ typedef struct ivas_binaural_crend_wrapper_struct { int32_t binaural_latency_ns; CREND_HANDLE hCrend[MAX_HEAD_ROT_POSES]; - HRTFS_HANDLE hHrtfCrend; + HRTFS_CREND_HANDLE hHrtfCrend; } CREND_WRAPPER, *CREND_WRAPPER_HANDLE; @@ -1272,6 +1272,7 @@ typedef struct ivas_hrtf_statistics_struct float *average_energy_r_dyn; float *inter_aural_coherence_dyn; int16_t fromROM; /* Flag that indicates that the pointers point to tables in ROM (controls init/dealloc).*/ + } HRTFS_STATISTICS, *HRTFS_STATISTICS_HANDLE; diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 52994cdba1..c917353459 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -183,10 +183,10 @@ typedef struct typedef struct hrtf_handles { - IVAS_DEC_HRTF_CREND_HANDLE hSetOfHRTF; + IVAS_DEC_HRTF_CREND_HANDLE hHrtfCrend; IVAS_DEC_HRTF_FASTCONV_HANDLE hHrtfFastConv; IVAS_DEC_HRTF_PARAMBIN_HANDLE hHrtfParambin; - IVAS_DEC_HRTF_HANDLE hHrtfTD; + IVAS_DEC_HRTF_TD_HANDLE hHrtfTD; IVAS_DEC_HRTF_STATISTICS_HANDLE hHrtfStatistics; } hrtf_handles; @@ -1672,7 +1672,7 @@ static ivas_error setRendInputActiveIsm( } else if ( outConfig == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_IR ) { - if ( ( error = ivas_rend_openCrend( &inputIsm->crendWrapper, IVAS_AUDIO_CONFIG_7_1_4, outConfig, hRendCfg, hrtfs->hSetOfHRTF, hrtfs->hHrtfStatistics, *rendCtx.pOutSampleRate, 1, rendCtx.pSplitRendWrapper != NULL ? rendCtx.pSplitRendWrapper->multiBinPoseData.num_poses : 1 ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_rend_openCrend( &inputIsm->crendWrapper, IVAS_AUDIO_CONFIG_7_1_4, outConfig, hRendCfg, hrtfs->hHrtfCrend, hrtfs->hHrtfStatistics, *rendCtx.pOutSampleRate, 1, rendCtx.pSplitRendWrapper != NULL ? rendCtx.pSplitRendWrapper->multiBinPoseData.num_poses : 1 ) ) != IVAS_ERR_OK ) { return error; } @@ -2631,7 +2631,7 @@ static ivas_error setRendInputActiveMc( if ( getAudioConfigType( outConfig ) == IVAS_REND_AUDIO_CONFIG_TYPE_BINAURAL ) { - if ( ( error = initMcBinauralRendering( inputMc, inConfig, outConfig, hRendCfg, hrtfs->hSetOfHRTF, hrtfs->hHrtfStatistics, FALSE ) ) != IVAS_ERR_OK ) + if ( ( error = initMcBinauralRendering( inputMc, inConfig, outConfig, hRendCfg, hrtfs->hHrtfCrend, hrtfs->hHrtfStatistics, FALSE ) ) != IVAS_ERR_OK ) { return error; } @@ -2948,7 +2948,7 @@ static ivas_error setRendInputActiveSba( } } - if ( ( error = updateSbaPanGains( inputSba, outConfig, hRendCfg, hrtfs->hSetOfHRTF, hrtfs->hHrtfStatistics ) ) != IVAS_ERR_OK ) + if ( ( error = updateSbaPanGains( inputSba, outConfig, hRendCfg, hrtfs->hHrtfCrend, hrtfs->hHrtfStatistics ) ) != IVAS_ERR_OK ) { return error; } @@ -3241,7 +3241,7 @@ ivas_error IVAS_REND_Open( hIvasRend->hHrtfs.hHrtfFastConv = NULL; hIvasRend->hHrtfs.hHrtfParambin = NULL; hIvasRend->hHrtfs.hHrtfTD = NULL; - hIvasRend->hHrtfs.hSetOfHRTF = NULL; + hIvasRend->hHrtfs.hHrtfCrend = NULL; hIvasRend->hHrtfs.hHrtfStatistics = NULL; if ( asHrtfBinary ) { @@ -3249,7 +3249,7 @@ ivas_error IVAS_REND_Open( { return error; } - if ( ( error = ivas_HRTF_CRend_binary_open( &( hIvasRend->hHrtfs.hSetOfHRTF ) ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_HRTF_CRend_binary_open( &( hIvasRend->hHrtfs.hHrtfCrend ) ) ) != IVAS_ERR_OK ) { return error; } @@ -3947,7 +3947,7 @@ ivas_error IVAS_REND_ConfigureCustomInputLoudspeakerLayout( inputMc->base.inConfig, hIvasRend->outputConfig, hIvasRend->hRendererConfig, - hIvasRend->hHrtfs.hSetOfHRTF, + hIvasRend->hHrtfs.hHrtfCrend, hIvasRend->hHrtfs.hHrtfStatistics, FALSE ) ) != IVAS_ERR_OK ) { @@ -4807,7 +4807,7 @@ ivas_error IVAS_REND_SetHeadRotation( hIvasRend->inputsMc[i].base.inConfig, hIvasRend->outputConfig, hIvasRend->hRendererConfig, - hIvasRend->hHrtfs.hSetOfHRTF, + hIvasRend->hHrtfs.hHrtfCrend, hIvasRend->hHrtfs.hHrtfStatistics, TRUE ) ) != IVAS_ERR_OK ) { @@ -4871,7 +4871,7 @@ ivas_error IVAS_REND_DisableHeadRotation( hIvasRend->inputsMc[i].base.inConfig, hIvasRend->outputConfig, hIvasRend->hRendererConfig, - hIvasRend->hHrtfs.hSetOfHRTF, + hIvasRend->hHrtfs.hHrtfCrend, hIvasRend->hHrtfs.hHrtfStatistics, TRUE ) ) != IVAS_ERR_OK ) { @@ -7961,7 +7961,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, @@ -8093,7 +8093,7 @@ void IVAS_REND_Close( /* Parametric binauralizer HRTF filters */ ivas_HRTF_binary_close( &( hIvasRend->hHrtfs.hHrtfTD ) ); - ivas_HRTF_CRend_binary_close( &( hIvasRend->hHrtfs.hSetOfHRTF ) ); + ivas_HRTF_CRend_binary_close( &( hIvasRend->hHrtfs.hHrtfCrend ) ); ivas_HRTF_fastconv_binary_close( &( hIvasRend->hHrtfs.hHrtfFastConv ) ); ivas_HRTF_parambin_binary_close( &( hIvasRend->hHrtfs.hHrtfParambin ) ); ivas_HRTF_statistics_close( &( hIvasRend->hHrtfs.hHrtfStatistics ) ); @@ -8250,14 +8250,14 @@ int32_t IVAS_REND_GetCntFramesLimited( /*---------------------------------------------------------------------* - * IVAS_REND_GetHrtfHandle( ) + * IVAS_REND_GetHrtfTdHandle( ) * * *---------------------------------------------------------------------*/ -ivas_error IVAS_REND_GetHrtfHandle( - IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ - IVAS_DEC_HRTF_HANDLE **hHrtfTD /* o : HRTF handle */ +ivas_error IVAS_REND_GetHrtfTdHandle( + IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ + IVAS_DEC_HRTF_TD_HANDLE **hHrtfTD /* o : TD rend. HRTF handle */ ) { if ( hIvasRend == NULL || hIvasRend->hHrtfs.hHrtfTD == NULL ) @@ -8279,15 +8279,15 @@ ivas_error IVAS_REND_GetHrtfHandle( ivas_error IVAS_REND_GetHrtfCRendHandle( IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ - IVAS_DEC_HRTF_CREND_HANDLE **hSetOfHRTF /* o : Set of HRTF handle */ + IVAS_DEC_HRTF_CREND_HANDLE **hHrtfCrend /* o : Crend HRTF handle */ ) { - if ( hIvasRend == NULL || hIvasRend->hHrtfs.hSetOfHRTF == NULL ) + if ( hIvasRend == NULL || hIvasRend->hHrtfs.hHrtfCrend == NULL ) { return IVAS_ERR_WRONG_PARAMS; } - *hSetOfHRTF = &hIvasRend->hHrtfs.hSetOfHRTF; + *hHrtfCrend = &hIvasRend->hHrtfs.hHrtfCrend; return IVAS_ERR_OK; } diff --git a/lib_rend/lib_rend.h b/lib_rend/lib_rend.h index c03dd72485..2e635ad479 100644 --- a/lib_rend/lib_rend.h +++ b/lib_rend/lib_rend.h @@ -194,15 +194,15 @@ ivas_error IVAS_REND_GetDelay( ); /*! r: error code */ -ivas_error IVAS_REND_GetHrtfHandle( +ivas_error IVAS_REND_GetHrtfTdHandle( IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS render handle */ - IVAS_DEC_HRTF_HANDLE **hHrtfTD /* o : HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE **hHrtfTD /* o : TD rend. HRTF handle */ ); /*! r: error code */ ivas_error IVAS_REND_GetHrtfCRendHandle( IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ - IVAS_DEC_HRTF_CREND_HANDLE **hSetOfHRTF /* o : Set of HRTF handle */ + IVAS_DEC_HRTF_CREND_HANDLE **hHrtfCrend /* o : Crend HRTF handle */ ); ivas_error IVAS_REND_GetHrtfFastConvHandle( diff --git a/lib_util/hrtf_file_reader.c b/lib_util/hrtf_file_reader.c index 3347a0c9ab..66c886b7b6 100644 --- a/lib_util/hrtf_file_reader.c +++ b/lib_util/hrtf_file_reader.c @@ -436,8 +436,8 @@ static ivas_error TDREND_LoadBSplineBinaryITD( --------------------------------------------------------------------*/ static ivas_error TDREND_LoadBSplineBinary( - IVAS_DEC_HRTF_HANDLE HrFiltSet_p, /* i/o: HR filter model parameter structure */ - FILE *f_hrtf /* i : HR filter data file handle */ + IVAS_DEC_HRTF_TD_HANDLE HrFiltSet_p, /* i/o: HR filter model parameter structure */ + FILE *f_hrtf /* i : HR filter data file handle */ ) { ModelParams_t *model; @@ -882,9 +882,9 @@ ivas_error load_reverb_binary( --------------------------------------------------------------------*/ static ivas_error TDREND_MIX_LoadHRTF( - FILE *f_hrtf, /* i/o: File pointer to HRTF file */ - const int32_t sampleRate, /* i : sample rate */ - IVAS_DEC_HRTF_HANDLE HrFiltSet_p /* o : Loaded HR filter set */ + FILE *f_hrtf, /* i/o: File pointer to HRTF file */ + const int32_t sampleRate, /* i : sample rate */ + IVAS_DEC_HRTF_TD_HANDLE HrFiltSet_p /* o : Loaded HR filter set */ ) { int16_t tmp; @@ -1006,7 +1006,7 @@ static ivas_error TDREND_MIX_LoadHRTF( *---------------------------------------------------------------------*/ ivas_error load_TDrend_HRTF_binary( - IVAS_DEC_HRTF_HANDLE hHrtf, /* i/o: HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE hHrtf, /* i/o: TD rend. HRTF handle */ const int32_t sampleRate, /* i : sample rate */ const hrtfFileReader *hrtfReader /* i : pointer to hrtfFileReader handle */ ) @@ -1029,7 +1029,7 @@ ivas_error load_TDrend_HRTF_binary( *---------------------------------------------------------------------*/ void destroy_td_hrtf( - IVAS_DEC_HRTF_HANDLE *hHrtf /* i/o: HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE *hHrtf /* i/o: TD rend. HRTF handle */ ) { int16_t i; @@ -1099,8 +1099,8 @@ void destroy_td_hrtf( *---------------------------------------------------------------------*/ static ivas_error create_Crend_HRTF_from_rawdata( - HRTFS_HANDLE *hHRTF, /* i/o: HRTF CRend handle */ - char *hrtf_data /* i : pointer to binary file */ + HRTFS_CREND_HANDLE *hHRTF, /* i/o: HRTF CRend handle */ + char *hrtf_data /* i : pointer to binary file */ ) { int16_t i, j, k; @@ -1112,7 +1112,6 @@ static ivas_error create_Crend_HRTF_from_rawdata( ivas_error error; Word16 factorQ; - if ( hrtf_data == NULL ) { return IVAS_ERR_UNEXPECTED_NULL_POINTER; @@ -1254,7 +1253,6 @@ static ivas_error create_Crend_HRTF_from_rawdata( } } - /* coeff_im (the size depends on pIndex_frequency_max) */ for ( i = 0; i < ( *hHRTF )->max_num_ir; i++ ) { @@ -2095,7 +2093,6 @@ void destroy_hrtf_statistics( IVAS_DEC_HRTF_STATISTICS_HANDLE *hHrtfStatistics /* i/o: HRTF statistics handle */ ) { - if ( ( hHrtfStatistics != NULL ) && ( *hHrtfStatistics != NULL ) && ( ( *hHrtfStatistics )->fromROM == FALSE ) ) { if ( ( *hHrtfStatistics )->average_energy_l != NULL ) diff --git a/lib_util/hrtf_file_reader.h b/lib_util/hrtf_file_reader.h index 727e660dfb..938b5c1595 100644 --- a/lib_util/hrtf_file_reader.h +++ b/lib_util/hrtf_file_reader.h @@ -99,7 +99,7 @@ void hrtfFileReader_close( *---------------------------------------------------------------------*/ ivas_error load_TDrend_HRTF_binary( - IVAS_DEC_HRTF_HANDLE hHrtf, /* i/o: HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE hHrtf, /* i/o: TD rend. HRTF handle */ const int32_t sampleRate, /* i : sample rate */ const hrtfFileReader *hrtfReader /* i : pointer to hrtfFileReader handle */ ); @@ -192,7 +192,7 @@ void destroy_parambin_hrtf( *---------------------------------------------------------------------*/ void destroy_td_hrtf( - IVAS_DEC_HRTF_HANDLE *hHRTF /* i/o: HRTF handle */ + IVAS_DEC_HRTF_TD_HANDLE *hHRTF /* i/o: TD rend. HRTF handle */ ); /*---------------------------------------------------------------------* -- GitLab From c587e4dfa79af18844a2772a9eb07d545e8a1d27 Mon Sep 17 00:00:00 2001 From: vaclav Date: Tue, 23 Sep 2025 15:46:20 +0200 Subject: [PATCH 119/340] clang-format --- lib_rend/lib_rend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index c917353459..31efac60e4 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7961,7 +7961,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From e7e90bc604f4e7b1f8664f5d4d270d09b3ebc0a2 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 23 Sep 2025 16:49:34 +0200 Subject: [PATCH 120/340] add test for comparing flt and fx ambi_converter for BE --- .../test_be_ambi_converter_fixed_to_float.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/test_be_ambi_converter_fixed_to_float.py diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py new file mode 100644 index 0000000000..238a9ed695 --- /dev/null +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -0,0 +1,99 @@ +import pytest +import subprocess +import sys +from enum import Enum +from pathlib import Path +from tempfile import TemporaryDirectory + +HERE = Path(__file__).absolute().parent +TESTV_DIR = HERE.parent / "scripts/testv" + +sys.path.append(str(HERE.parent / "scripts")) +from pyaudio3dtools import audiofile, audioarray + + +class AMBI_CONVENTION(int, Enum): + ACN_SN3D = 0 + ACN_N3D = 1 + FUMA_MAXN = 2 + FUMA_FUMA = 3 + SID_SN3D = 4 + SID_N3D = 5 + + +def run_ambi_converter( + bin_path: Path, + infile: Path, + outfile: str, + convention_in: AMBI_CONVENTION, + convention_out: AMBI_CONVENTION, +): + cmd = [ + str(bin_path), + str(infile), + outfile, + f"{int(convention_in)}", + f"{int(convention_out)}", + ] + + p = subprocess.run(cmd, capture_output=True) + if p.returncode != 0: + pytest.fail( + f"Ambisonics converter run failed: {p.stdout.decode('utf8') + p.stderr.decode('utf8')}" + ) + + +INPUT_FILES = [TESTV_DIR / "stv3OA48c.wav"] +CONVENTIONS = [c.value for c in AMBI_CONVENTION] +AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" +AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" + + +@pytest.mark.parametrize("infile", INPUT_FILES) +@pytest.mark.parametrize("convention_out", CONVENTIONS) +@pytest.mark.parametrize("convention_in", CONVENTIONS) +def test_ambi_converter( + infile: Path, + convention_in: AMBI_CONVENTION, + convention_out: AMBI_CONVENTION, + # needs to be passed to correctly report errors + test_info, +): + if ( + convention_out != AMBI_CONVENTION.ACN_SN3D + and convention_in != AMBI_CONVENTION.ACN_SN3D + ): + pytest.skip("One of in and out convention needs to be ACN_SN3D") + + with TemporaryDirectory() as tmp_dir: + outfile_base = Path(tmp_dir) / ( + infile.stem + f"-{str(convention_in)}-to-{str(convention_out)}" + ) + + outfile_flt = str(outfile_base) + "-flt.wav" + outfile_fx = str(outfile_base) + "-fx.wav" + + run_ambi_converter( + AMBI_CONVERTER_PATH_FLOAT, + infile, + outfile_flt, + convention_in, + convention_out, + ) + + run_ambi_converter( + AMBI_CONVERTER_PATH_FIXED, + infile, + outfile_fx, + convention_in, + convention_out, + ) + + s_flt, _ = audiofile.readfile(outfile_flt) + s_fx, _ = audiofile.readfile(outfile_fx) + + cmp_result = audioarray.compare(s_flt, s_fx, fs=48000, per_frame=False) + if not cmp_result["bitexact"]: + pytest.fail( + f"Difference between float and fixed ambi_converter output found! Max abs diff: {cmp_result['max_abs_diff']}" + ) -- GitLab From 3b669c894593b720d80ef4aaeb90a8951093d7f4 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 24 Sep 2025 08:58:15 +0200 Subject: [PATCH 121/340] introduce dummy threshold and use xfail instead of skip --- tests/test_be_ambi_converter_fixed_to_float.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 238a9ed695..0ff1499809 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -47,6 +47,7 @@ INPUT_FILES = [TESTV_DIR / "stv3OA48c.wav"] CONVENTIONS = [c.value for c in AMBI_CONVENTION] AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" +THRESHOLD_FAIL = 2 @pytest.mark.parametrize("infile", INPUT_FILES) @@ -63,7 +64,7 @@ def test_ambi_converter( convention_out != AMBI_CONVENTION.ACN_SN3D and convention_in != AMBI_CONVENTION.ACN_SN3D ): - pytest.skip("One of in and out convention needs to be ACN_SN3D") + pytest.xfail("One of in and out convention needs to be ACN_SN3D") with TemporaryDirectory() as tmp_dir: outfile_base = Path(tmp_dir) / ( @@ -93,7 +94,7 @@ def test_ambi_converter( s_fx, _ = audiofile.readfile(outfile_fx) cmp_result = audioarray.compare(s_flt, s_fx, fs=48000, per_frame=False) - if not cmp_result["bitexact"]: + if abs(cmp_result["max_abs_diff"]) > THRESHOLD_FAIL: pytest.fail( f"Difference between float and fixed ambi_converter output found! Max abs diff: {cmp_result['max_abs_diff']}" ) -- GitLab From 6787be9c192062160e248a5a49c688d0862091ef Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 24 Sep 2025 10:36:16 +0200 Subject: [PATCH 122/340] remove strip_split_rendering.{py,sh} --- scripts/strip_split_rendering.py | 34 -------------------------------- scripts/strip_split_rendering.sh | 33 ------------------------------- 2 files changed, 67 deletions(-) delete mode 100644 scripts/strip_split_rendering.py delete mode 100755 scripts/strip_split_rendering.sh diff --git a/scripts/strip_split_rendering.py b/scripts/strip_split_rendering.py deleted file mode 100644 index 40da43bbcb..0000000000 --- a/scripts/strip_split_rendering.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 - -# -# (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, -# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -# contributors to this repository. All Rights Reserved. -# -# This software is protected by copyright law and by international treaties. -# The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, -# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -# contributors to this repository retain full ownership rights in their respective contributions in -# the software. This notice grants no license of any kind, including but not limited to patent -# license, nor is any license granted by implication, estoppel or otherwise. -# -# Contributors are required to enter into the IVAS codec Public Collaboration agreement before making -# contributions. -# -# This software is provided "AS IS", without any express or implied warranties. The software is in the -# development stage. It is intended exclusively for experts who have experience with such software and -# solely for the purpose of inspection. All implied warranties of non-infringement, merchantability -# and fitness for a particular purpose are hereby disclaimed and excluded. -# -# Any dispute, controversy or claim arising under or in relation to providing this software shall be -# submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in -# accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and -# the United Nations Convention on Contracts on the International Sales of Goods. -# - -import glob -import os diff --git a/scripts/strip_split_rendering.sh b/scripts/strip_split_rendering.sh deleted file mode 100755 index f663b72243..0000000000 --- a/scripts/strip_split_rendering.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# -# (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, -# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -# contributors to this repository. All Rights Reserved. -# -# This software is protected by copyright law and by international treaties. -# The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, -# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -# contributors to this repository retain full ownership rights in their respective contributions in -# the software. This notice grants no license of any kind, including but not limited to patent -# license, nor is any license granted by implication, estoppel or otherwise. -# -# Contributors are required to enter into the IVAS codec Public Collaboration agreement before making -# contributions. -# -# This software is provided "AS IS", without any express or implied warranties. The software is in the -# development stage. It is intended exclusively for experts who have experience with such software and -# solely for the purpose of inspection. All implied warranties of non-infringement, merchantability -# and fitness for a particular purpose are hereby disclaimed and excluded. -# -# Any dispute, controversy or claim arising under or in relation to providing this software shall be -# submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in -# accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and -# the United Nations Convention on Contracts on the International Sales of Goods. -# - -OUTDIR=$1 -- GitLab From e3e65b3d99ae4d08dbd7559ce5654219796a2c3f Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 11:38:05 +0200 Subject: [PATCH 123/340] Remove unused code under DEBUGGING --- lib_com/ivas_prot.h | 14 -- lib_dec/ivas_binRenderer_internal.c | 305 ---------------------------- lib_dec/ivas_jbm_dec.c | 6 - 3 files changed, 325 deletions(-) diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index b702dc027f..eb75ab124d 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -5161,20 +5161,6 @@ ivas_error ivas_allocate_binaural_hrtf( const int16_t allocate_init_flag /* i : Memory allocation flag */ ); -#ifdef DEBUGGING -void ivas_binaural_cldfb( - Decoder_Struct *st_ivas, /* i/o: IVAS decoder structure */ - float *output_f[] /* i/o: synthesized core-coder transport channels/DirAC output */ -); - -void ivas_binaural_cldfb_sf( - Decoder_Struct *st_ivas, /* i/o: IVAS decoder structure */ - const int16_t n_samples_to_render, /* i : output frame length per channel */ - const int16_t slot_size, /* i : JBM slot size */ - float *output_f[] /* i/o: synthesized core-coder transport channels/DirAC output */ -); -#endif - void ivas_binRenderer( BINAURAL_RENDERER_HANDLE hBinRenderer, /* i/o: binaural renderer handle */ const MULTI_BIN_REND_POSE_DATA *pMultiBinPoseData, /* i/o: pose correction data handle */ diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index 840b75e46f..13282efdd8 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -1285,311 +1285,6 @@ void ivas_binaural_add_LFE( } -#ifdef DEBUGGING -/*-------------------------------------------------------------------------* - * ivas_binaural_cldfb() - * - * Perform CLDFB analysis, fastconv binaural rendering and CLDFB synthesis - *-------------------------------------------------------------------------*/ - -void ivas_binaural_cldfb( - Decoder_Struct *st_ivas, /* i/o: IVAS decoder structure */ - float *output_f[] /* i/o: synthesized core-coder transport channels/DirAC output */ -) -{ - float Cldfb_RealBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *re, *im; -#endif - int16_t slot_idx, subframeIdx, index_slot, idx_in, idx_lfe, maxBand, ch; - - /* Implement a 5 msec loops */ - maxBand = (int16_t) ( ( CLDFB_NO_CHANNELS_MAX * st_ivas->hDecoderConfig->output_Fs ) / 48000 ); - - for ( subframeIdx = 0; subframeIdx < ( CLDFB_NO_COL_MAX / MAX_PARAM_SPATIAL_SUBFRAMES ); subframeIdx++ ) - { - for ( slot_idx = 0; slot_idx < MAX_PARAM_SPATIAL_SUBFRAMES; slot_idx++ ) - { - index_slot = subframeIdx * MAX_PARAM_SPATIAL_SUBFRAMES + slot_idx; - - /* Implement CLDFB analysis */ - idx_in = 0; - idx_lfe = 0; - - for ( ch = 0; ch < ( st_ivas->hIntSetup.nchan_out_woLFE + st_ivas->hIntSetup.num_lfe ); ch++ ) - { - if ( ( st_ivas->hIntSetup.num_lfe > 0 ) && ( st_ivas->hIntSetup.index_lfe[idx_lfe] == ch ) ) - { - if ( idx_lfe < ( st_ivas->hIntSetup.num_lfe - 1 ) ) - { - idx_lfe++; - } - } - else - { - cldfbAnalysis_ts( &( output_f[ch][maxBand * index_slot] ), Cldfb_RealBuffer[idx_in][slot_idx], Cldfb_ImagBuffer[idx_in][slot_idx], maxBand, st_ivas->cldfbAnaDec[idx_in] ); - idx_in++; - } - } - /*LFE handling for split rendering cases*/ - if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) - { - for ( idx_lfe = 0; idx_lfe < st_ivas->hIntSetup.num_lfe; idx_lfe++ ) - { - ch = st_ivas->hIntSetup.index_lfe[idx_lfe]; - cldfbAnalysis_ts( &( output_f[ch][maxBand * index_slot] ), Cldfb_RealBuffer[idx_in][slot_idx], Cldfb_ImagBuffer[idx_in][slot_idx], maxBand, st_ivas->cldfbAnaDec[idx_in] ); - idx_in++; - } - - if ( st_ivas->hSplitBinRend->hCldfbDataOut != NULL ) - { - for ( ch = 0; ch < ( st_ivas->hIntSetup.nchan_out_woLFE + st_ivas->hIntSetup.num_lfe ); ch++ ) - { - mvr2r( Cldfb_RealBuffer[ch][slot_idx], st_ivas->hSplitBinRend->hCldfbDataOut->Cldfb_RealBuffer[ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); - mvr2r( Cldfb_ImagBuffer[ch][slot_idx], st_ivas->hSplitBinRend->hCldfbDataOut->Cldfb_ImagBuffer[ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); - } - st_ivas->hSplitBinRend->hCldfbDataOut->config = st_ivas->hIntSetup.output_config; - } - } - } - - /* Implement binaural rendering */ - ivas_binRenderer( - st_ivas->hBinRenderer, - &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, - st_ivas->hCombinedOrientationData, - JBM_CLDFB_SLOTS_IN_SUBFRAME, -#ifdef SPLIT_REND_WITH_HEAD_ROT_DEBUG - NULL, -#endif - Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, - Cldfb_RealBuffer, Cldfb_ImagBuffer ); - - if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) - { - int16_t pos_idx; - for ( slot_idx = 0; slot_idx < JBM_CLDFB_SLOTS_IN_SUBFRAME; slot_idx++ ) - { - if ( st_ivas->hIntSetup.num_lfe > 0 ) - { - v_multc( Cldfb_RealBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], GAIN_LFE, Cldfb_RealBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], maxBand ); - v_multc( Cldfb_ImagBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], GAIN_LFE, Cldfb_ImagBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], maxBand ); - } - } - - for ( pos_idx = 0; pos_idx < st_ivas->hBinRenderer->numPoses; pos_idx++ ) - { - for ( slot_idx = 0; slot_idx < JBM_CLDFB_SLOTS_IN_SUBFRAME; slot_idx++ ) - { - for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) - { - if ( st_ivas->hIntSetup.num_lfe > 0 ) - { - v_add( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], - Cldfb_RealBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], - Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], - maxBand ); - - v_add( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], - Cldfb_ImagBuffer[st_ivas->hIntSetup.nchan_out_woLFE][slot_idx], - Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], - maxBand ); - } - -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[( pos_idx * BINAURAL_CHANNELS ) + ch], &re, &im, ( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx ); - mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], re, maxBand ); - mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], im, maxBand ); -#else - mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); - mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][( subframeIdx * JBM_CLDFB_SLOTS_IN_SUBFRAME ) + slot_idx], maxBand ); -#endif - } - } - } - } - - /* update combined orientation access index */ - ivas_combined_orientation_update_index( st_ivas->hCombinedOrientationData, maxBand * MAX_PARAM_SPATIAL_SUBFRAMES ); - - /* Implement CLDFB synthesis */ - for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) - { - float *RealBuffer[MAX_PARAM_SPATIAL_SUBFRAMES]; - float *ImagBuffer[MAX_PARAM_SPATIAL_SUBFRAMES]; - - index_slot = subframeIdx * MAX_PARAM_SPATIAL_SUBFRAMES; - - for ( slot_idx = 0; slot_idx < MAX_PARAM_SPATIAL_SUBFRAMES; slot_idx++ ) - { - RealBuffer[slot_idx] = Cldfb_RealBuffer_Binaural[0][ch][slot_idx]; - ImagBuffer[slot_idx] = Cldfb_ImagBuffer_Binaural[0][ch][slot_idx]; - } - - cldfbSynthesis( RealBuffer, ImagBuffer, &( output_f[ch][index_slot * maxBand] ), maxBand * MAX_PARAM_SPATIAL_SUBFRAMES, st_ivas->cldfbSynDec[ch] ); - } - } - - return; -} - - -/*-------------------------------------------------------------------------* - * ivas_binaural_cldfb_sf() - * - * Perform CLDFB analysis, fastconv binaural rendering and CLDFB synthesis - *-------------------------------------------------------------------------*/ - -void ivas_binaural_cldfb_sf( - Decoder_Struct *st_ivas, /* i/o: IVAS decoder structure */ - const int16_t n_samples_to_render, /* i : output frame length per channel */ - const int16_t slot_size, /* i : JBM slot size */ - float *output_f[] /* i/o: synthesized core-coder transport channels/DirAC output */ -) -{ - float Cldfb_RealBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer[MAX_INTERN_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_RealBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; - float Cldfb_ImagBuffer_Binaural[MAX_HEAD_ROT_POSES][BINAURAL_CHANNELS][MAX_PARAM_SPATIAL_SUBFRAMES][CLDFB_NO_CHANNELS_MAX]; -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - float *re, *im; -#endif - int16_t slot_idx, subframeIdx, index_slot, idx_in, idx_lfe, maxBand, ch; - int16_t slots_to_render, first_sf, last_sf; - int16_t slot_index_start, slot_index_start_cldfb; - - /* Implement a 5 msec loops */ - maxBand = (int16_t) ( ( CLDFB_NO_CHANNELS_MAX * st_ivas->hDecoderConfig->output_Fs ) / 48000 ); - - /* loop for synthesis, assume we always have to render in multiples of 5ms subframes with spills */ - slots_to_render = min( st_ivas->hTcBuffer->num_slots - st_ivas->hTcBuffer->slots_rendered, n_samples_to_render / slot_size ); - first_sf = st_ivas->hTcBuffer->subframes_rendered; - last_sf = first_sf; - slot_index_start = st_ivas->hTcBuffer->slots_rendered; - slot_index_start_cldfb = 0; - st_ivas->hTcBuffer->slots_rendered += slots_to_render; - - while ( slots_to_render > 0 ) - { - slots_to_render -= st_ivas->hTcBuffer->subframe_nbslots[last_sf]; - last_sf++; - } - for ( subframeIdx = first_sf; subframeIdx < last_sf; subframeIdx++ ) - { - for ( slot_idx = 0; slot_idx < st_ivas->hTcBuffer->subframe_nbslots[subframeIdx]; slot_idx++ ) - { - index_slot = slot_index_start + slot_idx; - - /* Implement CLDFB analysis */ - idx_in = 0; - idx_lfe = 0; - - for ( ch = 0; ch < ( st_ivas->hIntSetup.nchan_out_woLFE + st_ivas->hIntSetup.num_lfe ); ch++ ) - { - if ( ( st_ivas->hIntSetup.num_lfe > 0 ) && ( st_ivas->hIntSetup.index_lfe[idx_lfe] == ch ) ) - { - if ( idx_lfe < ( st_ivas->hIntSetup.num_lfe - 1 ) ) - { - idx_lfe++; - } - } - else - { - cldfbAnalysis_ts( &( st_ivas->hTcBuffer->tc[ch][maxBand * index_slot] ), Cldfb_RealBuffer[idx_in][slot_idx], Cldfb_ImagBuffer[idx_in][slot_idx], maxBand, st_ivas->cldfbAnaDec[idx_in] ); - idx_in++; - } - } - - /*LFE handling for split rendering cases*/ - if ( ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) || - ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) ) - { - for ( idx_lfe = 0; idx_lfe < st_ivas->hIntSetup.num_lfe; idx_lfe++ ) - { - ch = st_ivas->hIntSetup.index_lfe[idx_lfe]; - cldfbAnalysis_ts( &( output_f[ch][maxBand * index_slot] ), Cldfb_RealBuffer[idx_in][slot_idx], Cldfb_ImagBuffer[idx_in][slot_idx], maxBand, st_ivas->cldfbAnaDec[idx_in] ); - idx_in++; - } - - if ( st_ivas->hSplitBinRend->hCldfbDataOut != NULL ) - { - for ( ch = 0; ch < ( st_ivas->hIntSetup.nchan_out_woLFE + st_ivas->hIntSetup.num_lfe ); ch++ ) - { - mvr2r( Cldfb_RealBuffer[ch][slot_idx], st_ivas->hSplitBinRend->hCldfbDataOut->Cldfb_RealBuffer[ch][slot_index_start + slot_idx], maxBand ); - mvr2r( Cldfb_ImagBuffer[ch][slot_idx], st_ivas->hSplitBinRend->hCldfbDataOut->Cldfb_ImagBuffer[ch][slot_index_start + slot_idx], maxBand ); - } - st_ivas->hSplitBinRend->hCldfbDataOut->config = st_ivas->hIntSetup.output_config; - } - } - } - - /* Implement binaural rendering */ - ivas_binRenderer( - st_ivas->hBinRenderer, - &st_ivas->hSplitBinRend->splitrend.multiBinPoseData, - st_ivas->hCombinedOrientationData, - st_ivas->hTcBuffer->subframe_nbslots[subframeIdx], -#ifdef SPLIT_REND_WITH_HEAD_ROT_DEBUG - NULL, -#endif - Cldfb_RealBuffer_Binaural, - Cldfb_ImagBuffer_Binaural, - Cldfb_RealBuffer, - Cldfb_ImagBuffer ); - - if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) - { - int16_t pos_idx; - for ( pos_idx = 0; pos_idx < st_ivas->hBinRenderer->numPoses; pos_idx++ ) - { - for ( slot_idx = 0; slot_idx < st_ivas->hTcBuffer->subframe_nbslots[subframeIdx]; slot_idx++ ) - { - for ( ch = 0; ch < st_ivas->hDecoderConfig->nchan_out; ch++ ) - { -#ifdef FIX_1119_SPLIT_RENDERING_VOIP - ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[( pos_idx * BINAURAL_CHANNELS ) + ch], &re, &im, slot_index_start + slot_idx ); - mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], re, maxBand ); - mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], im, maxBand ); -#else - mvr2r( Cldfb_RealBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_RealBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); - mvr2r( Cldfb_ImagBuffer_Binaural[pos_idx][ch][slot_idx], st_ivas->hSplitBinRend->hMultiBinCldfbData->Cldfb_ImagBuffer_Binaural[( pos_idx * BINAURAL_CHANNELS ) + ch][slot_index_start + slot_idx], maxBand ); -#endif - } - } - } - } - - /* update combined orientation access index */ - ivas_combined_orientation_update_index( st_ivas->hCombinedOrientationData, maxBand * st_ivas->hTcBuffer->subframe_nbslots[subframeIdx] ); - - /* Implement CLDFB synthesis */ - for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) - { - float *RealBuffer[MAX_PARAM_SPATIAL_SUBFRAMES]; - float *ImagBuffer[MAX_PARAM_SPATIAL_SUBFRAMES]; - - for ( slot_idx = 0; slot_idx < st_ivas->hTcBuffer->subframe_nbslots[subframeIdx]; slot_idx++ ) - { - RealBuffer[slot_idx] = Cldfb_RealBuffer_Binaural[0][ch][slot_idx]; - ImagBuffer[slot_idx] = Cldfb_ImagBuffer_Binaural[0][ch][slot_idx]; - } - - cldfbSynthesis( RealBuffer, ImagBuffer, &( output_f[ch][slot_index_start_cldfb * maxBand] ), maxBand * st_ivas->hTcBuffer->subframe_nbslots[subframeIdx], st_ivas->cldfbSynDec[ch] ); - } - slot_index_start += st_ivas->hTcBuffer->subframe_nbslots[subframeIdx]; - slot_index_start_cldfb += st_ivas->hTcBuffer->subframe_nbslots[subframeIdx]; - } - - st_ivas->hTcBuffer->subframes_rendered = last_sf; - - return; -} -#endif - - /*------------------------------------------------------------------------- * ivas_binRenderer() * diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 5b8668c6d3..ef3b4d18e5 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -1013,12 +1013,6 @@ ivas_error ivas_jbm_dec_render( return error; } } -#ifdef DEBUGGING - else if ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) - { - ivas_binaural_cldfb_sf( st_ivas, *nSamplesRendered, st_ivas->hTcBuffer->nb_subframes, p_output ); - } -#endif } } else if ( st_ivas->ivas_format == SBA_FORMAT || st_ivas->ivas_format == MASA_FORMAT ) -- GitLab From 03c4146bace790ab9443a3b690fac906d067fb7d Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 12:45:53 +0200 Subject: [PATCH 124/340] Add ambi_converter to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 20c6aa6fdc..9097b50575 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ IVAS_cod IVAS_dec IVAS_rend ISAR_post_rend +ambi_converter obj/ *.a *.o @@ -18,6 +19,7 @@ IVAS_cod.exe IVAS_dec.exe IVAS_rend.exe ISAR_post_rend.exe +ambi_converter.exe *.user .vs/ Debug_*/ -- GitLab From 0d4d4c22a699aec34b181fc79e30647c43b5df73 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 12:46:05 +0200 Subject: [PATCH 125/340] Remove obsolete debugging option -force in IVAS_dec --- apps/decoder.c | 61 ------------------------------------ lib_dec/ivas_init_dec.c | 13 -------- lib_dec/ivas_output_config.c | 17 ---------- lib_dec/ivas_stat_dec.h | 4 --- lib_dec/lib_dec.c | 59 ---------------------------------- lib_dec/lib_dec.h | 16 ---------- 6 files changed, 170 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 41089d057e..58ac33889b 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -127,7 +127,6 @@ typedef struct bool tsmEnabled; IVAS_RENDER_FRAMESIZE renderFramesize; #ifdef DEBUGGING - IVAS_DEC_FORCED_REND_MODE forcedRendMode; #ifdef DEBUG_FOA_AGC FILE *agcBitstream; /* temporary */ #endif @@ -180,7 +179,6 @@ static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); static int16_t app_own_random( int16_t *seed ); -static IVAS_DEC_FORCED_REND_MODE parseForcedRendModeDec( char *forcedRendModeChar ); #endif static void do_object_editing( IVAS_EDITABLE_PARAMETERS *editableParameters, ObjectEditFileReader *objectEditFileReader ); @@ -537,28 +535,6 @@ int main( #endif } - /*------------------------------------------------------------------------------------------* - * Binaural rendering mode: set and print info - *------------------------------------------------------------------------------------------*/ - - if ( arg.forcedRendMode != IVAS_DEC_FORCE_REND_UNFORCED ) - { - if ( ( error = IVAS_DEC_SetForcedRendMode( hIvasDec, arg.forcedRendMode ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError: Forcing binaural rendering mode failed (only TDREND and CLDFBREND are expected).\n\n" ); - goto cleanup; - } - - if ( arg.forcedRendMode == IVAS_DEC_FORCE_REND_TD_RENDERER ) - { - fprintf( stdout, "Forcing rendering to: TD renderer\n" ); - } - else if ( arg.forcedRendMode == IVAS_DEC_FORCE_REND_CLDFB_RENDERER ) - { - fprintf( stdout, "Forcing rendering to: CLDFB renderer\n" ); - } - } - /*-----------------------------------------------------------------* * Open Error pattern file for simulation *-----------------------------------------------------------------*/ @@ -965,7 +941,6 @@ static bool parseCmdlIVAS_dec( #ifdef DEBUGGING float ftmp; - arg->forcedRendMode = IVAS_DEC_FORCE_REND_UNFORCED; #ifdef DEBUG_FOA_AGC arg->agcBitstream = NULL; #endif @@ -1135,17 +1110,6 @@ static bool parseCmdlIVAS_dec( } i += 2; } - else if ( strcmp( argv_to_upper, "-FORCE" ) == 0 ) - { - i++; - if ( i < argc - 3 ) - { - strncpy( argv_to_upper, argv[i], sizeof( argv_to_upper ) - 1 ); - argv_to_upper[sizeof( argv_to_upper ) - 1] = '\0'; - arg->forcedRendMode = parseForcedRendModeDec( argv_to_upper ); - i++; - } - } #ifdef DEBUG_MODE_INFO #ifdef DEBUG_MODE_INFO_TWEAK /* Define additional subfolder for debug info output in ./res */ @@ -3900,31 +3864,6 @@ static void do_object_editing( } -#ifdef DEBUGGING - -/*---------------------------------------------------------------------* - * parseForcedRendModeDec() - * - * - *---------------------------------------------------------------------*/ - -static IVAS_DEC_FORCED_REND_MODE parseForcedRendModeDec( - char *forcedRendModeChar ) -{ - if ( ( strcmp( to_upper( forcedRendModeChar ), "TDREND" ) == 0 ) ) - { - return IVAS_DEC_FORCE_REND_TD_RENDERER; - } - if ( ( strcmp( to_upper( forcedRendModeChar ), "CLDFBREND" ) == 0 ) ) - { - return IVAS_DEC_FORCE_REND_CLDFB_RENDERER; - } - - return IVAS_DEC_FORCE_REND_UNDEFINED; -} -#endif - - /*---------------------------------------------------------------------* * load_hrtf_from_file() * diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 89416ab404..507a387e6e 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1161,8 +1161,6 @@ ivas_error ivas_init_decoder_front( #ifdef DEBUGGING st_ivas->noClipping = 0; - - st_ivas->hDecoderConfig->force_rend = -1; #endif /*-------------------------------------------------------------------* * Allocate and initialize Custom loudspeaker layout handle @@ -3203,17 +3201,6 @@ static ivas_error doSanityChecks_IVAS( { return IVAS_ERROR( IVAS_ERR_OBJECTS_EDITING_AND_PANNING_NOT_SUPPORTED, "Wrong set-up: Only object editing or Non-diegetic panning can be used." ); } -#ifdef DEBUGGING - if ( ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) && ( ( st_ivas->ivas_format != MC_FORMAT && st_ivas->ivas_format != ISM_FORMAT ) || ( output_config != IVAS_AUDIO_CONFIG_BINAURAL && output_config != IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB ) || ( st_ivas->ivas_format == ISM_FORMAT && st_ivas->ism_mode == ISM_MODE_PARAM ) || ( st_ivas->ivas_format == MC_FORMAT && st_ivas->mc_mode != MC_MODE_MCT ) ) ) - { - return IVAS_ERROR( IVAS_ERR_INVALID_OUTPUT_FORMAT, "Incorrect output configuration: Time Domain object renderer not supported in this configuration" ); - } - - if ( ( st_ivas->hHrtfTD != NULL && st_ivas->hDecoderConfig->force_rend == FORCE_CLDFB_RENDERER ) ) - { - return IVAS_ERROR( IVAS_ERR_INVALID_FORCE_MODE, "Incorrect debug configuration: Cannot force CLDFB renderer in combination with TD renderer HRTF file" ); - } -#endif return IVAS_ERR_OK; } diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index 9d2e7734de..17ce37c4d4 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -95,21 +95,8 @@ void ivas_renderer_select( { if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { -#ifdef DEBUGGING - if ( st_ivas->hDecoderConfig->force_rend == FORCE_CLDFB_RENDERER ) - { - *renderer_type = RENDERER_BINAURAL_FASTCONV; - *internal_config = IVAS_AUDIO_CONFIG_HOA3; /* Render ISM to HOA3 before binauralization*/ - } - else - { - *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; - *internal_config = output_config; - } -#else *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; *internal_config = output_config; -#endif } else { @@ -198,11 +185,7 @@ void ivas_renderer_select( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) { -#ifdef DEBUGGING - if ( ( ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) || ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) ) && st_ivas->mc_mode == MC_MODE_MCT && st_ivas->hDecoderConfig->force_rend != FORCE_CLDFB_RENDERER ) -#else if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->mc_mode == MC_MODE_MCT ) -#endif { *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 0831079213..792c87ddc4 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -1024,10 +1024,6 @@ typedef struct decoder_config_structure int16_t Opt_dpid_on; /* indicates whether Directivity pattern option is used */ int16_t Opt_aeid_on; /* indicates whether Acoustic environment option is used */ int16_t Opt_ObjEdit_on; /* indicates whether object editing option is used */ -#ifdef DEBUGGING - /* temp. development parameters */ - int16_t force_rend; /* forced TD/CLDFB binaural renderer (for ISM and MC) */ -#endif int16_t Opt_tsm; /* indicates whether time scaling modification is activated */ IVAS_RENDER_FRAMESIZE render_framesize; int16_t Opt_delay_comp; /* flag indicating delay compensation active */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 0f54199425..a77c8f4edd 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -4803,65 +4803,6 @@ int32_t IVAS_DEC_GetCntFramesLimited( } } - -/*---------------------------------------------------------------------* - * forcedRendModeApiToInternalDec() - * - * - *---------------------------------------------------------------------*/ - -static ivas_error forcedRendModeApiToInternalDec( - const IVAS_DEC_FORCED_REND_MODE forcedRendMode, - int16_t *forcedModeInternal ) -{ - switch ( forcedRendMode ) - { - case IVAS_DEC_FORCE_REND_TD_RENDERER: - *forcedModeInternal = FORCE_TD_RENDERER; - break; - case IVAS_DEC_FORCE_REND_CLDFB_RENDERER: - *forcedModeInternal = FORCE_CLDFB_RENDERER; - break; - case IVAS_DEC_FORCE_REND_UNFORCED: - *forcedModeInternal = -1; - break; - default: - return IVAS_ERR_INVALID_FORCE_MODE; - break; - } - - return IVAS_ERR_OK; -} - - -/*---------------------------------------------------------------------* - * IVAS_DEC_SetForcedRendMode() - * - * - *---------------------------------------------------------------------*/ - -ivas_error IVAS_DEC_SetForcedRendMode( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const IVAS_DEC_FORCED_REND_MODE forcedRendMode /* i : forced renderer mode */ -) -{ - int16_t newForcedRend; - ivas_error error; - - if ( ( error = forcedRendModeApiToInternalDec( forcedRendMode, &newForcedRend ) ) != IVAS_ERR_OK ) - { - return error; - } - - if ( hIvasDec->st_ivas->hDecoderConfig->force_rend != newForcedRend ) - { - hIvasDec->st_ivas->hDecoderConfig->force_rend = newForcedRend; - } - - return IVAS_ERR_OK; -} - - #ifdef DEBUG_SBA_AUDIO_DUMP /*---------------------------------------------------------------------* * IVAS_DEC_GetSbaDebugParams( ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 18298954c4..da33ffca3c 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -64,16 +64,6 @@ typedef enum _IVAS_DEC_COMPLEXITY_LEVEL } IVAS_DEC_COMPLEXITY_LEVEL; -#ifdef DEBUGGING -typedef enum _IVAS_DEC_FORCED_REND_MODE -{ - IVAS_DEC_FORCE_REND_CLDFB_RENDERER, - IVAS_DEC_FORCE_REND_TD_RENDERER, - IVAS_DEC_FORCE_REND_UNFORCED, - IVAS_DEC_FORCE_REND_UNDEFINED = 0xffff -} IVAS_DEC_FORCED_REND_MODE; -#endif - typedef enum _IVAS_DEC_PCM_TYPE { IVAS_DEC_PCM_INT16, @@ -406,12 +396,6 @@ int32_t IVAS_DEC_GetCntFramesLimited( IVAS_DEC_HANDLE hIvasDec /* i : IVAS decoder handle */ ); -/*! r: error code */ -ivas_error IVAS_DEC_SetForcedRendMode( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const IVAS_DEC_FORCED_REND_MODE forcedRendMode /* i : forced renderer mode */ -); - #ifdef DEBUG_SBA_AUDIO_DUMP ivas_error IVAS_DEC_GetSbaDebugParams( const IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ -- GitLab From 60a57723d8a0c2f5d4b7faa0014dc6a7cfab275c Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 13:24:48 +0200 Subject: [PATCH 126/340] Apply formatting patch --- lib_dec/ivas_stat_dec.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_dec/ivas_stat_dec.h b/lib_dec/ivas_stat_dec.h index 792c87ddc4..f1efb32a10 100644 --- a/lib_dec/ivas_stat_dec.h +++ b/lib_dec/ivas_stat_dec.h @@ -1024,7 +1024,7 @@ typedef struct decoder_config_structure int16_t Opt_dpid_on; /* indicates whether Directivity pattern option is used */ int16_t Opt_aeid_on; /* indicates whether Acoustic environment option is used */ int16_t Opt_ObjEdit_on; /* indicates whether object editing option is used */ - int16_t Opt_tsm; /* indicates whether time scaling modification is activated */ + int16_t Opt_tsm; /* indicates whether time scaling modification is activated */ IVAS_RENDER_FRAMESIZE render_framesize; int16_t Opt_delay_comp; /* flag indicating delay compensation active */ -- GitLab From 22938cb097426f2170ac974ae4cc0bafc215d502 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 24 Sep 2025 14:07:08 +0200 Subject: [PATCH 127/340] implement keep_files arg and add printout of command --- tests/test_be_ambi_converter_fixed_to_float.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 0ff1499809..264b5565de 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -7,6 +7,7 @@ from tempfile import TemporaryDirectory HERE = Path(__file__).absolute().parent TESTV_DIR = HERE.parent / "scripts/testv" +OUTPUT_FOLDER_IF_KEEP_FILES = HERE.joinpath("output-ambi_converter-be") sys.path.append(str(HERE.parent / "scripts")) from pyaudio3dtools import audiofile, audioarray @@ -35,12 +36,12 @@ def run_ambi_converter( f"{int(convention_in)}", f"{int(convention_out)}", ] + print(" ".join(cmd)) p = subprocess.run(cmd, capture_output=True) if p.returncode != 0: - pytest.fail( - f"Ambisonics converter run failed: {p.stdout.decode('utf8') + p.stderr.decode('utf8')}" - ) + msg = f"{p.stdout.decode('utf8')}\n{p.stderr.decode('utf8')}" + pytest.fail(f"Ambisonics converter run failed:\n{msg}") INPUT_FILES = [TESTV_DIR / "stv3OA48c.wav"] @@ -57,6 +58,7 @@ def test_ambi_converter( infile: Path, convention_in: AMBI_CONVENTION, convention_out: AMBI_CONVENTION, + keep_files, # needs to be passed to correctly report errors test_info, ): @@ -66,8 +68,13 @@ def test_ambi_converter( ): pytest.xfail("One of in and out convention needs to be ACN_SN3D") + if keep_files: + OUTPUT_FOLDER_IF_KEEP_FILES.mkdir(exist_ok=True, parents=True) + with TemporaryDirectory() as tmp_dir: - outfile_base = Path(tmp_dir) / ( + output_dir = OUTPUT_FOLDER_IF_KEEP_FILES if keep_files else tmp_dir + + outfile_base = Path(output_dir) / ( infile.stem + f"-{str(convention_in)}-to-{str(convention_out)}" ) -- GitLab From 4aca7e15cb57d2b40dd553c056279b7502c3dd57 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 24 Sep 2025 15:18:53 +0300 Subject: [PATCH 128/340] Remove diegetic PI interpretation from individual ISM rendering --- lib_dec/ivas_objectRenderer_internal.c | 12 ------------ lib_rend/ivas_objectRenderer.c | 27 -------------------------- lib_rend/ivas_prot_rend.h | 6 ------ 3 files changed, 45 deletions(-) diff --git a/lib_dec/ivas_objectRenderer_internal.c b/lib_dec/ivas_objectRenderer_internal.c index 353c15d43c..ff47b1b9a7 100644 --- a/lib_dec/ivas_objectRenderer_internal.c +++ b/lib_dec/ivas_objectRenderer_internal.c @@ -206,26 +206,14 @@ ivas_error ivas_td_binaural_renderer_sf( hIsmMetaData[nS] = &ismMetaData[nS]; } -#ifdef RTP_S4_251135_CR26253_0016_REV1 - if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, hIsmMetaData, - ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPI : NULL, - ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) -#else if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, hIsmMetaData ) ) != IVAS_ERR_OK ) -#endif { return error; } } else { -#ifdef RTP_S4_251135_CR26253_0016_REV1 - if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, st_ivas->hIsmMetaData, - ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPI : NULL, - ( st_ivas->hCombinedOrientationData != NULL ) ? st_ivas->hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) -#else if ( ( error = TDREND_Update_object_positions( st_ivas->hBinRendererTd, nchan_ism, st_ivas->ivas_format, st_ivas->hIsmMetaData ) ) != IVAS_ERR_OK ) -#endif { return error; } diff --git a/lib_rend/ivas_objectRenderer.c b/lib_rend/ivas_objectRenderer.c index c467838971..f0fb90fe4e 100644 --- a/lib_rend/ivas_objectRenderer.c +++ b/lib_rend/ivas_objectRenderer.c @@ -363,13 +363,7 @@ ivas_error ivas_td_binaural_renderer_unwrap( if ( subframe_idx == ism_md_subframe_update ) { /* Update object position(s) */ -#ifdef RTP_S4_251135_CR26253_0016_REV1 - if ( ( error = TDREND_Update_object_positions( hBinRendererTd, num_src, ivas_format, hIsmMetaData, - ( hCombinedOrientationData != NULL ) ? hCombinedOrientationData->isDiegeticInputPI : NULL, - ( hCombinedOrientationData != NULL ) ? hCombinedOrientationData->isDiegeticInputPISet : false ) ) != IVAS_ERR_OK ) -#else if ( ( error = TDREND_Update_object_positions( hBinRendererTd, num_src, ivas_format, hIsmMetaData ) ) != IVAS_ERR_OK ) -#endif { return error; } @@ -537,13 +531,7 @@ ivas_error TDREND_Update_object_positions( BINAURAL_TD_OBJECT_RENDERER_HANDLE hBinRendererTd, /* i/o: TD Renderer handle */ const int16_t num_src, /* i : number of sources to render */ const IVAS_FORMAT in_format, /* i : Format of input sources */ -#ifdef RTP_S4_251135_CR26253_0016_REV1 - const ISM_METADATA_HANDLE *hIsmMetaData, /* i : Input metadata for ISM objects */ - const bool *isDiegeticInputPI, /* i : diegetic values for the input streams from the PI data */ - const bool isDiegeticInputPISet /* i : is diegetic values obtained from PI data */ -#else const ISM_METADATA_HANDLE *hIsmMetaData /* i : Input metadata for ISM objects */ -#endif ) { #ifndef NONBE_1377_REND_DIRATT_CONF @@ -556,9 +544,6 @@ ivas_error TDREND_Update_object_positions( #ifndef NONBE_1377_REND_DIRATT_CONF DirAtten_p = hBinRendererTd->DirAtten_p; #endif -#ifdef RTP_S4_251135_CR26253_0016_REV1 - bool isDiegeticSrcPI; -#endif /* For each source, write the frame data to the source object*/ for ( nS = 0; nS < num_src; nS++ ) @@ -586,19 +571,7 @@ ivas_error TDREND_Update_object_positions( return error; } -#ifdef RTP_S4_251135_CR26253_0016_REV1 - isDiegeticSrcPI = false; - if ( isDiegeticInputPISet && isDiegeticInputPI != NULL ) - { - if ( isDiegeticInputPI[nS] ) - { - isDiegeticSrcPI = true; - } - } - if ( hIsmMetaData[nS]->non_diegetic_flag || !isDiegeticSrcPI ) -#else if ( hIsmMetaData[nS]->non_diegetic_flag ) -#endif { Pos[0] = 0; Pos[1] = hIsmMetaData[nS]->azimuth / 90.f; diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index e3d205cd25..937923c338 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -691,13 +691,7 @@ ivas_error TDREND_Update_object_positions( BINAURAL_TD_OBJECT_RENDERER_HANDLE hBinRendererTd, /* i/o: TD Renderer handle */ const int16_t num_src, /* i : number of sources to render */ const IVAS_FORMAT in_format, /* i : Format of input sources */ -#ifdef RTP_S4_251135_CR26253_0016_REV1 - const ISM_METADATA_HANDLE *hIsmMetaData, /* i : Input metadata for ISM objects */ - const bool *isDiegeticInputPI, /* i : diegetic values for the input streams from the PI data */ - const bool isDiegeticInputPISet /* i : is diegetic values obtained from PI data */ -#else const ISM_METADATA_HANDLE *hIsmMetaData /* i : Input metadata for ISM objects */ -#endif ); void BSplineModelEvalDealloc( -- GitLab From fa7a741a9e65585dada56e38062f162d69cc6cac Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 24 Sep 2025 14:41:04 +0200 Subject: [PATCH 129/340] improve testcase naming --- .../test_be_ambi_converter_fixed_to_float.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 264b5565de..265fd643b8 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -1,5 +1,6 @@ import pytest import subprocess +import itertools import sys from enum import Enum from pathlib import Path @@ -45,15 +46,26 @@ def run_ambi_converter( INPUT_FILES = [TESTV_DIR / "stv3OA48c.wav"] -CONVENTIONS = [c.value for c in AMBI_CONVENTION] +CONVENTIONS = [c for c in AMBI_CONVENTION] AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" THRESHOLD_FAIL = 2 - -@pytest.mark.parametrize("infile", INPUT_FILES) -@pytest.mark.parametrize("convention_out", CONVENTIONS) -@pytest.mark.parametrize("convention_in", CONVENTIONS) +CONVENTIONS_FULL_COMBI = list(itertools.product(CONVENTIONS, CONVENTIONS)) +CONVENTIONS_TEST_PARAMS = [ + (c_in.value, c_out.value) for c_in, c_out in CONVENTIONS_FULL_COMBI +] +CONVENTIONS_TEST_PARAMS_IDS = [ + f"{c_in.name}-to-{c_out.name}" for c_in, c_out in CONVENTIONS_FULL_COMBI +] + + +@pytest.mark.parametrize("infile", INPUT_FILES, ids=[p.name for p in INPUT_FILES]) +@pytest.mark.parametrize( + "convention_in,convention_out", + CONVENTIONS_TEST_PARAMS, + ids=CONVENTIONS_TEST_PARAMS_IDS, +) def test_ambi_converter( infile: Path, convention_in: AMBI_CONVENTION, -- GitLab From 476debe59d8b8e336a2a7b8de40710ae1b85b0d5 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 24 Sep 2025 15:31:06 +0200 Subject: [PATCH 130/340] switch to other input files for ambi_converter test - use the spectral_test items now --- tests/test_be_ambi_converter_fixed_to_float.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 265fd643b8..8fdd81e3cc 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -45,7 +45,9 @@ def run_ambi_converter( pytest.fail(f"Ambisonics converter run failed:\n{msg}") -INPUT_FILES = [TESTV_DIR / "stv3OA48c.wav"] +# test all ambisonics orders from 1 to 3 +INPUT_CH_NUM = [4, 9, 16] +INPUT_FILES = [TESTV_DIR / f"spectral_test_{ch}ch_48kHz.wav" for ch in INPUT_CH_NUM] CONVENTIONS = [c for c in AMBI_CONVENTION] AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" -- GitLab From 116bacde489de22f0a5c202a0477335171278399 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 24 Sep 2025 16:41:10 +0300 Subject: [PATCH 131/340] Begin audio focus PI implementation --- apps/decoder.c | 36 +++++++++++-- lib_util/ivas_rtp_pi_data.c | 103 +++++++++++++++++++++++++++++++++--- lib_util/ivas_rtp_pi_data.h | 42 +++++++++++++-- tests/rtp/ivasrtp.py | 34 ++++++++++-- tests/rtp/test_rtp.py | 3 +- 5 files changed, 198 insertions(+), 20 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index a28d5d6023..df7dcf5168 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -216,8 +216,8 @@ static const char *PiDataNames[IVAS_PI_MAX_ID] = { "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", - "RESERVED14", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", - "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_DIRECTION", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", + "AUDIO_FOCUS_INDICATION", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", + "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", "RESERVED30", "NO_DATA" }; @@ -261,7 +261,6 @@ static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t n #ifdef RTP_S4_251135_CR26253_0016_REV1 case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: case IVAS_PI_HEAD_ORIENTATION: - case IVAS_PI_AUDIO_FOCUS_DIRECTION: case IVAS_PI_R_ISM_ORIENTATION: #endif { @@ -327,6 +326,21 @@ static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t n fprintf( f_piDataOut, "\t\t\t]\n\t\t}" ); } break; + case IVAS_PI_AUDIO_FOCUS_INDICATION: + { + fprintf( f_piDataOut, "{\n" ); + if ( cur->data.focusIndication.availDirection ) + { + fprintf( f_piDataOut, "\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t\t", + cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); + } + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "\t\t\t\"level\": %d", cur->data.focusIndication.flvl ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: { IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; @@ -339,7 +353,21 @@ static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t n } break; case IVAS_PI_RESERVED13: - case IVAS_PI_RESERVED14: + case IVAS_PI_AUDIO_FOCUS_REQUEST: + { + fprintf( f_piDataOut, "{\n" ); + if ( cur->data.focusRequest.availDirection ) + { + fprintf( f_piDataOut, "\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t\t", + cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); + } + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "\t\t\t\"level\": %d", cur->data.focusRequest.flvl ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; case IVAS_PI_RESERVED15: case IVAS_PI_RESERVED27: case IVAS_PI_RESERVED28: diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index e7dcde8998..24ecabe6e8 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -132,7 +132,7 @@ static ivas_error packOrientation( const IVAS_PIDATA_GENERIC *piData, uint8_t *b if ( ( piData->piDataType != IVAS_PI_SCENE_ORIENTATION ) && ( piData->piDataType != IVAS_PI_DEVICE_ORIENTATION_COMPENSATED ) && ( piData->piDataType != IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED ) #ifdef RTP_S4_251135_CR26253_0016_REV1 - && ( piData->piDataType != IVAS_PI_PLAYBACK_DEVICE_ORIENTATION ) && ( piData->piDataType != IVAS_PI_HEAD_ORIENTATION ) && ( piData->piDataType != IVAS_PI_AUDIO_FOCUS_DIRECTION ) + && ( piData->piDataType != IVAS_PI_PLAYBACK_DEVICE_ORIENTATION ) && ( piData->piDataType != IVAS_PI_HEAD_ORIENTATION ) #endif /* RTP_S4_251135_CR26253_0016_REV1 */ ) { @@ -544,6 +544,95 @@ static ivas_error unpackDiegetic( const uint8_t *buffer, uint32_t numDataBytes, return IVAS_ERR_OK; } +static ivas_error packAudioFocusCommon( const IVAS_PIDATA_GENERIC *piData, uint8_t *buffer, uint32_t maxDataBytes, uint32_t *nBytesWritten ) +{ + uint32_t nBytes = 0; + uint8_t packedSize = 1; + const IVAS_PIDATA_AUDIO_FOCUS *audioFocus = (const IVAS_PIDATA_AUDIO_FOCUS *) piData; + + *nBytesWritten = 0; + + if ( piData->size != sizeof( IVAS_PIDATA_AUDIO_FOCUS ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Incorrect size in PI data of type Audio Focus" ); + } + + if ( audioFocus->availDirection && audioFocus->availLevel ) + { + packedSize = 9; + } + else if ( audioFocus->availDirection ) + { + packedSize = 8; + } + else if ( audioFocus->availLevel ) + { + packedSize = 1; + } + else + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Neither direction or level is available for packing Audio Focus" ); + } + + /* Audio Focus data is packedSize bytes, header is 2 bytes */ + if ( maxDataBytes < (uint32_t) packedSize + 2 ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Insufficient space to pack Audio Focus PI data" ); + } + + buffer[nBytes++] = ( audioFocus->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ + buffer[nBytes++] = packedSize; + + if ( packedSize == 9 || packedSize == 8 ) + { + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( audioFocus->direction.w ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( audioFocus->direction.x ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( audioFocus->direction.y ) ); + nBytes = writeInt16( buffer, nBytes, ivasPayload_convertToQ15( audioFocus->direction.z ) ); + } + if ( packedSize == 9 || packedSize == 1) + { + buffer[nBytes++] = ( (uint8_t) audioFocus->flvl & MASK_4BIT ) << 4; + } + + *nBytesWritten = nBytes; + return IVAS_ERR_OK; +} + +static ivas_error unpackAudioFocusCommon( const uint8_t *buffer, uint32_t numDataBytes, IVAS_PIDATA_GENERIC *piData ) +{ + IVAS_PIDATA_AUDIO_FOCUS *audioFocus = (IVAS_PIDATA_AUDIO_FOCUS *) piData; + + /* Audio Focus data is either 1, 8 or 9 bytes */ + if ( numDataBytes != 1 && numDataBytes != 8 && numDataBytes != 9 ) + { + return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack PI data of type Audio Focus" ); + } + + piData->size = sizeof( IVAS_PIDATA_AUDIO_FOCUS ); + audioFocus->availDirection = ( numDataBytes >= 8 ); + audioFocus->availLevel = ( numDataBytes == 1 || numDataBytes == 9 ); + + if ( numDataBytes == 1 ) + { + audioFocus->flvl = ( buffer[0] >> 4 ); + } + else + { + audioFocus->direction.w = FLOAT_FROM_Q15( readInt16( &buffer[0] ) ); + audioFocus->direction.x = FLOAT_FROM_Q15( readInt16( &buffer[2] ) ); + audioFocus->direction.y = FLOAT_FROM_Q15( readInt16( &buffer[4] ) ); + audioFocus->direction.z = FLOAT_FROM_Q15( readInt16( &buffer[6] ) ); + + if ( numDataBytes == 9 ) + { + audioFocus->flvl = ( buffer[8] >> 4 ); + } + } + + return IVAS_ERR_OK; +} + #endif /* RTP_S4_251135_CR26253_0016_REV1 */ @@ -570,14 +659,14 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* DIEGETIC_TYPE */ #endif packUnsupportedData, /* RESERVED13 */ - packUnsupportedData, /* RESERVED14 */ + packAudioFocusCommon,/* AUDIO_FOCUS_INDICATION */ packUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ packOrientation, /* HEAD_ORIENTATION */ packListenerPosition, /* LISTENER_POSITION */ packDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - packOrientation, /* AUDIO_FOCUS_DIRECTION */ + packAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ #else packUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ packUnsupportedData, /* HEAD_ORIENTATION */ @@ -625,14 +714,14 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* DIEGETIC_TYPE */ #endif unpackUnsupportedData, /* RESERVED13 */ - unpackUnsupportedData, /* RESERVED14 */ + unpackAudioFocusCommon,/* AUDIO_FOCUS_INDICATION */ unpackUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ unpackOrientation, /* HEAD_ORIENTATION */ unpackListenerPosition, /* LISTENER_POSITION */ unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ - unpackOrientation, /* AUDIO_FOCUS_DIRECTION */ + unpackAudioFocusCommon, /* AUDIO_FOCUS_REQUEST */ #else unpackUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ unpackUnsupportedData, /* HEAD_ORIENTATION */ @@ -672,13 +761,13 @@ static const uint32_t maxPiDataSize[IVAS_PI_MAX_ID] = { 8, /* IVAS_PI_ISM_DIRECTIVITY */ 1, /* IVAS_PI_DIEGETIC_TYPE */ 0, /* IVAS_PI_RESERVED13 */ - 0, /* IVAS_PI_RESERVED14 */ + 9, /* IVAS_PI_AUDIO_FOCUS_INDICATION */ 0, /* IVAS_PI_RESERVED15 */ 8, /* IVAS_PI_PLAYBACK_DEVICE_ORIENTATION */ 8, /* IVAS_PI_HEAD_ORIENTATION */ 6, /* IVAS_PI_LISTENER_POSITION */ 2, /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ - 8, /* IVAS_PI_AUDIO_FOCUS_DIRECTION */ + 9, /* IVAS_PI_AUDIO_FOCUS_REQUEST */ 4, /* IVAS_PI_PI_LATENCY */ 1, /* IVAS_PI_R_ISM_ID */ 1, /* IVAS_PI_R_ISM_GAIN */ diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 3be265bb87..aad50dd6b6 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -64,7 +64,7 @@ extern "C" IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ IVAS_PI_RESERVED13, /* reserved */ - IVAS_PI_RESERVED14, /* reserved */ + IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ IVAS_PI_RESERVED15, /* reserved */ /* Reverse direction PI types */ @@ -72,7 +72,7 @@ extern "C" IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION, /* receiver’s preference with respect to audio suppression */ - IVAS_PI_AUDIO_FOCUS_DIRECTION, /* direction of interest for the listener in Quaternions */ + IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ IVAS_PI_R_ISM_ID, /* id of an object for editing */ IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ @@ -100,7 +100,6 @@ extern "C" * - IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED * - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION * - IVAS_PI_HEAD_ORIENTATION - * - IVAS_PI_AUDIO_FOCUS_DIRECTION * - IVAS_PI_R_ISM_ORIENTATION * * piDataType is used to identify the correct pi data type contained here @@ -293,6 +292,40 @@ extern "C" bool isDiegetic[1 + IVAS_PI_MAX_OBJECTS]; /* diegetic indication as per audio format */ } IVAS_PIDATA_DIEGETIC; + /* Audio focus direction indicates a direction of interest. + * The audio focus level indicates the amount of suppression applied to the + * directions other than the audio focus direction. + */ + typedef enum + { + IVAS_FLVL_NO_AUDIO_FOCUS = 0, /* Apply no audio focus */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_1, /* Audio focus level 1 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_2, /* Audio focus level 2 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_3, /* Audio focus level 3 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_4, /* Audio focus level 4 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_5, /* Audio focus level 5 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_6, /* Audio focus level 6 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_7, /* Audio focus level 7 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_8, /* Audio focus level 8 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_9, /* Audio focus level 9 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_10, /* Audio focus level 10 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_11, /* Audio focus level 11 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_12, /* Audio focus level 12 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_13, /* Audio focus level 13 */ + IVAS_FLVL_DEFAULT_AUDIO_FOCUS, /* Default audio focus */ + IVAS_FLVL_MAX_AUDIO_FOCUS, /* Apply max audio focus */ + } IVAS_FLVL; + + typedef struct + { + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_FOCUS) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_FOCUS_INDCATION or IVAS_PI_AUDIO_FOCUS_REQUEST */ + bool availDirection; /* audio focus contains direction */ + bool availLevel; /* audio focus contains level */ + IVAS_QUATERNION direction; /* direction data expressed as quarternions */ + IVAS_FLVL flvl; /* audio focus level */ + } IVAS_PIDATA_AUDIO_FOCUS; + /* Listener position */ typedef struct { @@ -416,12 +449,13 @@ extern "C" IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; IVAS_PIDATA_DIEGETIC digeticIndicator; + IVAS_PIDATA_AUDIO_FOCUS focusIndication; IVAS_PIDATA_ORIENTATION playbackOrientation; IVAS_PIDATA_ORIENTATION headOrientation; IVAS_PIDATA_LISTENER_POSITION listnerPosition; IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppression; - IVAS_PIDATA_ORIENTATION focusDirection; + IVAS_PIDATA_AUDIO_FOCUS focusRequest; IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; IVAS_PIDATA_ISM_EDIT_ID ismEditId; IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index f6923c0077..8a2fd0f87d 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -170,13 +170,13 @@ class PIDATAS(str, Enum): ISM_DIRECTIVITY = "ISM_DIRECTIVITY" DIEGETIC_TYPE = "DIEGETIC_TYPE" RESERVED13 = "RESERVED13" - RESERVED14 = "RESERVED14" + AUDIO_FOCUS_INDICATION = "AUDIO_FOCUS_INDICATION" RESERVED15 = "RESERVED15" PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" HEAD_ORIENTATION = "HEAD_ORIENTATION" LISTENER_POSITION = "LISTENER_POSITION" DYNAMIC_AUDIO_SUPPRESSION = "DYNAMIC_AUDIO_SUPPRESSION" - AUDIO_FOCUS_DIRECTION = "AUDIO_FOCUS_DIRECTION" + AUDIO_FOCUS_REQUEST = "AUDIO_FOCUS_REQUEST" PI_LATENCY = "PI_LATENCY" R_ISM_ID = "R_ISM_ID" R_ISM_GAIN = "R_ISM_GAIN" @@ -207,6 +207,24 @@ class SUPPRESSION_LEVEL(int, Enum): SUPPRESSION_LEVEL_14 = 14 SUPPRESSION_LEVEL_MAX = 15 +class AUDIO_FOCUS_LEVEL(int, Enum): + AUDIO_FOCUS_LEVEL_NONE = 0 + AUDIO_FOCUS_LEVEL_1 = 1 + AUDIO_FOCUS_LEVEL_2 = 2 + AUDIO_FOCUS_LEVEL_3 = 3 + AUDIO_FOCUS_LEVEL_4 = 4 + AUDIO_FOCUS_LEVEL_5 = 5 + AUDIO_FOCUS_LEVEL_6 = 6 + AUDIO_FOCUS_LEVEL_7 = 7 + AUDIO_FOCUS_LEVEL_8 = 8 + AUDIO_FOCUS_LEVEL_9 = 9 + AUDIO_FOCUS_LEVEL_10 = 10 + AUDIO_FOCUS_LEVEL_11 = 11 + AUDIO_FOCUS_LEVEL_12 = 12 + AUDIO_FOCUS_LEVEL_13 = 13 + AUDIO_FOCUS_LEVEL_DEFAULT = 14 + AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 + @dataclass class RTPHDR: @@ -343,6 +361,14 @@ class ACOUSTIC_ENVIRONMENT: dim: tuple[float, float, float] = () abscoeff: tuple[float, float, float, float, float, float] = () +@dataclass +class AUDIO_FOCUS: + w: float = 0.0 + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + level: AUDIO_FOCUS_LEVEL = AUDIO_FOCUS_LEVEL.AUDIO_FOCUS_LEVEL_DEFAULT + @dataclass class PIDATA: timestamp: int = 0 @@ -589,13 +615,13 @@ PIDataUnpacker = [ unpackUnsupported, # ISM_DIRECTIVITY unpackDiegetic, # DIEGETIC_TYPE unpackUnsupported, # RESERVED13 - unpackUnsupported, # RESERVED14 + unpackAudioFocusCommon, # AUDIO_FOCUS_INDICATION unpackUnsupported, # RESERVED15 unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION unpackOrientation, # HEAD_ORIENTATION unpackPosition, # LISTENER_POSITION unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION - unpackOrientation, # AUDIO_FOCUS_DIRECTION + unpackAudioFocusCommon, # AUDIO_FOCUS_REQUEST unpackUnsupported, # PI_LATENCY unpackUnsupported, # R_ISM_ID unpackUnsupported, # R_ISM_GAIN diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 342e29c8b6..d63dcb9da2 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -164,6 +164,7 @@ def generatePiData(startTs: int, endTs: int) -> dict: someDesc = lambda : AUDIO_DESCRIPTION(isSpeech=bool(random.getrandbits(1)), isMusic=bool(random.getrandbits(1)), isAmbiance=bool(random.getrandbits(1)), isEditable=bool(random.getrandbits(1)), isBinaural=bool(random.getrandbits(1))) someDAS = lambda : DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=bool(random.getrandbits(1)), preferMusic=bool(random.getrandbits(1)), preferAmbiance=bool(random.getrandbits(1)), level=random.randint(0, 15)) someDIG = lambda : DIEGETIC_TYPE(isDigetic=[ bool(random.getrandbits(1)) for _ in range(random.randint(1, 5)) ]) + someAFocus = lambda : AUDIO_FOCUS(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=random.randint(0, 15)) for ts in range(startTs, endTs, 320): pidata = dict() @@ -172,7 +173,7 @@ def generatePiData(startTs: int, endTs: int) -> dict: pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() pidata["HEAD_ORIENTATION"] = someOrientation() - pidata["AUDIO_FOCUS_DIRECTION"] = someOrientation() + pidata["AUDIO_FOCUS_REQUEST"] = someAFocus() pidata["LISTENER_POSITION"] = somePosition() pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] -- GitLab From 3e3f57dee537bc1e34f73ebb403d537fe67db6f3 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 13:33:51 +0200 Subject: [PATCH 132/340] Remove obsolete comment --- lib_dec/ivas_omasa_dec.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index fdbca49b31..4a4e56506a 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -791,7 +791,6 @@ ivas_error ivas_omasa_dirac_td_binaural_jbm( { cldfbAnalysis_ts( &( p_rend_obj[n][num_cldfb_bands * slot_idx] ), Cldfb_RealBuffer, Cldfb_ImagBuffer, num_cldfb_bands, st_ivas->hSplitBinRend->splitrend.hCldfbHandles->cldfbAna[n] ); - /* note: this intentionally differs from OSBA by: no scaling by 0.5 */ #ifdef FIX_1119_SPLIT_RENDERING_VOIP ivas_CLDFB_RINGBUF_GetByIdx( st_ivas->hSplitBinRend->hMultiBinCldfbData[n], &re, &im, slot_idx - cldfb_slots ); v_add( re, Cldfb_RealBuffer, re, num_cldfb_bands ); -- GitLab From 785c7164541737334a51cd12f8e8784b4905d171 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 24 Sep 2025 15:51:20 +0200 Subject: [PATCH 133/340] Enable split rendering + VoIP tests for OSBA and OMASA --- tests/split_rendering/test_split_rendering.py | 8 ++++---- .../split_rendering/test_voip_be_splitrend_vs_binaural.py | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/split_rendering/test_split_rendering.py b/tests/split_rendering/test_split_rendering.py index 8f4d71a406..0ff32fa290 100644 --- a/tests/split_rendering/test_split_rendering.py +++ b/tests/split_rendering/test_split_rendering.py @@ -357,7 +357,7 @@ def test_masa_external_split( """ OMASA """ -# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OMASA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OMASA) @@ -375,7 +375,7 @@ def test_omasa_full_chain_split( bitrate, render_config, trajectory, - delay_profile=None, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") @@ -437,7 +437,7 @@ def test_omasa_external_split( """ OSBA """ -# @pytest.mark.parametrize("delay_profile", DELAY_PROFILES) # Waiting for issue #1343 to be resolved +@pytest.mark.parametrize("delay_profile", DELAY_PROFILES) @pytest.mark.parametrize("trajectory", SPLIT_REND_HR_TRAJECTORIES_TO_TEST) @pytest.mark.parametrize("render_config", RENDERER_CONFIGS_TO_TEST_OSBA) @pytest.mark.parametrize("bitrate", IVAS_BITRATES_OSBA) @@ -455,7 +455,7 @@ def test_osba_full_chain_split( bitrate, render_config, trajectory, - delay_profile=None, + delay_profile, ): post_trajectory = HR_TRAJECTORY_DIR.joinpath(f"{trajectory}.csv") pre_trajectory = post_trajectory.with_stem(f"{post_trajectory.stem}_delayed") diff --git a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py index fcd6cb231f..d70640b33a 100644 --- a/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py +++ b/tests/split_rendering/test_voip_be_splitrend_vs_binaural.py @@ -49,9 +49,6 @@ IN_FORMATS = [ "ISM4", "FOA", "MASA2TC", - # Mixed formats to be finalised in a follow-up issue #1343 - # "OSBA_ISM3_HOA3", - # "OMASA_ISM4", ] DELAY_PROFILES = ["dly_error_profile_0.dat", "dly_error_profile_5.dat"] -- GitLab From 8d514b636f82c3834e0a446c608f9f36bbe2851a Mon Sep 17 00:00:00 2001 From: vaclav Date: Wed, 24 Sep 2025 19:18:13 +0200 Subject: [PATCH 134/340] address comments from BASOP --- lib_com/edct.c | 41 +++-------------------------------------- lib_dec/ivas_init_dec.c | 20 ++++++++++---------- lib_enc/ivas_init_enc.c | 2 +- 3 files changed, 14 insertions(+), 49 deletions(-) diff --git a/lib_com/edct.c b/lib_com/edct.c index d7af669c65..c336ccdd81 100644 --- a/lib_com/edct.c +++ b/lib_com/edct.c @@ -44,6 +44,7 @@ #include "prot.h" #include "wmc_auto.h" #include /* for cosf, sinf */ +#include static ivas_error get_edct_table( const float **edct_table, @@ -226,7 +227,6 @@ void edst( return; } -#define FAST_EDXT /* optimized FFT-based DCT/DST algorithm */ /*-------------------------------------------------------------------------* * edxt() @@ -243,9 +243,8 @@ void edxt( ) { const float pi_len = EVS_PI / length; - int16_t k, m; + int16_t k; -#ifdef FAST_EDXT if ( kernelType == MDST_II || kernelType == MDCT_II ) { const int16_t Nm1 = length - 1; @@ -351,42 +350,8 @@ void edxt( } } else -#endif - if ( kernelType & 1 ) /* DST */ { - const float offK = ( kernelType == MDST_II && synthesis ? 0.5f : 1.0f - 0.5f * ( kernelType >> 1 ) ); - const float offM = ( kernelType == MDST_II && synthesis ? 1.0f : 0.5f ); - - for ( k = 0; k < length; k++ ) - { - y[k] = 0.f; - for ( m = 0; m < length; m++ ) - { - y[k] += x[m] * sinf( pi_len * ( m + offM ) * ( k + offK ) ); - } - } - if ( offK == 1.f ) - { - y[length - 1] *= 0.5f; /* scale Nyquist sample */ - } - } - else /* kernelType 0, 2: DCT */ - { - const float offK = ( kernelType == MDCT_II && synthesis ? 0.5f : 0.5f - 0.5f * ( kernelType >> 1 ) ); - const float offM = ( kernelType == MDCT_II && synthesis ? 0.0f : 0.5f ); - - for ( k = 0; k < length; k++ ) - { - y[k] = 0.f; - for ( m = 0; m < length; m++ ) - { - y[k] += x[m] * cosf( pi_len * ( m + offM ) * ( k + offK ) ); - } - } - if ( offK == 0.f ) - { - y[0] *= 0.5f; /* scale lowest (i.e. DC) sample */ - } + assert( !"Unsupported Kernel type in edxt()" ); } v_multc( y, ( kernelType == MDCT_II ? -1.f : 1.f ) * sqrtf( 2.f / length ), y, length ); diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 89416ab404..adb160dbe4 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1521,7 +1521,7 @@ ivas_error ivas_init_decoder( return error; } - if ( hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_FOA && st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_STEREO && st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_MONO && !( hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_EXTERNAL && st_ivas->intern_config == IVAS_AUDIO_CONFIG_FOA ) ) + if ( output_config != IVAS_AUDIO_CONFIG_FOA && output_config != IVAS_AUDIO_CONFIG_STEREO && output_config != IVAS_AUDIO_CONFIG_MONO && !( output_config == IVAS_AUDIO_CONFIG_EXTERNAL && st_ivas->intern_config == IVAS_AUDIO_CONFIG_FOA ) ) { if ( ( error = ivas_dirac_dec_config( st_ivas, DIRAC_OPEN ) ) != IVAS_ERR_OK ) { @@ -1543,7 +1543,7 @@ ivas_error ivas_init_decoder( st_ivas->hQMetaData->numTwoDirBands = (uint8_t) st_ivas->hQMetaData->q_direction[0].cfg.nbands; } - ivas_dirac_config_bands( band_grouping, IVAS_MAX_NUM_BANDS, (int16_t) ( st_ivas->hDecoderConfig->output_Fs * INV_CLDFB_BANDWIDTH + 0.5f ), + ivas_dirac_config_bands( band_grouping, IVAS_MAX_NUM_BANDS, (int16_t) ( output_Fs * INV_CLDFB_BANDWIDTH + 0.5f ), st_ivas->hSpar->dirac_to_spar_md_bands, st_ivas->hQMetaData->useLowerBandRes, st_ivas->hSpar->enc_param_start_band, 0, 1 ); } st_ivas->sba_dirac_stereo_flag = ivas_get_sba_dirac_stereo_flag( st_ivas ); @@ -1668,7 +1668,7 @@ ivas_error ivas_init_decoder( return error; } - if ( hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_FOA && st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_STEREO && st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_MONO ) + if ( output_config != IVAS_AUDIO_CONFIG_FOA && output_config != IVAS_AUDIO_CONFIG_STEREO && output_config != IVAS_AUDIO_CONFIG_MONO ) { if ( ( error = ivas_dirac_dec_config( st_ivas, DIRAC_OPEN ) ) != IVAS_ERR_OK ) { @@ -1690,7 +1690,7 @@ ivas_error ivas_init_decoder( st_ivas->hQMetaData->numTwoDirBands = (uint8_t) st_ivas->hQMetaData->q_direction[0].cfg.nbands; } - ivas_dirac_config_bands( band_grouping, IVAS_MAX_NUM_BANDS, (int16_t) ( st_ivas->hDecoderConfig->output_Fs * INV_CLDFB_BANDWIDTH + 0.5f ), + ivas_dirac_config_bands( band_grouping, IVAS_MAX_NUM_BANDS, (int16_t) ( output_Fs * INV_CLDFB_BANDWIDTH + 0.5f ), st_ivas->hSpar->dirac_to_spar_md_bands, st_ivas->hQMetaData->useLowerBandRes, st_ivas->hSpar->enc_param_start_band, 0, 1 ); } @@ -2127,7 +2127,7 @@ ivas_error ivas_init_decoder( if ( st_ivas->hOutSetup.output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB ) { - if ( ( error = ivas_reverb_open( &st_ivas->hReverb, st_ivas->hHrtfStatistics, st_ivas->hRenderConfig, st_ivas->hDecoderConfig->output_Fs ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_reverb_open( &st_ivas->hReverb, st_ivas->hHrtfStatistics, st_ivas->hRenderConfig, output_Fs ) ) != IVAS_ERR_OK ) { return error; } @@ -2157,8 +2157,8 @@ ivas_error ivas_init_decoder( } } - if ( ( error = ivas_rend_openCrend( &( st_ivas->hCrendWrapper ), st_ivas->intern_config, st_ivas->hDecoderConfig->output_config, - st_ivas->hRenderConfig, st_ivas->hHrtfCrend, st_ivas->hHrtfStatistics, st_ivas->hDecoderConfig->output_Fs, 0, ( st_ivas->hSplitBinRend == NULL ) ? 1 : st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_rend_openCrend( &( st_ivas->hCrendWrapper ), st_ivas->intern_config, output_config, + st_ivas->hRenderConfig, st_ivas->hHrtfCrend, st_ivas->hHrtfStatistics, output_Fs, 0, ( st_ivas->hSplitBinRend == NULL ) ? 1 : st_ivas->hSplitBinRend->splitrend.multiBinPoseData.num_poses ) ) != IVAS_ERR_OK ) { return error; } @@ -2260,7 +2260,7 @@ ivas_error ivas_init_decoder( if ( st_ivas->hOutSetup.output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB ) { - if ( ( error = ivas_reverb_open( &st_ivas->hReverb, st_ivas->hHrtfStatistics, st_ivas->hRenderConfig, st_ivas->hDecoderConfig->output_Fs ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_reverb_open( &st_ivas->hReverb, st_ivas->hHrtfStatistics, st_ivas->hRenderConfig, output_Fs ) ) != IVAS_ERR_OK ) { return error; } @@ -2323,7 +2323,7 @@ ivas_error ivas_init_decoder( { if ( st_ivas->hBinRenderer->render_lfe ) { - if ( st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED && st_ivas->hDecoderConfig->output_config != IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) + if ( output_config != IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED && output_config != IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) { /* Account for filterbank delay */ delay_ns += IVAS_FB_DEC_DELAY_NS; @@ -2378,7 +2378,7 @@ ivas_error ivas_init_decoder( if ( ( st_ivas->ivas_format == MASA_FORMAT || st_ivas->ivas_format == MASA_ISM_FORMAT ) && hDecoderConfig->Opt_tsm ) { - if ( st_ivas->hDecoderConfig->output_config == IVAS_AUDIO_CONFIG_EXTERNAL ) + if ( output_config == IVAS_AUDIO_CONFIG_EXTERNAL ) { if ( ( error = ivas_jbm_dec_metadata_open( st_ivas ) ) != IVAS_ERR_OK ) { diff --git a/lib_enc/ivas_init_enc.c b/lib_enc/ivas_init_enc.c index 54e0a8c7e5..336e5fcbfe 100644 --- a/lib_enc/ivas_init_enc.c +++ b/lib_enc/ivas_init_enc.c @@ -782,7 +782,7 @@ ivas_error ivas_init_encoder( { st_ivas->mc_mode = ivas_mc_mode_select( hEncoderConfig->mc_input_setup, ivas_total_brate ); - if ( ( error = ivas_create_lfe_lpf_enc( &st_ivas->hLfeLpf, hEncoderConfig->input_Fs ) ) != IVAS_ERR_OK ) + if ( ( error = ivas_create_lfe_lpf_enc( &st_ivas->hLfeLpf, input_Fs ) ) != IVAS_ERR_OK ) { return error; } -- GitLab From 45d9f2c1c812af61f3a9676c230c0f09bc4c067b Mon Sep 17 00:00:00 2001 From: vaclav Date: Wed, 24 Sep 2025 19:46:51 +0200 Subject: [PATCH 135/340] address reviewers comments --- lib_com/tcx_utils.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib_com/tcx_utils.c b/lib_com/tcx_utils.c index 458a43d72b..d37ecf207b 100644 --- a/lib_com/tcx_utils.c +++ b/lib_com/tcx_utils.c @@ -568,14 +568,11 @@ void lpc2mdct( if ( noInverse ) { - for ( i = 0; i < length; i++ ) - { - mdct_gains[i] = (float) ( sqrt( RealData[i] * RealData[i] + ImagData[i] * ImagData[i] ) ); - } + assert( !"not supported option in lpc2mdct()" ); } else - /* Get amplitude */ { + /* Get amplitude */ for ( i = 0; i < length; i++ ) { mdct_gains[i] = (float) ( 1.0f / max( EPSILON, sqrt( RealData[i] * RealData[i] + ImagData[i] * ImagData[i] ) ) ); -- GitLab From c854546d9c44888cacf1f3dc8cc080549ddd05ce Mon Sep 17 00:00:00 2001 From: vaclav Date: Thu, 25 Sep 2025 12:58:59 +0200 Subject: [PATCH 136/340] issue 1386: fix use-of-uninitialized value in ivas_spar_dec_open(); under FIX_1384_MSAN_ivas_spar_dec_open --- lib_com/options.h | 1 + lib_dec/ivas_init_dec.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..03820b55cb 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_1384_MSAN_ivas_spar_dec_open /* VA: issue 1386: fix use-of-uninitialized value in ivas_spar_dec_open() */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 89416ab404..b5b13dcaf4 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1154,6 +1154,9 @@ ivas_error ivas_init_decoder_front( st_ivas->ism_mode = ISM_MODE_NONE; st_ivas->mc_mode = MC_MODE_NONE; +#ifdef FIX_1384_MSAN_ivas_spar_dec_open + st_ivas->sid_format = -1; +#endif st_ivas->sba_dirac_stereo_flag = 0; /* HRTF binauralization latency in ns */ -- GitLab From c7a29ef04ca97e9820daefd584346219dfd61134 Mon Sep 17 00:00:00 2001 From: vaclav Date: Thu, 25 Sep 2025 13:59:10 +0200 Subject: [PATCH 137/340] introduce define SID_FORMAT_NONE --- lib_com/ivas_cnst.h | 3 +++ lib_dec/ivas_init_dec.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index 6163953764..ff0e1972fe 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -229,6 +229,9 @@ typedef enum /* format signaling in SID frames */ #define SID_FORMAT_NBITS 3 /* Bit 0 | Bit 1 | Bit 2 */ /*-------|-------|------ */ +#ifdef FIX_1384_MSAN_ivas_spar_dec_open +#define SID_FORMAT_NONE (-0x1) /* n/a| n/a| n/a*/ +#endif #define SID_DFT_STEREO 0x0 /* 0| 0| 0 */ #define SID_MDCT_STEREO 0x1 /* 1| 0| 0 */ #define SID_ISM 0x2 /* 0| 1| 0 */ diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index b5b13dcaf4..04b1668bbb 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1155,7 +1155,7 @@ ivas_error ivas_init_decoder_front( st_ivas->mc_mode = MC_MODE_NONE; #ifdef FIX_1384_MSAN_ivas_spar_dec_open - st_ivas->sid_format = -1; + st_ivas->sid_format = SID_FORMAT_NONE; #endif st_ivas->sba_dirac_stereo_flag = 0; -- GitLab From cb0384fa2667575e09f44a90b971c5f24221a266 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Thu, 25 Sep 2025 14:07:02 +0200 Subject: [PATCH 138/340] add jobs to coverage test: - LTV tests with differing decoder framesizes - LTV jobs for renderer - Split rendering test - VoIP BE tests - BE vs EXT test - LC3plus wrapper unit test --- .gitlab-ci.yml | 54 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7a02bd69e5..bdcd7eb104 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2203,31 +2203,69 @@ coverage-test-on-main-scheduled: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh - - make GCOV=1 -j + # compile with coverage enabled + - cmake -B cmake-build -G "Unix Makefiles" -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true -DGCOV=ON + - cmake --build cmake-build -- -j - cp IVAS_rend IVAS_rend_ref # Copy exec to be able to run renderer script # -- Run short test vector suite to check coverage of that first + # codec smoke test + - bash ci/smoke_test.sh coverage + # default renderer framesize is 20ms - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - + # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true + # run renderer smoke test - needed for REFVEC tests + - python3 -m pytest -q -n auto tests/renderer/test_renderer.py + # run renderer test only with REF creation stage - no CUT needed since executables are identical - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --create_ref - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --create_cut - - lcov -c -d obj -o coverage_stv.info # extract coverage of short test vectors here - # remove apps and lib_util files from coverage + # run split rendering smoke test and REF creation + - python3 -m pytest -n auto tests/split_rendering/test_split_rendering.py + - python3 -m pytest -n auto tests/split_rendering/test_split_rendering.py --create_ref + # VoIP BE test + - python3 -m pytest tests/test_be_for_jbm_neutral_dly_profile.py + # split rendering VoIP BE test + - python3 -m pytest -n auto tests/split_rendering/test_voip_be_splitrend_vs_binaural.py + # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted + # - bash ci/run-first-frame-is-sid-test.sh + # TODO consider HRTF binary loading test, or probably already covered by PRM files + # - python3 -m pytest tests/hrtf_binary_loading + # run BE vs EXT test + - python3 -m pytest tests/test_be_for_ext_outputs.py + # run LC3plus wrapper unit test + - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test + # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job + + # -- Extract coverage of short testvectors + - lcov -c -d cmake-build -o coverage_stv.info + # -- Remove apps and lib_util files from coverage - lcov -r coverage_stv.info "*apps*" -o coverage_stv.info - lcov -r coverage_stv.info "*lib_util*" -o coverage_stv.info # -- Add extended coverage of EVS test and long test vectors - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # codec smoke test - bash ci/smoke_test.sh coverage + # default renderer framesize is 20ms - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only + # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true - - python3 -m pytest tests/test_be_for_jbm_neutral_dly_profile.py - - lcov -c -d obj -o coverage.info + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true + # run renderer smoke test - needed for REFVEC tests + - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv + # run renderer test only with REF creation stage - no CUT needed since executables are identical + - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv --create_cut + # split rendering test doesn't support LTV - skipped here + # VoIP BE tests don't support LTV - skipped here + + # -- Extract accumulated coverage of STV + EVS + LTV + - lcov -c -d cmake-build -o coverage.info # remove apps and lib_util files from coverage - lcov -r coverage.info "*apps*" -o coverage.info - lcov -r coverage.info "*lib_util*" -o coverage.info -- GitLab From 2a972eabcfce5c394b64cfa9aef12d2ebab9547c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 25 Sep 2025 16:14:06 +0300 Subject: [PATCH 139/340] PI tests for audio focus --- apps/decoder.c | 22 ++++++++++++++----- tests/rtp/ivasrtp.py | 42 +++++++++++++++++++++++++---------- tests/rtp/test_rtp.py | 51 ++++++++++++++++++++++++++++++------------- 3 files changed, 83 insertions(+), 32 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index df7dcf5168..da0fdd2fbd 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -328,15 +328,20 @@ static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t n break; case IVAS_PI_AUDIO_FOCUS_INDICATION: { - fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "{" ); if ( cur->data.focusIndication.availDirection ) { - fprintf( f_piDataOut, "\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t\t", + fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); + fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "," ); + } } if ( cur->data.focusIndication.availLevel ) { - fprintf( f_piDataOut, "\t\t\t\"level\": %d", cur->data.focusIndication.flvl ); + fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusIndication.flvl ); } fprintf( f_piDataOut, "\n\t\t}" ); } @@ -355,15 +360,20 @@ static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t n case IVAS_PI_RESERVED13: case IVAS_PI_AUDIO_FOCUS_REQUEST: { - fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "{" ); if ( cur->data.focusRequest.availDirection ) { - fprintf( f_piDataOut, "\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t\t", + fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); + fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "," ); + } } if ( cur->data.focusRequest.availLevel ) { - fprintf( f_piDataOut, "\t\t\t\"level\": %d", cur->data.focusRequest.flvl ); + fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusRequest.flvl ); } fprintf( f_piDataOut, "\n\t\t}" ); } diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index 8a2fd0f87d..d5b256cf42 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -42,7 +42,7 @@ import json import base64 import argparse from pathlib import Path -from typing import cast +from typing import cast, Optional NO_REQ="NO_REQ" @@ -225,7 +225,6 @@ class AUDIO_FOCUS_LEVEL(int, Enum): AUDIO_FOCUS_LEVEL_DEFAULT = 14 AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 - @dataclass class RTPHDR: version: int = 2 @@ -363,11 +362,8 @@ class ACOUSTIC_ENVIRONMENT: @dataclass class AUDIO_FOCUS: - w: float = 0.0 - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 - level: AUDIO_FOCUS_LEVEL = AUDIO_FOCUS_LEVEL.AUDIO_FOCUS_LEVEL_DEFAULT + direction: Optional[ORIENTATION] = None + level: Optional[AUDIO_FOCUS_LEVEL] = None @dataclass class PIDATA: @@ -599,6 +595,30 @@ def packAcousticEnv(bitstrm: BitStream, data: any): absCoeff = mapNearestIndex(absorptionCoeffValues, aenv.abscoeff[n]) bitstrm.append(f'uint:2={absCoeff}') +def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: + assert piSize == 1 or piSize == 8 or piSize == 9, "Incorrect PI Data Size for AUDIO_FOCUS" + direction = None + level = None + if piSize == 1: + level = bitstrm.read(4).uint + _ = bitstrm.read(4) + else: + direction = unpackOrientation(bitstrm, 8) + if piSize == 9: + level = bitstrm.read(4).uint + _ = bitstrm.read(4) + + return AUDIO_FOCUS(direction=direction, level=level) + +def packAudioFocus(bitstrm: BitStream, data: any): + assert type(data) == AUDIO_FOCUS, "Diegetic type PI Data expects a data of type AUDIO_FOCUS" + auFocus = cast(AUDIO_FOCUS, data) + if auFocus.direction is not None: + packOrientations(bitstrm, [auFocus.direction]) + if auFocus.level is not None: + bitstrm.append(f'uint:4={auFocus.level}') + bitstrm.append(f'uint:4=0') + PIDataUnpacker = [ unpackOrientation, # SCENE_ORIENTATION, @@ -615,13 +635,13 @@ PIDataUnpacker = [ unpackUnsupported, # ISM_DIRECTIVITY unpackDiegetic, # DIEGETIC_TYPE unpackUnsupported, # RESERVED13 - unpackAudioFocusCommon, # AUDIO_FOCUS_INDICATION + unpackAudioFocus, # AUDIO_FOCUS_INDICATION unpackUnsupported, # RESERVED15 unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION unpackOrientation, # HEAD_ORIENTATION unpackPosition, # LISTENER_POSITION unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION - unpackAudioFocusCommon, # AUDIO_FOCUS_REQUEST + unpackAudioFocus, # AUDIO_FOCUS_REQUEST unpackUnsupported, # PI_LATENCY unpackUnsupported, # R_ISM_ID unpackUnsupported, # R_ISM_GAIN @@ -650,13 +670,13 @@ PIDataPacker = [ packUnsupported, # ISM_DIRECTIVITY packDiegetic, # DIEGETIC_TYPE packUnsupported, # RESERVED13 - packUnsupported, # RESERVED14 + packAudioFocus, # AUDIO_FOCUS_INDICATION packUnsupported, # RESERVED15 packOrientation, # PLAYBACK_DEVICE_ORIENTATION packOrientation, # HEAD_ORIENTATION packPosition, # LISTENER_POSITION packDAS, # DYNAMIC_AUDIO_SUPPRESSION - packOrientation, # AUDIO_FOCUS_DIRECTION + packAudioFocus, # AUDIO_FOCUS_DIRECTION packUnsupported, # PI_LATENCY packUnsupported, # R_ISM_ID packUnsupported, # R_ISM_GAIN diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index d63dcb9da2..1ed8340ac5 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -102,9 +102,14 @@ def test_rtp_bitstream_evs ( ) -@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) -@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) -@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) +#@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) +#@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +#@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) +#@pytest.mark.parametrize("framesPerPacket", [8]) + +@pytest.mark.parametrize("bitrate", [24400]) +@pytest.mark.parametrize("bandwidth", ["SWB"]) +@pytest.mark.parametrize("format", ["MASA"]) @pytest.mark.parametrize("framesPerPacket", [8]) def test_rtp_bitstream_ivas_nodtx ( test_info, @@ -164,21 +169,24 @@ def generatePiData(startTs: int, endTs: int) -> dict: someDesc = lambda : AUDIO_DESCRIPTION(isSpeech=bool(random.getrandbits(1)), isMusic=bool(random.getrandbits(1)), isAmbiance=bool(random.getrandbits(1)), isEditable=bool(random.getrandbits(1)), isBinaural=bool(random.getrandbits(1))) someDAS = lambda : DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=bool(random.getrandbits(1)), preferMusic=bool(random.getrandbits(1)), preferAmbiance=bool(random.getrandbits(1)), level=random.randint(0, 15)) someDIG = lambda : DIEGETIC_TYPE(isDigetic=[ bool(random.getrandbits(1)) for _ in range(random.randint(1, 5)) ]) - someAFocus = lambda : AUDIO_FOCUS(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=random.randint(0, 15)) + 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)) + someAuFocusLvl = lambda : AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) + someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] for ts in range(startTs, endTs, 320): pidata = dict() - pidata["SCENE_ORIENTATION"] = someOrientation() - pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() - pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() - pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() - pidata["HEAD_ORIENTATION"] = someOrientation() - pidata["AUDIO_FOCUS_REQUEST"] = someAFocus() - pidata["LISTENER_POSITION"] = somePosition() - pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() - pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] - pidata["DIEGETIC_TYPE"] = someDIG() - pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) + #pidata["SCENE_ORIENTATION"] = someOrientation() + #pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() + #pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() + #pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() + #pidata["HEAD_ORIENTATION"] = someOrientation() + pidata["AUDIO_FOCUS_REQUEST"] = random.choice(someAuFocusList)() + #pidata["LISTENER_POSITION"] = somePosition() + #pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() + #pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] + #pidata["DIEGETIC_TYPE"] = someDIG() + #pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) data[str(ts)] = pidata return data @@ -225,6 +233,17 @@ def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): for r, d in zip(ref.rt60, dut.rt60): assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" +def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): + if ref.direction is not None or dut.direction is not None: + assert ref.direction is not None, "Audio Focus PI Data missing direction" + 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.0001, "Audio Focus PI Data mismatch in direction w" + assert abs(ref.direction.x - dut.direction.x) < 0.0001, "Audio Focus PI Data mismatch in direction x" + assert abs(ref.direction.y - dut.direction.y) < 0.0001, "Audio Focus PI Data mismatch in direction y" + assert abs(ref.direction.z - dut.direction.z) < 0.0001, "Audio Focus PI Data mismatch in direction z" + assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" + class CSVREADER: def __init__(self, csvFile: Path): self.rIdx = 0 @@ -448,6 +467,8 @@ def run_rtp_bitstream_tests ( isEqualDiegetic(DIEGETIC_TYPE(**decoded), data) elif type(generatedPIData[ts][pitype]) == ACOUSTIC_ENVIRONMENT: isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) + if type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: + isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) elif type(generatedPIData[ts][pitype]) == list: for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): isEqualAD(AUDIO_DESCRIPTION(**d), r) -- GitLab From 5646836cf40059c99d00d98d399637fbd812a4e0 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Thu, 25 Sep 2025 16:11:09 +0200 Subject: [PATCH 140/340] increase coverage test timeout to 12 hours --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bdcd7eb104..c301f1d624 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2198,7 +2198,7 @@ coverage-test-on-main-scheduled: rules: # only run in scheduled pipeline that passes this env vars - if: $COVERAGE_TEST - timeout: 3 hours + timeout: 12 hours script: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh -- GitLab From 75d0d942b6849504d618454304a1e6d20bc8f87f Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Fri, 26 Sep 2025 08:58:20 +0200 Subject: [PATCH 141/340] [fix] missing executables for coverage job --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c301f1d624..c37375d3df 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2206,7 +2206,11 @@ coverage-test-on-main-scheduled: # compile with coverage enabled - cmake -B cmake-build -G "Unix Makefiles" -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true -DGCOV=ON - cmake --build cmake-build -- -j - - cp IVAS_rend IVAS_rend_ref # Copy exec to be able to run renderer script + # copy executables to ref to be able to run some --create_ref tests + - cp IVAS_cod IVAS_cod_ref + - cp IVAS_dec IVAS_dec_ref + - cp IVAS_rend IVAS_rend_ref + - cp ISAR_post_rend ISAR_post_rend_ref # -- Run short test vector suite to check coverage of that first # codec smoke test -- GitLab From d7adc329b9383329c176abf91667484bc9d42f4a Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Fri, 26 Sep 2025 10:14:44 +0200 Subject: [PATCH 142/340] [fix] missing short testvectors for check BE to EXT --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c37375d3df..a54e8e6666 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2237,6 +2237,7 @@ coverage-test-on-main-scheduled: # TODO consider HRTF binary loading test, or probably already covered by PRM files # - python3 -m pytest tests/hrtf_binary_loading # run BE vs EXT test + - python3 tests/create_short_testvectors.py --cut_len 1.0 - python3 -m pytest tests/test_be_for_ext_outputs.py # run LC3plus wrapper unit test - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test -- GitLab From 2bf48e36133f091379092dc85e83a4b57f7a3240 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Fri, 26 Sep 2025 11:58:53 +0300 Subject: [PATCH 143/340] Fix audio focus tests --- tests/rtp/test_rtp.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 1ed8340ac5..4e394ee478 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -102,14 +102,9 @@ def test_rtp_bitstream_evs ( ) -#@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) -#@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) -#@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) -#@pytest.mark.parametrize("framesPerPacket", [8]) - -@pytest.mark.parametrize("bitrate", [24400]) -@pytest.mark.parametrize("bandwidth", ["SWB"]) -@pytest.mark.parametrize("format", ["MASA"]) +@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) @pytest.mark.parametrize("framesPerPacket", [8]) def test_rtp_bitstream_ivas_nodtx ( test_info, @@ -176,17 +171,17 @@ def generatePiData(startTs: int, endTs: int) -> dict: for ts in range(startTs, endTs, 320): pidata = dict() - #pidata["SCENE_ORIENTATION"] = someOrientation() - #pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() - #pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() - #pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() - #pidata["HEAD_ORIENTATION"] = someOrientation() + pidata["SCENE_ORIENTATION"] = someOrientation() + pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() + pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() + pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() + pidata["HEAD_ORIENTATION"] = someOrientation() pidata["AUDIO_FOCUS_REQUEST"] = random.choice(someAuFocusList)() - #pidata["LISTENER_POSITION"] = somePosition() - #pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() - #pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] - #pidata["DIEGETIC_TYPE"] = someDIG() - #pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) + pidata["LISTENER_POSITION"] = somePosition() + pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() + pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] + pidata["DIEGETIC_TYPE"] = someDIG() + pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) data[str(ts)] = pidata return data @@ -238,10 +233,10 @@ def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): assert ref.direction is not None, "Audio Focus PI Data missing direction" 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.0001, "Audio Focus PI Data mismatch in direction w" - assert abs(ref.direction.x - dut.direction.x) < 0.0001, "Audio Focus PI Data mismatch in direction x" - assert abs(ref.direction.y - dut.direction.y) < 0.0001, "Audio Focus PI Data mismatch in direction y" - assert abs(ref.direction.z - dut.direction.z) < 0.0001, "Audio Focus PI Data mismatch in direction z" + assert abs(ref.direction['w'] - dut.direction.w) < 0.0001, "Audio Focus PI Data mismatch in direction w" + assert abs(ref.direction['x'] - dut.direction.x) < 0.0001, "Audio Focus PI Data mismatch in direction x" + assert abs(ref.direction['y'] - dut.direction.y) < 0.0001, "Audio Focus PI Data mismatch in direction y" + assert abs(ref.direction['z'] - dut.direction.z) < 0.0001, "Audio Focus PI Data mismatch in direction z" assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" class CSVREADER: @@ -467,7 +462,7 @@ def run_rtp_bitstream_tests ( isEqualDiegetic(DIEGETIC_TYPE(**decoded), data) elif type(generatedPIData[ts][pitype]) == ACOUSTIC_ENVIRONMENT: isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) - if type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: + elif type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) elif type(generatedPIData[ts][pitype]) == list: for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): -- GitLab From fc1241075fd1c3b4fd50d07172e19508301c9a0c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Fri, 26 Sep 2025 12:21:35 +0300 Subject: [PATCH 144/340] Add flags --- lib_util/ivas_rtp_pi_data.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index 24ecabe6e8..c939c4539c 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -659,7 +659,11 @@ static const PACK_PI_FN packPiDataFuntions[IVAS_PI_MAX_ID] = { packUnsupportedData, /* DIEGETIC_TYPE */ #endif packUnsupportedData, /* RESERVED13 */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 packAudioFocusCommon,/* AUDIO_FOCUS_INDICATION */ +#else + packUnsupportedData, /* AUDIO_FOCUS_INDICATION */ +#endif packUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 packOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ @@ -714,7 +718,11 @@ static const UNPACK_PI_FN unpackPiDataFuntions[IVAS_PI_MAX_ID] = { unpackUnsupportedData, /* DIEGETIC_TYPE */ #endif unpackUnsupportedData, /* RESERVED13 */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 unpackAudioFocusCommon,/* AUDIO_FOCUS_INDICATION */ +#else + unpackUnsupportedData, /* AUDIO_FOCUS_INDICATION */ +#endif unpackUnsupportedData, /* RESERVED15 */ #ifdef RTP_S4_251135_CR26253_0016_REV1 unpackOrientation, /* PLAYBACK_DEVICE_ORIENTATION */ -- GitLab From 80f5e00d594d452d3e4583d4e33df442d97149ce Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Fri, 26 Sep 2025 13:38:35 +0200 Subject: [PATCH 145/340] The ambi_converter application is reading the correct amount of samples now. --- apps/ambi_converter.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index affd3cf5a8..d4fa974d81 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -78,7 +78,7 @@ int main( int argc, char *argv[] ) printf( "Ambisonics converter program\n" ); printf( "----------------------------------------------------------------------------------\n" ); printf( "Usage:\n" ); - printf( "./ambi_conveter input_file output_file input_convention output_convention\n" ); + printf( "./ambi_converter input_file output_file input_convention output_convention\n" ); printf( "\n" ); printf( "input_convention and output convention must be an integer number in [0,5]\n" ); printf( "the following conventions are supported:\n" ); @@ -117,6 +117,7 @@ int main( int argc, char *argv[] ) order = (int16_t) sqrtf( numChannels ) - 1; assert( order > 0 && order <= 3 ); + samples = L_FRAME48k * numChannels; while ( ReadWavShort( wavFile_in, samples, numSamples, &numSamplesRead32 ) == __TWI_SUCCESS ) { int32_t err = 0; @@ -126,7 +127,7 @@ int main( int argc, char *argv[] ) break; } - for ( uint16_t i = 0; i < numSamplesRead32; i++ ) + for ( uint16_t i = 0; i < numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { @@ -141,7 +142,7 @@ int main( int argc, char *argv[] ) } - for ( uint16_t i = 0; i < numSamplesRead32; i++ ) + for ( uint16_t i = 0; i < numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { -- GitLab From b83ac90a4e341ce0d7476997c29791b0ee82217d Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Fri, 26 Sep 2025 14:03:37 +0200 Subject: [PATCH 146/340] removed a warning where a 32 bit value was compared against a 16 bit value in main() of the ambi_converter.c application. --- apps/ambi_converter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index d4fa974d81..40091f613b 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -127,7 +127,7 @@ int main( int argc, char *argv[] ) break; } - for ( uint16_t i = 0; i < numSamplesRead32 / numChannels; i++ ) + for ( uint16_t i = 0; i < (uint16_t)numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { @@ -142,7 +142,7 @@ int main( int argc, char *argv[] ) } - for ( uint16_t i = 0; i < numSamplesRead32 / numChannels; i++ ) + for ( uint16_t i = 0; i < (uint16_t)numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { -- GitLab From f1134c0c8180b8f1b55ce880bc1b3b57ebf80e83 Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Fri, 26 Sep 2025 14:09:35 +0200 Subject: [PATCH 147/340] typo in main() of ambi_converter.c --- apps/ambi_converter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index 40091f613b..fd7cd249ab 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -117,7 +117,7 @@ int main( int argc, char *argv[] ) order = (int16_t) sqrtf( numChannels ) - 1; assert( order > 0 && order <= 3 ); - samples = L_FRAME48k * numChannels; + numSamples = L_FRAME48k * numChannels; while ( ReadWavShort( wavFile_in, samples, numSamples, &numSamplesRead32 ) == __TWI_SUCCESS ) { int32_t err = 0; -- GitLab From 6e12182e714412bb483b5c5b247a6883ef6d9b3d Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Fri, 26 Sep 2025 14:14:49 +0200 Subject: [PATCH 148/340] [fix] renderer LTV test accidentally using CUT mode --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a54e8e6666..77603623b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2265,7 +2265,7 @@ coverage-test-on-main-scheduled: # run renderer smoke test - needed for REFVEC tests - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv # run renderer test only with REF creation stage - no CUT needed since executables are identical - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv --create_cut + - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv --create_ref # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here -- GitLab From a39f8e812cf4846210b75d84d10a703f57c44f68 Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Fri, 26 Sep 2025 14:30:49 +0200 Subject: [PATCH 149/340] changed the output of the ambi_converter application. --- apps/ambi_converter.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index fd7cd249ab..1686fb2ad9 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -60,6 +60,7 @@ int main( int argc, char *argv[] ) int16_t samples[L_FRAME48k * AMBI_MAX_CHANNELS]; int16_t order = 0; int16_t numChannels; + const char *name_conventions[6]= { "ACN-SN3D", "ACN-N3D", "FuMa-MaxN", "FuMa-FuMa", "SID-SN3D", "SID-N3D" }; AMBI_FMT in_format, out_format; @@ -73,9 +74,9 @@ int main( int argc, char *argv[] ) out[j] = &samples_f_out[j * L_FRAME48k]; } + printf( "Ambisonics converter program\n" ); if ( argc != 5 ) { - printf( "Ambisonics converter program\n" ); printf( "----------------------------------------------------------------------------------\n" ); printf( "Usage:\n" ); printf( "./ambi_converter input_file output_file input_convention output_convention\n" ); @@ -97,8 +98,12 @@ int main( int argc, char *argv[] ) fileName_out = argv[2]; in_format = atoi( argv[3] ); out_format = atoi( argv[4] ); - - printf( "In %d, Out: %d\n", in_format, out_format ); + if ( in_format < 0 || out_format < 0 || in_format > 5 || out_format > 5 ) + { + printf( "input_convention and output convention must be an integer number in [0,5]\n" ); + return -1; + } + printf( "In: [%s], Out: [%s]\n",name_conventions[in_format], name_conventions[out_format] ); wavFile_in = OpenWav( fileName_in, &samplingRate, &numChannels, &samplesInFile, &bps ); if ( !wavFile_in ) -- GitLab From 0618df3da8c083a513e118c61e3136ff84067dfe Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Fri, 26 Sep 2025 14:43:58 +0200 Subject: [PATCH 150/340] applied the clang patch. --- apps/ambi_converter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index 1686fb2ad9..673d4a63f4 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -60,7 +60,7 @@ int main( int argc, char *argv[] ) int16_t samples[L_FRAME48k * AMBI_MAX_CHANNELS]; int16_t order = 0; int16_t numChannels; - const char *name_conventions[6]= { "ACN-SN3D", "ACN-N3D", "FuMa-MaxN", "FuMa-FuMa", "SID-SN3D", "SID-N3D" }; + const char *name_conventions[6] = { "ACN-SN3D", "ACN-N3D", "FuMa-MaxN", "FuMa-FuMa", "SID-SN3D", "SID-N3D" }; AMBI_FMT in_format, out_format; @@ -103,7 +103,7 @@ int main( int argc, char *argv[] ) printf( "input_convention and output convention must be an integer number in [0,5]\n" ); return -1; } - printf( "In: [%s], Out: [%s]\n",name_conventions[in_format], name_conventions[out_format] ); + printf( "In: [%s], Out: [%s]\n", name_conventions[in_format], name_conventions[out_format] ); wavFile_in = OpenWav( fileName_in, &samplingRate, &numChannels, &samplesInFile, &bps ); if ( !wavFile_in ) @@ -132,7 +132,7 @@ int main( int argc, char *argv[] ) break; } - for ( uint16_t i = 0; i < (uint16_t)numSamplesRead32 / numChannels; i++ ) + for ( uint16_t i = 0; i < (uint16_t) numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { @@ -147,7 +147,7 @@ int main( int argc, char *argv[] ) } - for ( uint16_t i = 0; i < (uint16_t)numSamplesRead32 / numChannels; i++ ) + for ( uint16_t i = 0; i < (uint16_t) numSamplesRead32 / numChannels; i++ ) { for ( int16_t j = 0; j < numChannels; j++ ) { -- GitLab From 70e0a1328e3966ef04a563759f67017e46597ac1 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Sun, 28 Sep 2025 16:44:46 +0200 Subject: [PATCH 151/340] initialize pointer prm_sqQ, which might be uninitialized in case of bfi == 1 --- lib_com/options.h | 1 + lib_dec/ivas_mdct_core_dec.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..c835ce480a 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_1387_INIT_PRM_SQQ /* FhG: initialize pointer prm_sqQ, which might be uninitialized in case of bfi == 1 */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/ivas_mdct_core_dec.c b/lib_dec/ivas_mdct_core_dec.c index 4e7f54eecf..eef4b37bf8 100644 --- a/lib_dec/ivas_mdct_core_dec.c +++ b/lib_dec/ivas_mdct_core_dec.c @@ -532,6 +532,9 @@ void ivas_mdct_core_invQ( set_s( total_nbbits, 0, CPE_CHANNELS ); set_s( bitsRead, 0, CPE_CHANNELS ); tmp_concealment_method = 0; +#ifdef FIX_1387_INIT_PRM_SQQ + prm_sqQ = NULL; /* set prm_sqQ to NULL - in case of bfi == 1 it's not set or needed, but it triggers sanitizers */ +#endif for ( ch = 0; ch < CPE_CHANNELS; ch++ ) { -- GitLab From b9c539e3d487aadaafab6f6acd3be26056778b57 Mon Sep 17 00:00:00 2001 From: vaclav Date: Mon, 29 Sep 2025 08:57:51 +0200 Subject: [PATCH 152/340] HRTF updates - bring float main in line with BASOP main; under FIX_HRTF_LEFTOVERS --- lib_com/options.h | 1 + lib_util/hrtf_file_reader.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..18b830f2b6 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_HRTF_LEFTOVERS /* VA: HRTF updates - bring float main in line with BASOP main */ /* #################### End BE switches ################################## */ diff --git a/lib_util/hrtf_file_reader.c b/lib_util/hrtf_file_reader.c index 66c886b7b6..0affb9401c 100644 --- a/lib_util/hrtf_file_reader.c +++ b/lib_util/hrtf_file_reader.c @@ -816,7 +816,11 @@ static ivas_error load_reverb_from_binary( hHrtfStatistics->average_energy_r_dyn = (float *) malloc( lr_iac_len * sizeof( float ) ); hHrtfStatistics->inter_aural_coherence_dyn = (float *) malloc( lr_iac_len * sizeof( float ) ); +#ifdef FIX_HRTF_LEFTOVERS + if ( hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->average_energy_r_dyn == NULL || hHrtfStatistics->inter_aural_coherence_dyn == NULL ) +#else if ( hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->inter_aural_coherence_dyn == NULL ) +#endif { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Could not allocate memory for hrtf data" ); } -- GitLab From a9cc66bc602f952a946854d6ca72e5494c62462f Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 29 Sep 2025 10:43:25 +0300 Subject: [PATCH 153/340] Code review, add more descriptive numbering and comments, also disable headtracking with ext orientations --- lib_com/options.h | 2 +- lib_dec/lib_dec.c | 2 +- lib_rend/ivas_rotation.c | 41 +++++++++++++++++++++++++++++++++++++-- lib_rend/ivas_stat_rend.h | 2 +- tests/rtp/ivasrtp.py | 2 +- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 58ca69d570..b1f5965027 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -158,7 +158,7 @@ /* ################## Start DEVELOPMENT switches ######################### */ -//#define RTP_S4_251135_CR26253_0016_REV1 /* RTP Pack/Unpack API corresponding to CR 26253 */ +#define RTP_S4_251135_CR26253_0016_REV1 /* RTP Pack/Unpack API corresponding to CR 26253 */ #define IVAS_RTPDUMP /* RTPDUMP writing and reading for IVAS payloads */ /* ################### Start BE switches ################################# */ diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index b46def86e4..c3b7e69a82 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -3843,7 +3843,7 @@ void IVAS_DEC_setDiegeticInputPI( if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) { int8_t i; - for ( i = 0; i < 4; i++ ) + for ( i = 0; i < (1 + IVAS_MAX_NUM_OBJECTS); i++ ) { hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; } diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 05fb92cd18..8b62d7a111 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -864,7 +864,7 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; #ifdef RTP_S4_251135_CR26253_0016_REV1 - for ( i = 0; i < 4; i++ ) + for ( i = 0; i < (1 + IVAS_MAX_NUM_OBJECTS); i++ ) { ( *hCombinedOrientationData )->isDiegeticInputPI[i] = true; } @@ -1030,8 +1030,8 @@ ivas_error combine_external_and_head_orientations( } else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { - /* Head rotation only */ #ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) { for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) @@ -1041,12 +1041,14 @@ ivas_error combine_external_and_head_orientations( } else { + /* Head rotation only */ for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; } } #else + /* Head rotation only */ for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; @@ -1128,6 +1130,40 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternion_frozen_head = identity; hCombinedOrientationData->isHeadRotationFrozen = 0; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ + if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) + { + continue; + } + else + { + /* Use the most recent head rotation */ + if ( hExtOrientationData->enableHeadRotation[i] == 1 ) + { + if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) + { + QuaternionProduct( hCombinedOrientationData->Quaternions[i], headRotQuaternions[i], &hCombinedOrientationData->Quaternions[i] ); + } + else + { + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + } + } + /* Use the freezed head rotation */ + else if ( hExtOrientationData->enableHeadRotation[i] == 2 ) + { + if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) + { + QuaternionProduct( hCombinedOrientationData->Quaternions[i], hCombinedOrientationData->Quaternion_frozen_head, &hCombinedOrientationData->Quaternions[i] ); + } + else + { + hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; + } + } + } +#else /* Use the most recent head rotation */ if ( hExtOrientationData->enableHeadRotation[i] == 1 ) { @@ -1152,6 +1188,7 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; } } +#endif /* Reset the combined orientations to identity */ if ( hExtOrientationData->enableHeadRotation[i] == 0 && hExtOrientationData->enableExternalOrientation[i] == 0 ) diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index fb8bf44b83..3e6270e229 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -711,7 +711,7 @@ typedef struct ivas_combined_orientation_struct int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; #ifdef RTP_S4_251135_CR26253_0016_REV1 - bool isDiegeticInputPI[5]; + bool isDiegeticInputPI[1 + IVAS_MAX_NUM_OBJECTS]; bool isDiegeticInputPISet; #endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index d5b256cf42..1eec45fdce 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -611,7 +611,7 @@ def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: return AUDIO_FOCUS(direction=direction, level=level) def packAudioFocus(bitstrm: BitStream, data: any): - assert type(data) == AUDIO_FOCUS, "Diegetic type PI Data expects a data of type AUDIO_FOCUS" + assert type(data) == AUDIO_FOCUS, "Audio focus PI Data expects a data of type AUDIO_FOCUS" auFocus = cast(AUDIO_FOCUS, data) if auFocus.direction is not None: packOrientations(bitstrm, [auFocus.direction]) -- GitLab From 7f5feb6ab0b774838f68a5734c78e09907bc6412 Mon Sep 17 00:00:00 2001 From: vaclav Date: Mon, 29 Sep 2025 09:55:00 +0200 Subject: [PATCH 154/340] improve ambisonicsBuses init in the renderer app --- apps/renderer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/renderer.c b/apps/renderer.c index caa1ce00d8..bf2587680a 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -2611,7 +2611,14 @@ static CmdlnArgs defaultArgs( args.outConfig.audioConfig = IVAS_AUDIO_CONFIG_INVALID; args.outConfig.outSetupCustom.num_spk = 0; args.outConfig.outSetupCustom.num_lfe = 0; +#ifdef FIX_HRTF_LEFTOVERS + for ( i = 0; i < RENDERER_MAX_SBA_INPUTS; ++i ) + { + args.inConfig.ambisonicsBuses[i].audioConfig = IVAS_AUDIO_CONFIG_INVALID; + } +#else args.inConfig.ambisonicsBuses->audioConfig = IVAS_AUDIO_CONFIG_INVALID; +#endif for ( i = 0; i < RENDERER_MAX_ISM_INPUTS + RENDERER_MAX_MASA_INPUTS; ++i ) { -- GitLab From 4438b209ae983fa169cc84e826f047db65c4b646 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Mon, 29 Sep 2025 10:49:31 +0200 Subject: [PATCH 155/340] make sure hIGFDec->infoIGFStopFreq is initialized --- lib_com/options.h | 1 + lib_dec/igf_dec.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..b0747cfe95 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -165,6 +165,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ +#define FIX_1385_INIT_IGF_STOP_FREQ /* FhG: Initialize infoIGFStopFreq in init_igf_dec() */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/igf_dec.c b/lib_dec/igf_dec.c index dac1f02787..ba7c4993fd 100755 --- a/lib_dec/igf_dec.c +++ b/lib_dec/igf_dec.c @@ -1592,6 +1592,9 @@ void init_igf_dec( hIGFDec->flag_sparse = &hIGFDec->flag_sparseBuf[0]; hIGFDec->infoTCXNoise = &hIGFDec->infoTCXNoiseBuf[0]; hIGFDec->virtualSpec = &hIGFDec->virtualSpecBuf[0]; +#ifdef FIX_1385_INIT_IGF_STOP_FREQ + hIGFDec->infoIGFStopFreq = 0; +#endif return; } -- GitLab From 3b2049c97356bd3c5cf04e8bcaf42d76dc3ceb31 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 29 Sep 2025 11:59:00 +0300 Subject: [PATCH 156/340] Fix object editing issues 1399 and 1400 --- lib_com/options.h | 2 +- lib_dec/ivas_jbm_dec.c | 14 ++++++++++++++ lib_dec/ivas_omasa_dec.c | 10 +++++++++- lib_rend/ivas_dirac_dec_binaural_functions.c | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..22910491a5 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -176,7 +176,7 @@ #define NONBE_1244_FIX_SWB_BWE_MEMORY /* VA: issue 1244: fix to SWB BWE memory in case of switching from FB coding - pending a review by Huawei */ #define NONBE_1122_KEEP_EVS_MODE_UNCHANGED /* FhG: Disables fix for issue 1122 in EVS mode to keep BE tests green. This switch should be removed once the 1122 fix is added to EVS via a CR. */ #define NONBE_1328_FIX_NON_LINEARITY /* VA: Fix possible issue when computing bwe_exc_extended and previous frame were almost 0 */ - +#define NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES /* Nokia: Fix for issues 1399: obj edit broken with MC/SBA output in VOIP, and 1400: negative energy estimate used for gaining. */ /* ##################### End NON-BE switches ########################### */ diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 5b8668c6d3..576967b25d 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -2907,6 +2907,20 @@ void ivas_dec_prepare_renderer( nchan_transport_ism = st_ivas->nchan_ism; } +#ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES + if ( st_ivas->hDecoderConfig->Opt_tsm && st_ivas->ism_mode == ISM_MASA_MODE_DISC && st_ivas->renderer_type == RENDERER_DIRAC) + { + /* Gain MASA part, if edited */ + if ( st_ivas->hMasaIsmData->masa_gain_is_edited ) + { + for ( int16_t ch = 0; ch < 2; ch++ ) + { + v_multc( st_ivas->hTcBuffer->tc[ch], st_ivas->hMasaIsmData->gain_masa_edited, st_ivas->hTcBuffer->tc[ch], st_ivas->hTcBuffer->n_samples_available ); + } + } + } +#endif + for ( n = 0; n < nchan_transport_ism; n++ ) { if ( st_ivas->ism_mode == ISM_MASA_MODE_DISC && st_ivas->renderer_type == RENDERER_BINAURAL_PARAMETRIC ) diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index fdbca49b31..346c6f591d 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -680,7 +680,11 @@ void ivas_omasa_dirac_rend_jbm( { mvr2r( &output_f[CPE_CHANNELS][st_ivas->hTcBuffer->n_samples_rendered], data_separated_objects[0], *nSamplesRendered ); +#ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES + if ( st_ivas->ism_mode == ISM_MASA_MODE_PARAM_ONE_OBJ ) +#else if ( !st_ivas->hDecoderConfig->Opt_tsm && st_ivas->ism_mode == ISM_MASA_MODE_PARAM_ONE_OBJ ) +#endif { /* Gain separated object, if edited */ for ( n = 0; n < st_ivas->nchan_ism; n++ ) @@ -699,13 +703,17 @@ void ivas_omasa_dirac_rend_jbm( mvr2r( &output_f[n + CPE_CHANNELS][st_ivas->hTcBuffer->n_samples_rendered], data_separated_objects[n], *nSamplesRendered ); /* Gain discrete objects, if edited */ +#ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES + if ( st_ivas->hMasaIsmData->ism_gain_is_edited[n] ) +#else if ( !st_ivas->hDecoderConfig->Opt_tsm && st_ivas->hMasaIsmData->ism_gain_is_edited[n] ) +#endif { v_multc( data_separated_objects[n], st_ivas->hMasaIsmData->gain_ism_edited[n], data_separated_objects[n], *nSamplesRendered ); } } - /* Gain MASA part, if edited */ + /* Gain MASA part, if edited in G192. MASA gaining with VOIP is done in ivas_dec_prepare_renderer() */ if ( !st_ivas->hDecoderConfig->Opt_tsm && st_ivas->hMasaIsmData->masa_gain_is_edited ) { for ( int16_t ch = 0; ch < 2; ch++ ) diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 9e40f63ee8..684180e239 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -3112,6 +3112,13 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* Limit target energies to non-negative values */ +#ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES + for ( ch = 0; ch < 2; ch++ ) + { + totalTargetEneCh[ch] = max( totalTargetEneCh[ch], 0.0f ); + } +#endif + /* due to rounding, the sum may exceed 1.0f ever so slightly, so clip it */ ratioAccOrig = min( ratioAccOrig, 1.0f ); if ( masaGainEdited ) -- GitLab From 2f1d83406d9f9d81587204791c9a3ccf6132d01d Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Mon, 29 Sep 2025 11:00:53 +0200 Subject: [PATCH 157/340] ambi_convert() can handle samplerates other than 48k now. --- apps/ambi_converter.c | 16 +++++------ lib_util/ambi_convert.c | 64 +++++++++++++++++++++++------------------ lib_util/ambi_convert.h | 40 ++++++++++++++------------ 3 files changed, 66 insertions(+), 54 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index 673d4a63f4..928def1dc3 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -52,26 +52,26 @@ int main( int argc, char *argv[] ) uint32_t samplingRate; uint32_t samplesInFile; - uint32_t numSamples = L_FRAME48k; + uint32_t numSamples = AMBI_MAX_FRAME_LENGTH; uint32_t numSamplesRead32 = 0; uint32_t numSamplesClipped = 0; int16_t bps; - int16_t samples[L_FRAME48k * AMBI_MAX_CHANNELS]; + int16_t samples[AMBI_MAX_FRAME_LENGTH * AMBI_MAX_CHANNELS]; int16_t order = 0; int16_t numChannels; const char *name_conventions[6] = { "ACN-SN3D", "ACN-N3D", "FuMa-MaxN", "FuMa-FuMa", "SID-SN3D", "SID-N3D" }; AMBI_FMT in_format, out_format; - float samples_f_in[L_FRAME48k * AMBI_MAX_CHANNELS]; - float samples_f_out[L_FRAME48k * AMBI_MAX_CHANNELS]; + float samples_f_in[AMBI_MAX_FRAME_LENGTH * AMBI_MAX_CHANNELS]; + float samples_f_out[AMBI_MAX_FRAME_LENGTH * AMBI_MAX_CHANNELS]; float *in[AMBI_MAX_CHANNELS], *out[AMBI_MAX_CHANNELS]; for ( int16_t j = 0; j < AMBI_MAX_CHANNELS; j++ ) { - in[j] = &samples_f_in[j * L_FRAME48k]; - out[j] = &samples_f_out[j * L_FRAME48k]; + in[j] = &samples_f_in[j * AMBI_MAX_FRAME_LENGTH]; + out[j] = &samples_f_out[j * AMBI_MAX_FRAME_LENGTH]; } printf( "Ambisonics converter program\n" ); @@ -122,7 +122,7 @@ int main( int argc, char *argv[] ) order = (int16_t) sqrtf( numChannels ) - 1; assert( order > 0 && order <= 3 ); - numSamples = L_FRAME48k * numChannels; + numSamples = ( samplingRate * 20 * numChannels ) / 1000; /* 20ms worth of samples */ while ( ReadWavShort( wavFile_in, samples, numSamples, &numSamplesRead32 ) == __TWI_SUCCESS ) { int32_t err = 0; @@ -140,7 +140,7 @@ int main( int argc, char *argv[] ) } } - if ( ( err = convert_ambi_format( in, out, order, in_format, out_format ) ) != 0 ) + if ( ( err = convert_ambi_format( in, out, order, in_format, out_format, numSamples / numChannels ) ) != 0 ) { printf( "Error converting the input signal!\n" ); return err; diff --git a/lib_util/ambi_convert.c b/lib_util/ambi_convert.c index 282f4a0872..4c6a46d7c8 100644 --- a/lib_util/ambi_convert.c +++ b/lib_util/ambi_convert.c @@ -124,15 +124,16 @@ static const int16_t REORDER_ACN_SID[AMBI_MAX_CHANNELS] = { 0, --------------------------------------------------------------------------*/ AMBI_CONVERT_ERROR convert_ambi_format( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_FMT in_format, /* i: input ambisonics format */ - AMBI_FMT out_format /* i: output ambisonics format */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_FMT in_format, /* i: input ambisonics format */ + AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ ) { - float tmp[AMBI_MAX_CHANNELS * L_FRAME48k]; + float tmp[AMBI_MAX_CHANNELS * AMBI_MAX_FRAME_LENGTH]; float *p_tmp[AMBI_MAX_CHANNELS]; AMBI_CONVERT_ERROR err = AMBI_CONVERT_OK; @@ -144,6 +145,11 @@ AMBI_CONVERT_ERROR convert_ambi_format( assert( order <= 3 ); + if ( frame_length > AMBI_MAX_FRAME_LENGTH ) + { + return AMBI_CONVERT_UNSUPPORTED_FRAME_LENGTH; + } + if ( in_format != AMBI_FMT_ACN_SN3D && out_format != AMBI_FMT_ACN_SN3D ) { assert( 0 && "Conversion only supported to and from ACN-SN3D" ); @@ -151,7 +157,7 @@ AMBI_CONVERT_ERROR convert_ambi_format( for ( int16_t j = 0; j < AMBI_MAX_CHANNELS; j++ ) { - p_tmp[j] = &tmp[j * L_FRAME48k]; + p_tmp[j] = &tmp[j * frame_length]; } switch ( in_format ) @@ -218,18 +224,18 @@ AMBI_CONVERT_ERROR convert_ambi_format( { if ( ch_ord_in != ch_ord_out ) { - if ( ( err = renormalize_channels( in, p_tmp, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = renormalize_channels( in, p_tmp, order, ch_norm_in, ch_norm_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } - if ( ( err = reorder_channels( p_tmp, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = reorder_channels( p_tmp, out, order, ch_ord_in, ch_ord_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } } else { - if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } @@ -237,7 +243,7 @@ AMBI_CONVERT_ERROR convert_ambi_format( } else if ( in_format == AMBI_FMT_ACN_SN3D && ch_ord_in != ch_ord_out ) { - if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } @@ -246,18 +252,18 @@ AMBI_CONVERT_ERROR convert_ambi_format( { if ( ch_ord_in != ch_ord_out ) { - if ( ( err = reorder_channels( in, p_tmp, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = reorder_channels( in, p_tmp, order, ch_ord_in, ch_ord_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } - if ( ( err = renormalize_channels( p_tmp, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = renormalize_channels( p_tmp, out, order, ch_norm_in, ch_norm_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } } else { - if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = renormalize_channels( in, out, order, ch_norm_in, ch_norm_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } @@ -265,7 +271,7 @@ AMBI_CONVERT_ERROR convert_ambi_format( } else if ( out_format == AMBI_FMT_ACN_SN3D && ch_ord_in != ch_ord_out ) { - if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out ) ) != AMBI_CONVERT_OK ) + if ( ( err = reorder_channels( in, out, order, ch_ord_in, ch_ord_out, frame_length ) ) != AMBI_CONVERT_OK ) { return err; } @@ -278,7 +284,7 @@ AMBI_CONVERT_ERROR convert_ambi_format( for ( i_chan = 0; i_chan < n_chan; i_chan++ ) { int16_t i = 0; - for ( i = 0; i < L_FRAME48k; i++ ) + for ( i = 0; i < frame_length; i++ ) { out[i_chan][i] = in[i_chan][i]; } @@ -299,11 +305,12 @@ AMBI_CONVERT_ERROR convert_ambi_format( --------------------------------------------------------------------------*/ AMBI_CONVERT_ERROR renormalize_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_NORM out_format /* i: output ambisonics format */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ ) { int16_t n_chan = ( order + 1 ) * ( order + 1 ); @@ -358,7 +365,7 @@ AMBI_CONVERT_ERROR renormalize_channels( for ( i_chan = 0; i_chan < n_chan; i_chan++ ) { float conversion_factor = conversion_table[i_chan]; - for ( i = 0; i < L_FRAME48k; i++ ) + for ( i = 0; i < frame_length; i++ ) { out[i_chan][i] = in[i_chan][i] * conversion_factor; } @@ -374,11 +381,12 @@ AMBI_CONVERT_ERROR renormalize_channels( --------------------------------------------------------------------------*/ AMBI_CONVERT_ERROR reorder_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_ORDER out_format /* i: output ambisonics format */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ ) { int16_t n_chan = ( order + 1 ) * ( order + 1 ); @@ -421,7 +429,7 @@ AMBI_CONVERT_ERROR reorder_channels( return AMBI_CONVERT_UNSUPPORTED_CONVERSION; } - for ( i = 0; i < L_FRAME48k; i++ ) + for ( i = 0; i < frame_length; i++ ) { for ( i_chan = 0; i_chan < n_chan; i_chan++ ) { diff --git a/lib_util/ambi_convert.h b/lib_util/ambi_convert.h index 825d65127c..c680a7ff55 100644 --- a/lib_util/ambi_convert.h +++ b/lib_util/ambi_convert.h @@ -35,8 +35,8 @@ #include -#define L_FRAME48k 960 -#define AMBI_MAX_CHANNELS 16 +#define AMBI_MAX_FRAME_LENGTH 960 /* 20ms at 48 kHz Sampling rate */ +#define AMBI_MAX_CHANNELS 16 typedef enum { @@ -66,30 +66,34 @@ typedef enum typedef enum { AMBI_CONVERT_OK = 0, - AMBI_CONVERT_UNSUPPORTED_CONVERSION + AMBI_CONVERT_UNSUPPORTED_CONVERSION, + AMBI_CONVERT_UNSUPPORTED_FRAME_LENGTH } AMBI_CONVERT_ERROR; AMBI_CONVERT_ERROR convert_ambi_format( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_FMT in_format, /* i: input ambisonics format */ - AMBI_FMT out_format /* i: output ambisonics format */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_FMT in_format, /* i: input ambisonics format */ + AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ ); AMBI_CONVERT_ERROR renormalize_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_NORM out_format /* i: output ambisonics format */ -); - -AMBI_CONVERT_ERROR reorder_channels( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_ORDER out_format /* i: output ambisonics format */ + AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ +); + +AMBI_CONVERT_ERROR reorder_channels( + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ + int16_t order, /* i: ambisonics order */ + AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ + const int16_t frame_length /* i: input/output frame length */ ); #endif -- GitLab From 5c813befd3e879afe87ab232dfc6b972ffb945fe Mon Sep 17 00:00:00 2001 From: Dominik Weckbecker Date: Mon, 29 Sep 2025 12:14:52 +0200 Subject: [PATCH 158/340] make ambisonics-converter function arguments const --- lib_util/ambi_convert.c | 18 +++++++++--------- lib_util/ambi_convert.h | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib_util/ambi_convert.c b/lib_util/ambi_convert.c index 4c6a46d7c8..cd4722d994 100644 --- a/lib_util/ambi_convert.c +++ b/lib_util/ambi_convert.c @@ -126,9 +126,9 @@ static const int16_t REORDER_ACN_SID[AMBI_MAX_CHANNELS] = { 0, AMBI_CONVERT_ERROR convert_ambi_format( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_FMT in_format, /* i: input ambisonics format */ - AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_FMT in_format, /* i: input ambisonics format */ + const AMBI_FMT out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ) { @@ -307,9 +307,9 @@ AMBI_CONVERT_ERROR convert_ambi_format( AMBI_CONVERT_ERROR renormalize_channels( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + const AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ) { @@ -383,9 +383,9 @@ AMBI_CONVERT_ERROR renormalize_channels( AMBI_CONVERT_ERROR reorder_channels( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + const AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ) { diff --git a/lib_util/ambi_convert.h b/lib_util/ambi_convert.h index c680a7ff55..6d833481ad 100644 --- a/lib_util/ambi_convert.h +++ b/lib_util/ambi_convert.h @@ -73,27 +73,27 @@ typedef enum AMBI_CONVERT_ERROR convert_ambi_format( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_FMT in_format, /* i: input ambisonics format */ - AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_FMT in_format, /* i: input ambisonics format */ + const AMBI_FMT out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ); AMBI_CONVERT_ERROR renormalize_channels( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ + const AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ); AMBI_CONVERT_ERROR reorder_channels( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - int16_t order, /* i: ambisonics order */ - AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ - AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ + const AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ); #endif -- GitLab From 5f91263cad329aa4ae93eca4c2725fc8e180bcb6 Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Mon, 29 Sep 2025 12:33:18 +0200 Subject: [PATCH 159/340] Missing cast in ambi_converter.c caused a Compiler Warning on MSVC. --- apps/ambi_converter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index 928def1dc3..4341925886 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -140,7 +140,7 @@ int main( int argc, char *argv[] ) } } - if ( ( err = convert_ambi_format( in, out, order, in_format, out_format, numSamples / numChannels ) ) != 0 ) + if ( ( err = convert_ambi_format( in, out, order, in_format, out_format, (const uint16_t) ( numSamples / numChannels ) ) ) != 0 ) { printf( "Error converting the input signal!\n" ); return err; -- GitLab From c051fb9718adfc1e46cb412d9e9aee778de554ce Mon Sep 17 00:00:00 2001 From: lintervo Date: Mon, 29 Sep 2025 13:53:14 +0300 Subject: [PATCH 160/340] Clang formatting --- lib_dec/ivas_jbm_dec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 576967b25d..f8240a3328 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -2908,7 +2908,7 @@ void ivas_dec_prepare_renderer( } #ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES - if ( st_ivas->hDecoderConfig->Opt_tsm && st_ivas->ism_mode == ISM_MASA_MODE_DISC && st_ivas->renderer_type == RENDERER_DIRAC) + if ( st_ivas->hDecoderConfig->Opt_tsm && st_ivas->ism_mode == ISM_MASA_MODE_DISC && st_ivas->renderer_type == RENDERER_DIRAC ) { /* Gain MASA part, if edited */ if ( st_ivas->hMasaIsmData->masa_gain_is_edited ) -- GitLab From b622f8bac027d54c0525fd2bf8de03243d63bd56 Mon Sep 17 00:00:00 2001 From: Thomas Dettbarn Date: Mon, 29 Sep 2025 13:23:09 +0200 Subject: [PATCH 161/340] applied the clang patch --- apps/ambi_converter.c | 2 +- lib_util/ambi_convert.c | 18 +++++++++--------- lib_util/ambi_convert.h | 18 +++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/ambi_converter.c b/apps/ambi_converter.c index 4341925886..ea880d26a9 100644 --- a/apps/ambi_converter.c +++ b/apps/ambi_converter.c @@ -140,7 +140,7 @@ int main( int argc, char *argv[] ) } } - if ( ( err = convert_ambi_format( in, out, order, in_format, out_format, (const uint16_t) ( numSamples / numChannels ) ) ) != 0 ) + if ( ( err = convert_ambi_format( in, out, order, in_format, out_format, ( const uint16_t )( numSamples / numChannels ) ) ) != 0 ) { printf( "Error converting the input signal!\n" ); return err; diff --git a/lib_util/ambi_convert.c b/lib_util/ambi_convert.c index cd4722d994..e91dd0f989 100644 --- a/lib_util/ambi_convert.c +++ b/lib_util/ambi_convert.c @@ -126,9 +126,9 @@ static const int16_t REORDER_ACN_SID[AMBI_MAX_CHANNELS] = { 0, AMBI_CONVERT_ERROR convert_ambi_format( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - const int16_t order, /* i: ambisonics order */ - const AMBI_FMT in_format, /* i: input ambisonics format */ - const AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_FMT in_format, /* i: input ambisonics format */ + const AMBI_FMT out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ) { @@ -305,12 +305,12 @@ AMBI_CONVERT_ERROR convert_ambi_format( --------------------------------------------------------------------------*/ AMBI_CONVERT_ERROR renormalize_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ const int16_t order, /* i: ambisonics order */ const AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ const AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ - const int16_t frame_length /* i: input/output frame length */ + const int16_t frame_length /* i: input/output frame length */ ) { int16_t n_chan = ( order + 1 ) * ( order + 1 ); @@ -381,12 +381,12 @@ AMBI_CONVERT_ERROR renormalize_channels( --------------------------------------------------------------------------*/ AMBI_CONVERT_ERROR reorder_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ const int16_t order, /* i: ambisonics order */ const AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ const AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ - const int16_t frame_length /* i: input/output frame length */ + const int16_t frame_length /* i: input/output frame length */ ) { int16_t n_chan = ( order + 1 ) * ( order + 1 ); diff --git a/lib_util/ambi_convert.h b/lib_util/ambi_convert.h index 6d833481ad..1b2ffb8c60 100644 --- a/lib_util/ambi_convert.h +++ b/lib_util/ambi_convert.h @@ -73,27 +73,27 @@ typedef enum AMBI_CONVERT_ERROR convert_ambi_format( float *in[], /* i: input ambisonics channels */ float *out[], /* o: output ambisonics channels */ - const int16_t order, /* i: ambisonics order */ - const AMBI_FMT in_format, /* i: input ambisonics format */ - const AMBI_FMT out_format, /* i: output ambisonics format */ + const int16_t order, /* i: ambisonics order */ + const AMBI_FMT in_format, /* i: input ambisonics format */ + const AMBI_FMT out_format, /* i: output ambisonics format */ const int16_t frame_length /* i: input/output frame length */ ); AMBI_CONVERT_ERROR renormalize_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ const int16_t order, /* i: ambisonics order */ const AMBI_CHANNEL_NORM in_format, /* i: input ambisonics format */ const AMBI_CHANNEL_NORM out_format, /* i: output ambisonics format */ - const int16_t frame_length /* i: input/output frame length */ + const int16_t frame_length /* i: input/output frame length */ ); AMBI_CONVERT_ERROR reorder_channels( - float *in[], /* i: input ambisonics channels */ - float *out[], /* o: output ambisonics channels */ + float *in[], /* i: input ambisonics channels */ + float *out[], /* o: output ambisonics channels */ const int16_t order, /* i: ambisonics order */ const AMBI_CHANNEL_ORDER in_format, /* i: input ambisonics format */ const AMBI_CHANNEL_ORDER out_format, /* i: output ambisonics format */ - const int16_t frame_length /* i: input/output frame length */ + const int16_t frame_length /* i: input/output frame length */ ); #endif -- GitLab From f517c3e04d0a375949dc7f049b05c068dd0b3746 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Mon, 29 Sep 2025 13:52:48 +0200 Subject: [PATCH 162/340] add mechanism to enable a trap on underflows - tested only on gcc so far --- apps/decoder.c | 6 +++ apps/encoder.c | 7 ++- apps/renderer.c | 6 +++ lib_com/options.h | 1 + lib_debug/flp_debug.h | 102 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100755 lib_debug/flp_debug.h diff --git a/apps/decoder.c b/apps/decoder.c index 41089d057e..14deb7de54 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -56,6 +56,9 @@ #include "debug.h" #endif #include "wmc_auto.h" +#ifdef DENORMAL_TRAP +#include "flp_debug.h" +#endif #define WMC_TOOL_SKIP @@ -229,6 +232,9 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif +#ifdef DENORMAL_TRAP + enable_denorm_trap(); +#endif hHrtfBinary.hHrtfTD = NULL; /* just to avoid compilation warning */ hHrtfBinary.hHrtfStatistics = NULL; /* just to avoid compilation warning */ diff --git a/apps/encoder.c b/apps/encoder.c index 61bb8ebb3f..4f3238f2d9 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -15,7 +15,6 @@ the software. This notice grants no license of any kind, including but not limited to patent license, nor is any license granted by implication, estoppel or otherwise. - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making Contributors are required to enter into the IVAS codec Public Collaboration agreement before making contributions. @@ -43,6 +42,9 @@ #include "debug.h" #endif #include "wmc_auto.h" +#ifdef DENORMAL_TRAP +#include "flp_debug.h" +#endif #define WMC_TOOL_SKIP @@ -206,6 +208,9 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif +#ifdef DENORMAL_TRAP + enable_denorm_trap(); +#endif /*------------------------------------------------------------------------------------------* * Parse command-line arguments diff --git a/apps/renderer.c b/apps/renderer.c index cf5a7e33d2..a9b1626f95 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -53,6 +53,9 @@ #include "debug.h" #endif #include "wmc_auto.h" +#ifdef DENORMAL_TRAP +#include "flp_debug.h" +#endif #define WMC_TOOL_SKIP @@ -723,6 +726,9 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif +#ifdef DENORMAL_TRAP + enable_denorm_trap(); +#endif for ( i = 0; i < RENDERER_MAX_MASA_INPUTS; ++i ) { diff --git a/lib_com/options.h b/lib_com/options.h index 8b761e69b2..14937ce1e4 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -55,6 +55,7 @@ /*#define WMOPS_DETAIL*/ /* Output detailed complexity printout for every function. Increases runtime overhead */ /*#define WMOPS_WC_FRAME_ANALYSIS*/ /* Output detailed complexity analysis for the worst-case frame */ /*#define MEM_COUNT_DETAILS*/ /* Output detailed memory analysis for the worst-case frame (writes to the file "mem_analysis.csv") */ +/*#define DENORMAL_TRAP*/ /* Enable trap for denormals */ #ifdef DEBUGGING /*#define DBG_BITSTREAM_ANALYSIS*/ /* Write bitstream with annotations to a text file */ diff --git a/lib_debug/flp_debug.h b/lib_debug/flp_debug.h new file mode 100755 index 0000000000..d24d909272 --- /dev/null +++ b/lib_debug/flp_debug.h @@ -0,0 +1,102 @@ +/****************************************************************************************************** + + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. + +*******************************************************************************************************/ + +#include + +#if defined(_MSC_VER) + // MSVC, x87 + #include +#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) + // GCC/Clang, x86 SSE/AVX + #include +#endif + +/* + detect underflow execption, which results in a denormal; + this will not detect each and every denormal - otherwise, + all FLP values would have to be tested for denormals using + e.g. fpclassify()/fpstatus or bitmasks +*/ + + +static inline void enable_denorm_trap(void) { +#if defined(_MSC_VER) + // MSVC, x87 + unsigned int cw = _controlfp(0,0); + cw &= ~_EM_UNDERFLOW; + _controlfpEM_DENORMAL(cw, _MCW_EM); + +#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) + // GCC/Clang, x86 SSE/AVX + unsigned int mx = _mm_getcsr(); + mx &= ~_MM_MASK_UNDERFLOW; // unmaks underflows + _mm_setcsr(mx); + +#elif defined(__aarch64__) + // AArch64 (Apple Silicon) + uint64_t fpcr; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + // disable sits 24(FZ) & 25(DN) --> allow denormals to happen + fpcr &= ~((1ull<<24)|(1ull<<25)); + // set bit 3 (UFE) to unmask underflow exceptions + fpcr |= (1ull<<3); + __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); + +#else + fprintf(stderr, "enable_denorm_trap() not supported on platform!\n"); +#endif +} + +static inline void disable_denorm_trap(void) { +#if defined(_MSC_VER) + unsigned int cw = _controlfp(0,0); + cw |= _EM_UNDERFLOW; + _controlfp(cw, _MCW_EM); + +#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) + unsigned int mx = _mm_getcsr(); + mx |= _MM_MASK_UNDERFLOW; // mask underflows + _mm_setcsr(mx); + +#elif defined(__aarch64__) + // AArch64 (Apple Silicon) + uint64_t fpcr; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + // delete bit 3 (UFE), set bits 24/25 (FZ/DN) again + fpcr &= ~(1ull<<3); + fpcr |= (1ull<<24)|(1ull<<25); + __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); + +#else + fprintf(stderr, "disable_denorm_trap() not supported on platform!\n"); +#endif +} -- GitLab From b51ccce8fe1694fe0e7485f26db61803d2ebd074 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Mon, 29 Sep 2025 14:36:36 +0200 Subject: [PATCH 163/340] set correct UFE bit for AArch64 --- lib_debug/flp_debug.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib_debug/flp_debug.h b/lib_debug/flp_debug.h index d24d909272..c11e97fe72 100755 --- a/lib_debug/flp_debug.h +++ b/lib_debug/flp_debug.h @@ -67,8 +67,8 @@ static inline void enable_denorm_trap(void) { __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); // disable sits 24(FZ) & 25(DN) --> allow denormals to happen fpcr &= ~((1ull<<24)|(1ull<<25)); - // set bit 3 (UFE) to unmask underflow exceptions - fpcr |= (1ull<<3); + // set bit 11 (UFE) to unmask underflow exceptions + fpcr |= (1ull<<11); __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); #else @@ -91,8 +91,8 @@ static inline void disable_denorm_trap(void) { // AArch64 (Apple Silicon) uint64_t fpcr; __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); - // delete bit 3 (UFE), set bits 24/25 (FZ/DN) again - fpcr &= ~(1ull<<3); + // delete bit 11 (UFE), set bits 24/25 (FZ/DN) again + fpcr &= ~(1ull<<11); fpcr |= (1ull<<24)|(1ull<<25); __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); -- GitLab From 84654f5a1bc87af415974c875a1e7d302a6a9596 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Mon, 29 Sep 2025 14:43:29 +0200 Subject: [PATCH 164/340] try debugging failed commands --- .gitlab-ci.yml | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 77603623b4..1b67ef29db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2200,6 +2200,8 @@ coverage-test-on-main-scheduled: - if: $COVERAGE_TEST timeout: 12 hours script: + - set -e + - "trap 'echo \"Command failed at line $LINENO: $BASH_COMMAND\"' ERR" - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh @@ -2216,29 +2218,29 @@ coverage-test-on-main-scheduled: # codec smoke test - bash ci/smoke_test.sh coverage # default renderer framesize is 20ms - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true # run renderer smoke test - needed for REFVEC tests - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py # run renderer test only with REF creation stage - no CUT needed since executables are identical - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --create_ref + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --create_ref # run split rendering smoke test and REF creation - - python3 -m pytest -n auto tests/split_rendering/test_split_rendering.py - - python3 -m pytest -n auto tests/split_rendering/test_split_rendering.py --create_ref + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py --create_ref # VoIP BE test - - python3 -m pytest tests/test_be_for_jbm_neutral_dly_profile.py + - python3 -m pytest -q --tb=no tests/test_be_for_jbm_neutral_dly_profile.py # split rendering VoIP BE test - - python3 -m pytest -n auto tests/split_rendering/test_voip_be_splitrend_vs_binaural.py + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_voip_be_splitrend_vs_binaural.py # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted # - bash ci/run-first-frame-is-sid-test.sh # TODO consider HRTF binary loading test, or probably already covered by PRM files - # - python3 -m pytest tests/hrtf_binary_loading + # - python3 -m pytest -q --tb=no tests/hrtf_binary_loading # run BE vs EXT test - python3 tests/create_short_testvectors.py --cut_len 1.0 - - python3 -m pytest tests/test_be_for_ext_outputs.py + - python3 -m pytest -q --tb=no tests/test_be_for_ext_outputs.py # run LC3plus wrapper unit test - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job @@ -2250,22 +2252,22 @@ coverage-test-on-main-scheduled: - lcov -r coverage_stv.info "*lib_util*" -o coverage_stv.info # -- Add extended coverage of EVS test and long test vectors - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # codec smoke test - bash ci/smoke_test.sh coverage # default renderer framesize is 20ms - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true # run renderer smoke test - needed for REFVEC tests - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv # run renderer test only with REF creation stage - no CUT needed since executables are identical - - python3 -m pytest -q -n auto tests/renderer/test_renderer.py --use_ltv --create_ref + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv --create_ref # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here -- GitLab From ddb7b3e9c5af0a798e036d0392b6c182775cc53e Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Mon, 29 Sep 2025 14:52:07 +0200 Subject: [PATCH 165/340] fix copy & paste error --- lib_debug/flp_debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_debug/flp_debug.h b/lib_debug/flp_debug.h index c11e97fe72..87a208a69c 100755 --- a/lib_debug/flp_debug.h +++ b/lib_debug/flp_debug.h @@ -53,7 +53,7 @@ static inline void enable_denorm_trap(void) { // MSVC, x87 unsigned int cw = _controlfp(0,0); cw &= ~_EM_UNDERFLOW; - _controlfpEM_DENORMAL(cw, _MCW_EM); + _controlfp(cw, _MCW_EM); #elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) // GCC/Clang, x86 SSE/AVX -- GitLab From 4a36b9fcfb1789fd2dc8dd105237fbd0aff7d87f Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 30 Sep 2025 08:49:55 +0200 Subject: [PATCH 166/340] fix pytest call argument --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b67ef29db..178d02f7ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2259,8 +2259,8 @@ coverage-test-on-main-scheduled: - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm -dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true -- GitLab From eb28fe8b80b786274a899914e238a174724125b3 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 30 Sep 2025 08:50:54 +0200 Subject: [PATCH 167/340] add 2 debug printouts --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 178d02f7ac..297f40275d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2205,6 +2205,7 @@ coverage-test-on-main-scheduled: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh + - ls -altr scripts\testv # compile with coverage enabled - cmake -B cmake-build -G "Unix Makefiles" -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true -DGCOV=ON - cmake --build cmake-build -- -j @@ -2265,6 +2266,7 @@ coverage-test-on-main-scheduled: - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true # run renderer smoke test - needed for REFVEC tests + - ls -altr scripts\testv - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv # run renderer test only with REF creation stage - no CUT needed since executables are identical - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv --create_ref -- GitLab From 25138faadca1ab9d804fe15ad1d4665d9677fae1 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 30 Sep 2025 09:15:35 +0200 Subject: [PATCH 168/340] fix typo: backslash - back = slash --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 297f40275d..0086e802e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2205,7 +2205,7 @@ coverage-test-on-main-scheduled: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh - - ls -altr scripts\testv + - ls -altr scripts/testv # compile with coverage enabled - cmake -B cmake-build -G "Unix Makefiles" -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true -DGCOV=ON - cmake --build cmake-build -- -j @@ -2266,7 +2266,7 @@ coverage-test-on-main-scheduled: - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true # run renderer smoke test - needed for REFVEC tests - - ls -altr scripts\testv + - ls -altr scripts/testv - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv # run renderer test only with REF creation stage - no CUT needed since executables are identical - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv --create_ref -- GitLab From ad537ceb87fdec91b0664cbd4716986b3b17200b Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Tue, 30 Sep 2025 10:36:44 +0300 Subject: [PATCH 169/340] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Archit Tamarapu --- lib_rend/ivas_masa_merge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/ivas_masa_merge.c b/lib_rend/ivas_masa_merge.c index cd5af80ff1..997cdc84d1 100644 --- a/lib_rend/ivas_masa_merge.c +++ b/lib_rend/ivas_masa_merge.c @@ -345,7 +345,7 @@ ivas_error masaPrerendOpen( #ifdef NONBE_1344_REND_MASA_LOW_FS /* Determine the number of bands and band grouping */ hMasaPrerend->nbands = MASA_FREQUENCY_BANDS; - mvs2s( MASA_band_grouping_24, hMasaPrerend->band_grouping, 24 + 1 ); + mvs2s( MASA_band_grouping_24, hMasaPrerend->band_grouping, MASA_FREQUENCY_BANDS + 1 ); maxBin = (int16_t) ( input_Fs * INV_CLDFB_BANDWIDTH + 0.5f ); for ( i = 1; i < hMasaPrerend->nbands + 1; i++ ) -- GitLab From 57bb26d3f1b4dbc69cf6839e4af29e8a6bc2868f Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 30 Sep 2025 13:18:08 +0200 Subject: [PATCH 170/340] [fix] whitespace in metadata filename --- tests/renderer/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderer/constants.py b/tests/renderer/constants.py index 85affb1b3a..2d6a4daee4 100644 --- a/tests/renderer/constants.py +++ b/tests/renderer/constants.py @@ -332,7 +332,7 @@ FORMAT_TO_METADATA_FILES_LTV = { "ISM2MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), str(LTV_DIR.joinpath("ltvISM2.csv")), - str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.met ")), + str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.met")), ], "ISM3MASA1": [ str(LTV_DIR.joinpath("ltvISM1.csv")), -- GitLab From d68051e43a846906a2a646b1a145e51e7ffcd058 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 30 Sep 2025 13:30:08 +0200 Subject: [PATCH 171/340] split up coverage into two jobs + light formatting --- .gitlab-ci.yml | 253 +++++++++++++++++++++++++------------------------ 1 file changed, 128 insertions(+), 125 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0086e802e6..aed699e6d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,43 +11,43 @@ include: # This sets when pipelines are created. Jobs have more specific rules to restrict them. workflow: - name: '$IVAS_PIPELINE_NAME' + name: "$IVAS_PIPELINE_NAME" rules: # see https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push" when: never - if: $CI_PIPELINE_SOURCE == 'merge_request_event' variables: - IVAS_PIPELINE_NAME: 'MR pipeline: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' + IVAS_PIPELINE_NAME: "MR pipeline: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" - if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Pushes to main variables: - IVAS_PIPELINE_NAME: 'Push pipeline: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Push pipeline: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'schedule' && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Scheduled in main variables: - IVAS_PIPELINE_NAME: 'Scheduled pipeline: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Scheduled pipeline: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'default' # for testing variables: - IVAS_PIPELINE_NAME: 'Web run pipeline: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Web run pipeline: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-be-release' variables: - IVAS_PIPELINE_NAME: 'Test BE to release pipeline: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Test BE to release pipeline: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-long-self-test' variables: - IVAS_PIPELINE_NAME: 'Test long self-test against main pipeline: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Test long self-test against main pipeline: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'ivas-conformance' variables: - IVAS_PIPELINE_NAME: 'Draft IVAS Conformance test: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Draft IVAS Conformance test: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'trigger' - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'ivas-conformance-linux' variables: - IVAS_PIPELINE_NAME: 'Draft IVAS Conformance test -- Linux: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Draft IVAS Conformance test -- Linux: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'trigger' - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'check-clipping' variables: - IVAS_PIPELINE_NAME: 'Check core input clipping: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Check core input clipping: $CI_COMMIT_BRANCH" - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-branch-vs-input-passthrough' variables: - IVAS_PIPELINE_NAME: 'Pass-through comparison vs input: $CI_COMMIT_BRANCH' + IVAS_PIPELINE_NAME: "Pass-through comparison vs input: $CI_COMMIT_BRANCH" # --------------------------------------------------------------- # Generic script anchors @@ -71,8 +71,8 @@ workflow: - cp ivas-basop/IVAS_cod ./ - cp ivas-basop/IVAS_dec ./ -.merge-request-comparison-setup-codec: &merge-request-comparison-setup-codec - ### build test binaries, initial clean for paranoia reasons +.merge-request-comparison-setup-codec: + &merge-request-comparison-setup-codec ### build test binaries, initial clean for paranoia reasons - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/disable-debugging-macro.sh - make clean - mkdir build @@ -125,9 +125,9 @@ workflow: - Pop-Location .copy-ltv-files-to-testv-dir-win: ©-ltv-files-to-testv-dir-win - - cp $LTV_DIR_WIN\*.wav scripts\testv - - cp $LTV_DIR_WIN\*.met scripts\testv - - cp $LTV_DIR_WIN\*.csv scripts\testv + - cp $LTV_DIR_WIN\*.wav scripts\testv + - cp $LTV_DIR_WIN\*.met scripts\testv + - cp $LTV_DIR_WIN\*.csv scripts\testv .activate-WX-windows: &activate-WX-windows - (Get-Content -Path "CMakeLists.txt") -replace '# \(add_compile_options\("\/WX"\)\)', '$1' | Set-Content -Path "CMakeLists.txt" @@ -160,7 +160,6 @@ workflow: - if [ $exit_code20 -ne 0 ] || [ $exit_code10 -ne 0 ] || [ $exit_code5 -ne 0 ]; then exit 1; fi - # --------------------------------------------------------------- # .pre jobs for setting up things # --------------------------------------------------------------- @@ -359,7 +358,6 @@ build-codec-instrumented-linux: paths: - wmc_tool_output.txt - # make sure that the codec builds with msan, asan and usan build-codec-sanitizers-linux: extends: @@ -402,7 +400,13 @@ codec-smoke-test: tags: - ivas-linux-fast stage: test - needs: ["build-codec-linux-cmake", "build-codec-linux-make", "build-codec-instrumented-linux", "build-codec-sanitizers-linux"] + needs: + [ + "build-codec-linux-cmake", + "build-codec-linux-make", + "build-codec-instrumented-linux", + "build-codec-sanitizers-linux", + ] script: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh # LTV update needed as ltv ISM metadata files are used @@ -411,11 +415,11 @@ codec-smoke-test: # for MRs to basop-ci-branch, we want to test with the BASOP float reference build # per default, the smoke test builds the current repo again with WMOPS activated to catch unbalanced instrumentation macros - if [ "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" = "basop-ci-branch" ]; then - - python3 scripts/smoketest-basop-filter.py scripts/config/ivas_modes.json --inline - - *get-basop-float-reference - - bash ci/smoke_test.sh coverage + - python3 scripts/smoketest-basop-filter.py scripts/config/ivas_modes.json --inline + - *get-basop-float-reference + - bash ci/smoke_test.sh coverage - else - - bash ci/smoke_test.sh + - bash ci/smoke_test.sh - fi ### analyze for failures @@ -481,7 +485,13 @@ pytest-compare-20ms-and-5ms-rendering: - .test-job-linux - .rules-merge-request-to-main stage: test - needs: ["build-codec-linux-cmake", "build-codec-linux-make", "build-codec-instrumented-linux", "build-codec-sanitizers-linux"] + needs: + [ + "build-codec-linux-cmake", + "build-codec-linux-make", + "build-codec-instrumented-linux", + "build-codec-sanitizers-linux", + ] script: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/disable-limiter.sh @@ -583,7 +593,6 @@ renderer-asan: artifacts: expose_as: "renderer asan result" - # test renderer executable with cmake + msan renderer-msan: extends: @@ -595,7 +604,6 @@ renderer-msan: artifacts: expose_as: "renderer msan result" - # test renderer executable with cmake + usan renderer-usan: extends: @@ -607,7 +615,6 @@ renderer-usan: artifacts: expose_as: "renderer usan result" - # compare renderer bitexactness between target and source branch renderer-pytest-on-merge-request: extends: @@ -846,7 +853,6 @@ ivas-pytest-on-merge-request: junit: - report-junit.xml - # Check interop IVAS_cod_test -> IVAS_dec_ref ivas-interop-on-merge-request: extends: @@ -1093,7 +1099,6 @@ check-bitexactness-ext-and-transport-format: expose_as: "logs-ext-sanity-check" expire_in: "5 days" - # --------------------------------------------------------------- # Test jobs for main branch # --------------------------------------------------------------- @@ -1230,7 +1235,6 @@ codec-comparison-on-main-push: reports: junit: report-junit.xml - # --------------------------------------------------------------- # Manual jobs # --------------------------------------------------------------- @@ -1244,7 +1248,6 @@ test-be-to-release: rules: - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-be-release' script: - - echo "$CI_COMMIT_BRANCH" - MSBuild.exe -maxcpucount .\Workspace_msvc\Workspace_msvc.sln /property:Configuration=Debug /p:Platform=win32 /m | tee -variable winoutdata @@ -1261,7 +1264,6 @@ test-be-to-release: - logs/ expose_as: "test-be-to-release results" - ivas-conformance: tags: - ivas-windows @@ -1274,13 +1276,13 @@ ivas-conformance: exit_codes: - 123 script: - - *print-common-info-windows - - MSBuild.exe -maxcpucount .\Workspace_msvc\Workspace_msvc.sln /property:Configuration=Debug + - *print-common-info-windows + - MSBuild.exe -maxcpucount .\Workspace_msvc\Workspace_msvc.sln /property:Configuration=Debug - cp -force IVAS_cod.exe IVAS_cod_ref.exe - cp -force IVAS_dec.exe IVAS_dec_ref.exe - - cp -force IVAS_rend.exe IVAS_rend_ref.exe + - cp -force IVAS_rend.exe IVAS_rend_ref.exe - cp -force ISAR_post_rend.exe ISAR_post_rend_ref.exe - + # Reference creation - python scripts/prepare_combined_format_inputs.py - $TEST_SET = "tests/codec_be_on_mr_nonselection", "tests/renderer/test_renderer.py", "tests/split_rendering/test_split_rendering.py" @@ -1289,21 +1291,21 @@ ivas-conformance: # Output creation - python -m pytest $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html - python scripts/parse_commands.py report_cmd.html Readme_IVAS.txt - + # Copy input data and output ref data - if (Test-Path testvec) {rm -r -force testvec} - if (Test-Path TMP_DEC) {rm -r -force TMP_DEC} - - if (Test-Path TMP_ENC) {rm -r -force TMP_ENC} + - if (Test-Path TMP_ENC) {rm -r -force TMP_ENC} - if (Test-Path TMP_JBM) {rm -r -force TMP_JBM} - if (Test-Path TMP_REND) {rm -r -force TMP_REND} - - if (Test-Path TMP_ISAR_POST_REND) {rm -r -force TMP_ISAR_POST_REND} - - if (Test-Path TMP_DEC_ISAR) {rm -r -force TMP_DEC_ISAR} + - if (Test-Path TMP_ISAR_POST_REND) {rm -r -force TMP_ISAR_POST_REND} + - if (Test-Path TMP_DEC_ISAR) {rm -r -force TMP_DEC_ISAR} - mkdir testvec - mkdir testvec/binauralRenderer_interface - mkdir testvec/testv - mkdir testvec/testv/renderer - - mkdir testvec/testv/split_rendering - - mkdir testvec/bin + - mkdir testvec/testv/split_rendering + - mkdir testvec/bin - cp -force -ErrorAction Ignore scripts/testv/* testvec/testv - cp -r -force -ErrorAction Ignore scripts/ls_layouts testvec - cp -r -force -ErrorAction Ignore scripts/object_edit testvec @@ -1313,29 +1315,28 @@ ivas-conformance: - cp -r -force -ErrorAction Ignore tests/ref testvec/testv/ref - cp -r -force -ErrorAction Ignore tests/dut/* testvec/testv/ref - cp -r -force -ErrorAction Ignore tests/renderer/cut testvec/testv/renderer/ref - - cp -r -force -ErrorAction Ignore tests/split_rendering/cut testvec/testv/split_rendering/ref - - cp -r -force -ErrorAction Ignore tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - - cp -r -force -ErrorAction Ignore tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns - - # Remove redundant files + - cp -r -force -ErrorAction Ignore tests/split_rendering/cut testvec/testv/split_rendering/ref + - cp -r -force -ErrorAction Ignore tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs + - cp -r -force -ErrorAction Ignore tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns + + # Remove redundant files - python scripts/cleanup_26252.py # Copy test script files - cp -r -force -ErrorAction Ignore tests/conformance-test testvec/ - cp Readme_IVAS_dec.txt testvec - - cp Readme_IVAS_enc.txt testvec + - cp Readme_IVAS_enc.txt testvec - cp Readme_IVAS_rend.txt testvec - cp Readme_IVAS_JBM_dec.txt testvec - - cp Readme_IVAS_ISAR_dec.txt testvec - - cp Readme_IVAS_ISAR_post_rend.txt testvec + - cp Readme_IVAS_ISAR_dec.txt testvec + - cp Readme_IVAS_ISAR_post_rend.txt testvec - cp IVAS_cod.exe testvec/bin - cp IVAS_dec.exe testvec/bin - cp IVAS_rend.exe testvec/bin - - cp ISAR_post_rend.exe testvec/bin - - + - cp ISAR_post_rend.exe testvec/bin + # Test run generated scripts in testvec - - cd testvec + - cd testvec - python -m pytest conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html - mv report.html .. - mv report-junit.xml .. @@ -1348,12 +1349,12 @@ ivas-conformance: - report_cmd.html - report-junit.xml - report.html - - Readme_IVAS_dec.txt - - Readme_IVAS_enc.txt + - Readme_IVAS_dec.txt + - Readme_IVAS_enc.txt - Readme_IVAS_rend.txt - Readme_IVAS_JBM_dec.txt - - Readme_IVAS_ISAR_dec.txt - - Readme_IVAS_ISAR_post_rend.txt + - Readme_IVAS_ISAR_dec.txt + - Readme_IVAS_ISAR_post_rend.txt expose_as: "Draft IVAS conformance" reports: junit: report-junit.xml @@ -1375,7 +1376,7 @@ ivas-conformance-linux: - cp IVAS_dec IVAS_dec_ref - cp IVAS_rend IVAS_rend_ref - cp ISAR_post_rend ISAR_post_rend_ref - + # Reference creation - python3 scripts/prepare_combined_format_inputs.py - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" @@ -1384,15 +1385,15 @@ ivas-conformance-linux: # Output creation - python3 -m pytest $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt - + # Copy input data and output ref data - rm -rf testvec - mkdir testvec - mkdir testvec/binauralRenderer_interface - mkdir testvec/testv - mkdir testvec/testv/renderer - - mkdir testvec/testv/split_rendering - - mkdir testvec/bin + - mkdir testvec/testv/split_rendering + - mkdir testvec/bin - cp -r scripts/testv/* testvec/testv - cp -r scripts/ls_layouts testvec - cp -r scripts/object_edit testvec @@ -1402,34 +1403,34 @@ ivas-conformance-linux: - cp -r tests/ref testvec/testv/ref - cp -r tests/dut/* testvec/testv/ref - cp -r tests/renderer/cut testvec/testv/renderer/ref - - cp -r tests/split_rendering/cut testvec/testv/split_rendering/ref - - cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - - cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns - - # Remove redundant files + - cp -r tests/split_rendering/cut testvec/testv/split_rendering/ref + - cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs + - cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns + + # Remove redundant files - python3 scripts/cleanup_26252.py - + # Copy test script files - cp -r tests/conformance-test testvec/ - cp Readme_IVAS_dec.txt testvec - - cp Readme_IVAS_enc.txt testvec + - cp Readme_IVAS_enc.txt testvec - cp Readme_IVAS_rend.txt testvec - cp Readme_IVAS_JBM_dec.txt testvec - - cp Readme_IVAS_ISAR_dec.txt testvec - - cp Readme_IVAS_ISAR_post_rend.txt testvec - + - cp Readme_IVAS_ISAR_dec.txt testvec + - cp Readme_IVAS_ISAR_post_rend.txt testvec + # Create GCOV execs for coverage analysis - make clean - make GCOV=1 -j - + - cp IVAS_cod testvec/bin - cp IVAS_dec testvec/bin - cp IVAS_rend testvec/bin - - cp ISAR_post_rend testvec/bin - + - cp ISAR_post_rend testvec/bin + # Test run generated scripts in testvec - - cd testvec - - exit_code=0 + - cd testvec + - exit_code=0 - python3 -m pytest conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? - mv report.html .. - mv report-junit.xml .. @@ -1444,7 +1445,7 @@ ivas-conformance-linux: # Check for failures - if [ $exit_code -ne 0 ]; then echo "Test failures encountered"; exit $EXIT_CODE_FAIL; fi - + artifacts: name: "ivas-conformance-linux-$CI_COMMIT_SHORT_SHA" expire_in: 1 week @@ -1453,19 +1454,18 @@ ivas-conformance-linux: - report_cmd.html - report-junit.xml - report.html - - Readme_IVAS_dec.txt - - Readme_IVAS_enc.txt + - Readme_IVAS_dec.txt + - Readme_IVAS_enc.txt - Readme_IVAS_rend.txt - Readme_IVAS_JBM_dec.txt - - Readme_IVAS_ISAR_dec.txt - - Readme_IVAS_ISAR_post_rend.txt + - Readme_IVAS_ISAR_dec.txt + - Readme_IVAS_ISAR_post_rend.txt - coverage.info - coverage expose_as: "Draft IVAS conformance -- Linux" reports: junit: report-junit.xml - test-long-self-test: extends: - .job-linux @@ -1523,7 +1523,6 @@ test-long-self-test: - if [ $exit_code -ne 0 ]; then echo "Non-bitexact cases encountered!"; exit $EXIT_CODE_NON_BE; fi - exit 0 - artifacts: name: "test-long-self-test--sha-$CI_COMMIT_SHORT_SHA--results" when: always @@ -1972,7 +1971,6 @@ sanitizer-test-omasa-ism4: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - python3 ci/run_scheduled_sanitizer_test.py MASA-ISM4 $OUT_FORMATS_ALL --tests $SANITIZER_TESTS - ### --- sanitizer schedule C --- .sanitizer-test-schedule-C: @@ -2185,10 +2183,8 @@ sanitizer-test-osba-planar-hoa3-ism4: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - python3 ci/run_scheduled_sanitizer_test.py PlanarHOA3-ISM4 $OUT_FORMATS_ALL --tests $SANITIZER_TESTS - - # GCOV/LCOV coverage analysis of self_test suite -coverage-test-on-main-scheduled: +.coverage-test-template: extends: - .test-job-linux-needs-testv-dir - .rules-main-scheduled @@ -2198,10 +2194,10 @@ coverage-test-on-main-scheduled: rules: # only run in scheduled pipeline that passes this env vars - if: $COVERAGE_TEST - timeout: 12 hours - script: + timeout: 3 hours + before_script: - set -e - - "trap 'echo \"Command failed at line $LINENO: $BASH_COMMAND\"' ERR" + - 'trap ''echo "Command failed at line $LINENO: $BASH_COMMAND"'' ERR' - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh @@ -2214,7 +2210,25 @@ coverage-test-on-main-scheduled: - cp IVAS_dec IVAS_dec_ref - cp IVAS_rend IVAS_rend_ref - cp ISAR_post_rend ISAR_post_rend_ref + after_script: + - lcov -c -d cmake-build -o $COVERAGE_OUTPUT_FILE + # remove apps and lib_util files from coverage + - lcov -r coverage.info "*apps*" -o $COVERAGE_OUTPUT_FILE + - lcov -r coverage.info "*lib_util*" -o $COVERAGE_OUTPUT_FILE + - commit_sha=$(git rev-parse HEAD) + - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" + artifacts: + name: "main-coverage-sha-$CI_COMMIT_SHORT_SHA" + when: always + expire_in: 1 week +coverage-test-on-main-scheduled-stv: + extends: .coverage-test-template + variables: + COVERAGE_OUTPUT_FILE: "coverage_stv.info" + COVERAGE_OUTPUT_DIR: "coverage_stv" + COVERAGE_TITLE: "short test vectors" + script: # -- Run short test vector suite to check coverage of that first # codec smoke test - bash ci/smoke_test.sh coverage @@ -2245,17 +2259,20 @@ coverage-test-on-main-scheduled: # run LC3plus wrapper unit test - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job + artifacts: + paths: + - coverage_stv.info + - coverage_stv - # -- Extract coverage of short testvectors - - lcov -c -d cmake-build -o coverage_stv.info - # -- Remove apps and lib_util files from coverage - - lcov -r coverage_stv.info "*apps*" -o coverage_stv.info - - lcov -r coverage_stv.info "*lib_util*" -o coverage_stv.info - +coverage-test-on-main-scheduled-ltv: + extends: .coverage-test-template + variables: + COVERAGE_OUTPUT_FILE: "coverage_ltv.info" + COVERAGE_OUTPUT_DIR: "coverage_ltv" + COVERAGE_TITLE: "long test vectors" + script: # -- Add extended coverage of EVS test and long test vectors - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # codec smoke test - - bash ci/smoke_test.sh coverage # default renderer framesize is 20ms - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only @@ -2272,24 +2289,10 @@ coverage-test-on-main-scheduled: - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv --create_ref # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here - - # -- Extract accumulated coverage of STV + EVS + LTV - - lcov -c -d cmake-build -o coverage.info - # remove apps and lib_util files from coverage - - lcov -r coverage.info "*apps*" -o coverage.info - - lcov -r coverage.info "*lib_util*" -o coverage.info - - commit_sha=$(git rev-parse HEAD) - - genhtml coverage.info -o coverage -t "Coverage on main @ $commit_sha" - - genhtml coverage_stv.info -o coverage_stv -t "Coverage on main -- short test vectors @ $commit_sha" artifacts: - name: "main-coverage-sha-$CI_COMMIT_SHORT_SHA" - when: always - expire_in: 1 week paths: - - coverage.info - - coverage_stv.info - - coverage - - coverage_stv + - coverage_ltv.info + - coverage_ltv # --------------------------------------------------------------- # Complexity measurement jobs @@ -2309,20 +2312,20 @@ coverage-test-on-main-scheduled: # 1. check for public_dir being there as this might not be the case when artifact download failed # 2. check for public dir not being empty - handle job failures in prev job that happen after the dir is created. In that case, the empty dir is in the artifacts - if [ -d $public_dir ] && [ ! -z "$( ls -A $public_dir )" ]; then - - mv $public_dir/* wmops/ + - mv $public_dir/* wmops/ # check here if we have the split-by-levels files present - if not, fake them up with the existing global one # this is needed for the first run with split graphs on a branch where the global version did run previously # NOTE: checking only for level_1 file here as this should already be sufficient # NOTE2: also not chechking for RAM for same reason - - wmops_all_global="wmops/log_wmops_all.txt" - - ram_all_global="wmops/log_ram_all.txt" - - if [ -f "${wmops_all_global}" ] && [ ! -f "wmops/log_wmops_all_level_1.txt" ]; then - - declare -a suffixes=("level_1" "level_2" "level_3" "rate_sw") - - for suffix in "${suffixes[@]}"; do - - cp ${wmops_all_global} wmops/log_wmops_all_${suffix}.txt - - cp ${ram_all_global} wmops/log_ram_all_${suffix}.txt - - done - - fi + - wmops_all_global="wmops/log_wmops_all.txt" + - ram_all_global="wmops/log_ram_all.txt" + - if [ -f "${wmops_all_global}" ] && [ ! -f "wmops/log_wmops_all_level_1.txt" ]; then + - declare -a suffixes=("level_1" "level_2" "level_3" "rate_sw") + - for suffix in "${suffixes[@]}"; do + - cp ${wmops_all_global} wmops/log_wmops_all_${suffix}.txt + - cp ${ram_all_global} wmops/log_ram_all_${suffix}.txt + - done + - fi - fi - rm artifacts.zip -- GitLab From ec9ca95f5727dc05d6076224c1639b2230d47f7f Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 30 Sep 2025 13:39:10 +0200 Subject: [PATCH 172/340] revert some whitespace changes and fix before_script --- .gitlab-ci.yml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aed699e6d5..a483a7272a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,8 +71,8 @@ workflow: - cp ivas-basop/IVAS_cod ./ - cp ivas-basop/IVAS_dec ./ -.merge-request-comparison-setup-codec: - &merge-request-comparison-setup-codec ### build test binaries, initial clean for paranoia reasons +.merge-request-comparison-setup-codec: &merge-request-comparison-setup-codec + ### build test binaries, initial clean for paranoia reasons - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/disable-debugging-macro.sh - make clean - mkdir build @@ -415,11 +415,11 @@ codec-smoke-test: # for MRs to basop-ci-branch, we want to test with the BASOP float reference build # per default, the smoke test builds the current repo again with WMOPS activated to catch unbalanced instrumentation macros - if [ "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" = "basop-ci-branch" ]; then - - python3 scripts/smoketest-basop-filter.py scripts/config/ivas_modes.json --inline - - *get-basop-float-reference - - bash ci/smoke_test.sh coverage + - python3 scripts/smoketest-basop-filter.py scripts/config/ivas_modes.json --inline + - *get-basop-float-reference + - bash ci/smoke_test.sh coverage - else - - bash ci/smoke_test.sh + - bash ci/smoke_test.sh - fi ### analyze for failures @@ -2196,6 +2196,7 @@ sanitizer-test-osba-planar-hoa3-ism4: - if: $COVERAGE_TEST timeout: 3 hours before_script: + - !reference [.job-linux, before_script] - set -e - 'trap ''echo "Command failed at line $LINENO: $BASH_COMMAND"'' ERR' - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh @@ -2312,20 +2313,20 @@ coverage-test-on-main-scheduled-ltv: # 1. check for public_dir being there as this might not be the case when artifact download failed # 2. check for public dir not being empty - handle job failures in prev job that happen after the dir is created. In that case, the empty dir is in the artifacts - if [ -d $public_dir ] && [ ! -z "$( ls -A $public_dir )" ]; then - - mv $public_dir/* wmops/ + - mv $public_dir/* wmops/ # check here if we have the split-by-levels files present - if not, fake them up with the existing global one # this is needed for the first run with split graphs on a branch where the global version did run previously # NOTE: checking only for level_1 file here as this should already be sufficient # NOTE2: also not chechking for RAM for same reason - - wmops_all_global="wmops/log_wmops_all.txt" - - ram_all_global="wmops/log_ram_all.txt" - - if [ -f "${wmops_all_global}" ] && [ ! -f "wmops/log_wmops_all_level_1.txt" ]; then - - declare -a suffixes=("level_1" "level_2" "level_3" "rate_sw") - - for suffix in "${suffixes[@]}"; do - - cp ${wmops_all_global} wmops/log_wmops_all_${suffix}.txt - - cp ${ram_all_global} wmops/log_ram_all_${suffix}.txt - - done - - fi + - wmops_all_global="wmops/log_wmops_all.txt" + - ram_all_global="wmops/log_ram_all.txt" + - if [ -f "${wmops_all_global}" ] && [ ! -f "wmops/log_wmops_all_level_1.txt" ]; then + - declare -a suffixes=("level_1" "level_2" "level_3" "rate_sw") + - for suffix in "${suffixes[@]}"; do + - cp ${wmops_all_global} wmops/log_wmops_all_${suffix}.txt + - cp ${ram_all_global} wmops/log_ram_all_${suffix}.txt + - done + - fi - fi - rm artifacts.zip -- GitLab From ba81f075c9a85142c9ee9a21c5b9a0bb9cfcd3fd Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 30 Sep 2025 13:50:06 +0200 Subject: [PATCH 173/340] remove ls command from debugging --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a483a7272a..dd91e1d752 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2202,7 +2202,6 @@ sanitizer-test-osba-planar-hoa3-ism4: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/update-ltv-repo.sh - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/copy-ltv-files-to-testv-dir.sh - - ls -altr scripts/testv # compile with coverage enabled - cmake -B cmake-build -G "Unix Makefiles" -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true -DGCOV=ON - cmake --build cmake-build -- -j -- GitLab From fa69c6376b33643255a62dcba2fda1839bb630d7 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Tue, 30 Sep 2025 15:55:36 +0200 Subject: [PATCH 174/340] revised version of trap for floating-point exceptions --- apps/decoder.c | 6 +- apps/encoder.c | 6 +- apps/renderer.c | 6 +- lib_com/options.h | 2 +- lib_debug/flp_debug.h | 243 +++++++++++++++++++++++++++++++++++------- 5 files changed, 212 insertions(+), 51 deletions(-) mode change 100644 => 100755 lib_com/options.h mode change 100755 => 100644 lib_debug/flp_debug.h diff --git a/apps/decoder.c b/apps/decoder.c index 14deb7de54..74e78fa6e3 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -56,7 +56,7 @@ #include "debug.h" #endif #include "wmc_auto.h" -#ifdef DENORMAL_TRAP +#ifdef FLP_EXCEPTION_TRAP #include "flp_debug.h" #endif @@ -232,8 +232,8 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif -#ifdef DENORMAL_TRAP - enable_denorm_trap(); +#ifdef FLP_EXCEPTION_TRAP + enable_float_exception_trap( FLE_MASK_DENORM | FLE_MASK_UNDERFLOW ); #endif hHrtfBinary.hHrtfTD = NULL; /* just to avoid compilation warning */ diff --git a/apps/encoder.c b/apps/encoder.c index 4f3238f2d9..25550974df 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -42,7 +42,7 @@ #include "debug.h" #endif #include "wmc_auto.h" -#ifdef DENORMAL_TRAP +#ifdef FLP_EXCEPTION_TRAP #include "flp_debug.h" #endif @@ -208,8 +208,8 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif -#ifdef DENORMAL_TRAP - enable_denorm_trap(); +#ifdef FLP_EXECPTION_TRAP + enable_float_exception_trap( FLE_MASK_DENORM | FLE_MASK_UNDERFLOW ); #endif /*------------------------------------------------------------------------------------------* diff --git a/apps/renderer.c b/apps/renderer.c index a9b1626f95..fa12006ce3 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -53,7 +53,7 @@ #include "debug.h" #endif #include "wmc_auto.h" -#ifdef DENORMAL_TRAP +#ifdef FLP_EXCEPTION_TRAP #include "flp_debug.h" #endif @@ -726,8 +726,8 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif -#ifdef DENORMAL_TRAP - enable_denorm_trap(); +#ifdef FLP_EXCEPTION_TRAP + enable_float_exception_trap( FLE_MASK_DENORM | FLE_MASK_UNDERFLOW ); #endif for ( i = 0; i < RENDERER_MAX_MASA_INPUTS; ++i ) diff --git a/lib_com/options.h b/lib_com/options.h old mode 100644 new mode 100755 index 14937ce1e4..726a383feb --- a/lib_com/options.h +++ b/lib_com/options.h @@ -55,7 +55,7 @@ /*#define WMOPS_DETAIL*/ /* Output detailed complexity printout for every function. Increases runtime overhead */ /*#define WMOPS_WC_FRAME_ANALYSIS*/ /* Output detailed complexity analysis for the worst-case frame */ /*#define MEM_COUNT_DETAILS*/ /* Output detailed memory analysis for the worst-case frame (writes to the file "mem_analysis.csv") */ -/*#define DENORMAL_TRAP*/ /* Enable trap for denormals */ +/*#define FLP_EXCEPTION_TRAP*/ /* Enable trap for floating-point exceptions (e.g., denormals, underflow, overflow, ...) */ #ifdef DEBUGGING /*#define DBG_BITSTREAM_ANALYSIS*/ /* Write bitstream with annotations to a text file */ diff --git a/lib_debug/flp_debug.h b/lib_debug/flp_debug.h old mode 100755 new mode 100644 index 87a208a69c..d0fd894a2e --- a/lib_debug/flp_debug.h +++ b/lib_debug/flp_debug.h @@ -32,71 +32,232 @@ #include -#if defined(_MSC_VER) - // MSVC, x87 - #include -#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) - // GCC/Clang, x86 SSE/AVX - #include +#if defined( _MSC_VER ) +// MSVC, x87 +#include +#elif defined( __GNUC__ ) && ( defined( __SSE__ ) || defined( __SSE2__ ) || defined( __AVX__ ) ) +// GCC/Clang, x86 SSE/AVX +#include #endif -/* - detect underflow execption, which results in a denormal; - this will not detect each and every denormal - otherwise, - all FLP values would have to be tested for denormals using +#define FLE_MASK_INVALID 0x080 +#define FLE_MASK_DENORM 0x100 +#define FLE_MASK_DIV_ZERO 0x200 +#define FLE_MASK_OVERFLOW 0x400 +#define FLE_MASK_UNDERFLOW 0x800 + +/* + detect underflow execption, which results in a denormal; + this will not detect each and every denormal - otherwise, + all FLP values would have to be tested for denormals using e.g. fpclassify()/fpstatus or bitmasks */ -static inline void enable_denorm_trap(void) { -#if defined(_MSC_VER) +static inline void enable_float_exception_trap( uint32_t fle_mask ) +{ + +#if defined( _MSC_VER ) + // MSVC, x87 - unsigned int cw = _controlfp(0,0); - cw &= ~_EM_UNDERFLOW; - _controlfp(cw, _MCW_EM); + unsigned int cw = _controlfp( 0, 0 ); + + if ( fle_mask & FLE_MASK_INVALID ) + { + cw &= ~_EM_INVALID; + } + if ( fle_mask & FLE_MASK_DENORM ) + { + cw &= ~_EM_DENORMAL; + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + cw &= ~_EM_ZERODIVIDE; + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + cw &= ~_EM_OVERFLOW; + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + cw &= ~_EM_UNDERFLOW; + } + + _controlfp( cw, _MCW_EM ); + +#elif defined( __GNUC__ ) && ( defined( __SSE__ ) || defined( __SSE2__ ) || defined( __AVX__ ) ) -#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) // GCC/Clang, x86 SSE/AVX unsigned int mx = _mm_getcsr(); - mx &= ~_MM_MASK_UNDERFLOW; // unmaks underflows - _mm_setcsr(mx); -#elif defined(__aarch64__) - // AArch64 (Apple Silicon) + if ( fle_mask & FLE_MASK_INVALID ) + { + mx &= ~_MM_MASK_INVALID; + } + if ( fle_mask & FLE_MASK_DENORM ) + { + mx &= ~_MM_MASK_DENORM; + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + mx &= ~_MM_MASK_DIV_ZERO; + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + mx &= ~_MM_MASK_OVERFLOW; + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + mx &= ~_MM_MASK_UNDERFLOW; + } + + _mm_setcsr( mx ); + +#elif defined( __aarch64__ ) + + // AArch64 (e.g., Apple Silicon) uint64_t fpcr; - __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + __asm__ volatile( "mrs %0, fpcr" + : "=r"( fpcr ) ); + // disable sits 24(FZ) & 25(DN) --> allow denormals to happen - fpcr &= ~((1ull<<24)|(1ull<<25)); - // set bit 11 (UFE) to unmask underflow exceptions - fpcr |= (1ull<<11); - __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); + fpcr &= ~( ( 1ull << 24 ) | ( 1ull << 25 ) ); + + if ( fle_mask & FLE_MASK_INVALID ) + { + // set bit 8 (IOE) to unmask invalid operations exceptions + fpcr |= ( 1ull << 8 ); + } + if ( fle_mask & FLE_MASK_DENORM ) + { + // set bit 15 (IDE) to unmask input denormal exceptions + fpcr |= ( 1ull << 15 ); + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + // set bit 9 (DZE) to unmask div_zero exceptions + fpcr |= ( 1ull << 9 ); + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + // set bit 10 (OFE) to unmask overflow exceptions + fpcr |= ( 1ull << 10 ); + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + // set bit 11 (UFE) to unmask underflow exceptions + fpcr |= ( 1ull << 11 ); + } + + __asm__ volatile( "msr fpcr, %0" ::"r"( fpcr ) ); #else - fprintf(stderr, "enable_denorm_trap() not supported on platform!\n"); + fprintf( stderr, "enable_float_exception_trap() not supported on platform!\n" ); #endif } -static inline void disable_denorm_trap(void) { -#if defined(_MSC_VER) - unsigned int cw = _controlfp(0,0); - cw |= _EM_UNDERFLOW; - _controlfp(cw, _MCW_EM); +static inline void disable_float_exception_trap( uint32_t fle_mask ) +{ + +#if defined( _MSC_VER ) -#elif defined(__GNUC__) && (defined(__SSE__)||defined(__SSE2__)||defined(__AVX__)) + // MSVC, x87 + unsigned int cw = _controlfp( 0, 0 ); + + if ( fle_mask & FLE_MASK_INVALID ) + { + cw |= _EM_INVALID; + } + if ( fle_mask & FLE_MASK_DENORM ) + { + cw |= _EM_DENORMAL; + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + cw |= _EM_ZERODIVIDE; + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + cw |= _EM_OVERFLOW; + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + cw |= _EM_UNDERFLOW; + } + + _controlfp( cw, _MCW_EM ); + +#elif defined( __GNUC__ ) && ( defined( __SSE__ ) || defined( __SSE2__ ) || defined( __AVX__ ) ) + + // GCC/Clang, x86 SSE/AVX unsigned int mx = _mm_getcsr(); - mx |= _MM_MASK_UNDERFLOW; // mask underflows - _mm_setcsr(mx); -#elif defined(__aarch64__) + if ( fle_mask & FLE_MASK_INVALID ) + { + mx |= _MM_MASK_INVALID; + } + if ( fle_mask & FLE_MASK_DENORM ) + { + mx |= _MM_MASK_DENORM; + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + mx |= _MM_MASK_DIV_ZERO; + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + mx |= _MM_MASK_OVERFLOW; + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + mx |= _MM_MASK_UNDERFLOW; + } + + _mm_setcsr( mx ); + +#elif defined( __aarch64__ ) + // AArch64 (Apple Silicon) uint64_t fpcr; - __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); - // delete bit 11 (UFE), set bits 24/25 (FZ/DN) again - fpcr &= ~(1ull<<11); - fpcr |= (1ull<<24)|(1ull<<25); - __asm__ volatile("msr fpcr, %0" :: "r"(fpcr)); + __asm__ volatile( "mrs %0, fpcr" + : "=r"( fpcr ) ); + + if ( fle_mask & FLE_MASK_INVALID ) + { + // unset bit 8 (IOE) to mask invalid operations exceptions + fpcr &= ~( 1ull << 8 ); + } + if ( fle_mask & FLE_MASK_DENORM ) + { + // unset bit 15 (IDE) to mask input denormal exceptions + fpcr &= ~( 1ull << 15 ); + } + if ( fle_mask & FLE_MASK_DIV_ZERO ) + { + // unset bit 9 (DZE) to mask div_zero exceptions + fpcr &= ~( 1ull << 9 ); + } + if ( fle_mask & FLE_MASK_OVERFLOW ) + { + // unset bit 10 (OFE) to mask overflow exceptions + fpcr &= ~( 1ull << 10 ); + } + if ( fle_mask & FLE_MASK_UNDERFLOW ) + { + // unset bit 11 (UFE) to mask underflow exceptions + fpcr &= ~( 1ull << 11 ); + } + + + // set bits 24/25 (FZ/DN) again + fpcr |= ( 1ull << 24 ) | ( 1ull << 25 ); + fprintf( stderr, "float_exception_trap(): Setting bits 24/25 (FZ/DN) again\n" ); + + __asm__ volatile( "msr fpcr, %0" ::"r"( fpcr ) ); #else - fprintf(stderr, "disable_denorm_trap() not supported on platform!\n"); + + fprintf( stderr, "float_exception_trap() not supported on platform!\n" ); + #endif } -- GitLab From 403ba96b3daecff4542940416f5f9f6821f04f99 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Tue, 30 Sep 2025 16:01:46 +0200 Subject: [PATCH 175/340] fix typo --- apps/encoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/encoder.c b/apps/encoder.c index 25550974df..4d9626edc3 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -208,7 +208,7 @@ int main( reset_wmops(); reset_mem( USE_BYTES ); #endif -#ifdef FLP_EXECPTION_TRAP +#ifdef FLP_EXCEPTION_TRAP enable_float_exception_trap( FLE_MASK_DENORM | FLE_MASK_UNDERFLOW ); #endif -- GitLab From 0d3c9d1beaa00a44c8e74e07a93b18088bc3d76b Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 30 Sep 2025 16:53:30 +0200 Subject: [PATCH 176/340] [fix] remove redundant second call to renderer LTV test --- .gitlab-ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd91e1d752..21997fe6f8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2213,8 +2213,8 @@ sanitizer-test-osba-planar-hoa3-ism4: after_script: - lcov -c -d cmake-build -o $COVERAGE_OUTPUT_FILE # remove apps and lib_util files from coverage - - lcov -r coverage.info "*apps*" -o $COVERAGE_OUTPUT_FILE - - lcov -r coverage.info "*lib_util*" -o $COVERAGE_OUTPUT_FILE + - lcov -r $COVERAGE_OUTPUT_FILE "*apps*" -o $COVERAGE_OUTPUT_FILE + - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - commit_sha=$(git rev-parse HEAD) - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" artifacts: @@ -2284,9 +2284,8 @@ coverage-test-on-main-scheduled-ltv: - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true # run renderer smoke test - needed for REFVEC tests - ls -altr scripts/testv + # LTV smoketest is the same as REF creation stage for renderer - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv - # run renderer test only with REF creation stage - no CUT needed since executables are identical - - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv --create_ref # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here artifacts: -- GitLab From b7239dbf485309c136b02730ff08f0a566b88983 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Wed, 1 Oct 2025 08:43:13 +0300 Subject: [PATCH 177/340] Revert "temporarily skip MASA prerendering cases at non-48kHz SR until #1334 is resolved" This reverts commit e151ef1b77b7f0e7de20a685986a28443a7e0d5f. --- tests/renderer/test_renderer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/renderer/test_renderer.py b/tests/renderer/test_renderer.py index 8b68e47541..392ee07c2a 100644 --- a/tests/renderer/test_renderer.py +++ b/tests/renderer/test_renderer.py @@ -677,8 +677,6 @@ def test_masa_prerend_scenes( get_odg_bin, split_comparison, ): - if fs != "48kHz": - pytest.skip("MASA Prerendering at 16 and 32 kHz WIP") run_renderer( record_property, props_to_record, -- GitLab From 06e267f96e4c030d68ca586cdbca7a229eac5929 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 1 Oct 2025 09:25:29 +0200 Subject: [PATCH 178/340] tag renderer sanitizer tests for fast runners --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7a02bd69e5..543332ac81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -549,6 +549,8 @@ renderer-smoke-test: - .test-job-linux - .rules-merge-request-to-main needs: ["build-codec-linux-cmake"] + tags: + - ivas-linux-fast stage: test timeout: "90 minutes" artifacts: -- GitLab From 7f775d5aa1d9c706c3dcad7e13a2bcb4728804a3 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 1 Oct 2025 11:47:36 +0200 Subject: [PATCH 179/340] [fix] update artifact name and replace hardcoded paths in .info files with relative paths --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 21997fe6f8..ae97cf7153 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2216,9 +2216,11 @@ sanitizer-test-osba-planar-hoa3-ism4: - lcov -r $COVERAGE_OUTPUT_FILE "*apps*" -o $COVERAGE_OUTPUT_FILE - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - commit_sha=$(git rev-parse HEAD) + # replace hardcoded path with relative path to allow easy merging of .info files + - sed -i "s|$(pwd)|..|g" $COVERAGE_OUTPUT_FILE - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" artifacts: - name: "main-coverage-sha-$CI_COMMIT_SHORT_SHA" + name: "main-$COVERAGE_OUTPUT_DIR-sha-$CI_COMMIT_SHORT_SHA" when: always expire_in: 1 week -- GitLab From 52854f012bf3c5504136aaacee8a8a2c9a6e4755 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 1 Oct 2025 11:55:54 +0200 Subject: [PATCH 180/340] try to merge coverage reports --- .gitlab-ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ae97cf7153..985532c3a9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2224,6 +2224,36 @@ sanitizer-test-osba-planar-hoa3-ism4: when: always expire_in: 1 week +coverage-merge: + extends: .rules-main-scheduled + tags: + - ivas-linux-fast + stage: test + rules: + # only run in scheduled pipeline that passes this env vars + - if: $COVERAGE_TEST + needs: + - job: coverage-test-on-main-scheduled-stv + artifacts: true + - job: coverage-test-on-main-scheduled-ltv + artifacts: true + - job: ivas-conformance-linux + artifacts: true + # before_script: + # - !reference [.job-linux, before_script] # check if this is really needed + script: + - echo "Merging coverage reports..." + - lcov -a *.info -o coverage_merged.info + - commit_sha=$(git rev-parse HEAD) + - genhtml coverage_merged.info -o coverage_merged -t "Merged Coverage Report @ $commit_sha" + artifacts: + name: "merged-coverage-sha-$CI_COMMIT_SHORT_SHA" + when: always + expire_in: 1 week + paths: + - coverage_merged.info + - coverage_merged + coverage-test-on-main-scheduled-stv: extends: .coverage-test-template variables: -- GitLab From 96a93fcf342e15c6e7c8d0268552b0924b6ee359 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 1 Oct 2025 12:39:35 +0200 Subject: [PATCH 181/340] switch renderer sanity tests to run smoke test only --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 543332ac81..8c1654e120 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -581,7 +581,7 @@ renderer-asan: variables: SANITIZER_BUILD_STRING: "asan" script: - - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout + - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: expose_as: "renderer asan result" @@ -593,7 +593,7 @@ renderer-msan: variables: SANITIZER_BUILD_STRING: "msan" script: - - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout + - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: expose_as: "renderer msan result" @@ -605,7 +605,7 @@ renderer-usan: variables: SANITIZER_BUILD_STRING: "usan" script: - - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1 python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout + - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1 python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: expose_as: "renderer usan result" -- GitLab From 2d3df7ed6461e42c8e1b8e88180481ae3ed075e9 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 1 Oct 2025 12:57:46 +0200 Subject: [PATCH 182/340] [fix] substitute path also for ivas-conformance-linux and replace glob with explicit file list (lcov doesn't like multiple arguments for -a) --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 985532c3a9..9c80573454 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1441,6 +1441,8 @@ ivas-conformance-linux: - lcov -r coverage.info "*apps*" -o coverage.info - lcov -r coverage.info "*lib_util*" -o coverage.info - commit_sha=$(git rev-parse HEAD) + # replace hardcoded path with relative path to allow easy merging of .info files + - sed -i "s|$(pwd)|..|g" coverage.info - genhtml coverage.info -o coverage -t "Coverage on main @ $commit_sha" # Check for failures @@ -2243,7 +2245,7 @@ coverage-merge: # - !reference [.job-linux, before_script] # check if this is really needed script: - echo "Merging coverage reports..." - - lcov -a *.info -o coverage_merged.info + - lcov -a coverage.info -a coverage_stv.info -a coverage_ltv.info -o coverage_merged.info - commit_sha=$(git rev-parse HEAD) - genhtml coverage_merged.info -o coverage_merged -t "Merged Coverage Report @ $commit_sha" artifacts: -- GitLab From ae14c5361dd26fb59eef5df32f0881bc9c45067e Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 1 Oct 2025 12:58:55 +0200 Subject: [PATCH 183/340] remove renaming from renderer-Xsan tests _ref suffix not needed anymore after switching to smoke test cases --- .gitlab-ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8c1654e120..dcfe8300ad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -566,12 +566,6 @@ renderer-smoke-test: before_script: - cmake -B cmake-build -G "Unix Makefiles" -DCLANG=$SANITIZER_BUILD_STRING -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true - cmake --build cmake-build -- -j - # rename files to fit naming convention - # en- and decoder needed because of split rendering testcases - - mv IVAS_cod IVAS_cod_ref - - mv IVAS_dec IVAS_dec_ref - - mv IVAS_rend IVAS_rend_ref - - mv ISAR_post_rend ISAR_post_rend_ref - testcase_timeout=180 # test renderer executable with cmake + asan -- GitLab From 89596ea7a938a6b232c9cfa9f16a51c3936ee07d Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 1 Oct 2025 15:08:27 +0200 Subject: [PATCH 184/340] cleanup coverage merge job --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c80573454..6e68613b54 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2241,8 +2241,6 @@ coverage-merge: artifacts: true - job: ivas-conformance-linux artifacts: true - # before_script: - # - !reference [.job-linux, before_script] # check if this is really needed script: - echo "Merging coverage reports..." - lcov -a coverage.info -a coverage_stv.info -a coverage_ltv.info -o coverage_merged.info -- GitLab From 9635c6b2af2448fa0e57ee67e66c4ded6f408db2 Mon Sep 17 00:00:00 2001 From: vaclav Date: Thu, 2 Oct 2025 08:25:55 +0200 Subject: [PATCH 185/340] introduce input_frame to init_encoder() --- lib_enc/init_enc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib_enc/init_enc.c b/lib_enc/init_enc.c index e920f21bc9..1da00f6eca 100755 --- a/lib_enc/init_enc.c +++ b/lib_enc/init_enc.c @@ -64,11 +64,11 @@ ivas_error init_encoder( const int32_t element_brate /* i : element bitrate */ ) { - int16_t i; + int16_t i, input_frame; ivas_error error; int32_t igf_brate; - error = IVAS_ERR_OK; + input_frame = (int16_t) ( st->input_Fs / FRAMES_PER_SEC ); /*-----------------------------------------------------------------* * General parameters @@ -303,11 +303,11 @@ ivas_error init_encoder( st->old_input_signal = st->input_buff; if ( st->element_mode == EVS_MONO ) { - st->input = st->input_buff + st->input_Fs / FRAMES_PER_SEC + NS2SA( st->input_Fs, DELAY_FIR_RESAMPL_NS ); + st->input = st->input_buff + input_frame + NS2SA( st->input_Fs, DELAY_FIR_RESAMPL_NS ); } else { - st->input = st->input_buff + st->input_Fs / FRAMES_PER_SEC; + st->input = st->input_buff + input_frame; } } else @@ -475,7 +475,7 @@ ivas_error init_encoder( st->core = -1; st->rf_mode = 0; - return error; + return IVAS_ERR_OK; } /*-----------------------------------------------------------------* @@ -862,11 +862,11 @@ ivas_error init_encoder( if ( st->element_mode > EVS_MONO ) { - InitTransientDetection( (int16_t) ( st->input_Fs / FRAMES_PER_SEC ), 0, st->hTranDet, 1 ); + InitTransientDetection( input_frame, 0, st->hTranDet, 1 ); } else { - InitTransientDetection( (int16_t) ( st->input_Fs / FRAMES_PER_SEC ), NS2SA( st->input_Fs, DELAY_FIR_RESAMPL_NS ), st->hTranDet, 0 ); + InitTransientDetection( input_frame, NS2SA( st->input_Fs, DELAY_FIR_RESAMPL_NS ), st->hTranDet, 0 ); } /*-----------------------------------------------------------------* @@ -878,7 +878,7 @@ ivas_error init_encoder( st->bits_frame_channel = 0; st->side_bits_frame_channel = 0; - return error; + return IVAS_ERR_OK; } -- GitLab From ca00d3da682646c7854a85ba0018f9c0cf1e67ea Mon Sep 17 00:00:00 2001 From: vaclav Date: Thu, 2 Oct 2025 08:44:31 +0200 Subject: [PATCH 186/340] align init_encoder() interface --- lib_com/prot.h | 14 ++++++-------- lib_enc/init_enc.c | 16 +++++++--------- lib_enc/ivas_cpe_enc.c | 2 +- lib_enc/ivas_sce_enc.c | 2 +- lib_enc/ivas_spar_encoder.c | 2 +- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib_com/prot.h b/lib_com/prot.h index efb77f0a3f..d4f41df63d 100644 --- a/lib_com/prot.h +++ b/lib_com/prot.h @@ -2247,14 +2247,12 @@ void read_next_force( #endif ivas_error init_encoder( - Encoder_State *st, /* i/o: state structure */ - Encoder_Struct *st_ivas, /* i/o: encoder state structure */ - const int16_t idchan, /* i : channel ID */ - const int16_t var_SID_rate_flag, /* i : flag for variable SID update rate */ - const int16_t interval_SID, /* i : interval for SID update */ - const int16_t vad_only_flag, /* i : flag to indicate front-VAD structure */ - const ISM_MODE ism_mode, /* i : ISM mode */ - const int32_t element_brate /* element bitrate */ + Encoder_State *st, /* i/o: state structure */ + Encoder_Struct *st_ivas, /* i/o: encoder state structure */ + const int16_t idchan, /* i : channel ID */ + const int16_t vad_only_flag, /* i : flag to indicate front-VAD structure */ + const ISM_MODE ism_mode, /* i : ISM mode */ + const int32_t element_brate /* element bitrate */ ); void LPDmem_enc_init( diff --git a/lib_enc/init_enc.c b/lib_enc/init_enc.c index 1da00f6eca..f5aab5580b 100755 --- a/lib_enc/init_enc.c +++ b/lib_enc/init_enc.c @@ -54,14 +54,12 @@ *-----------------------------------------------------------------------*/ ivas_error init_encoder( - Encoder_State *st, /* i/o: state structure */ - Encoder_Struct *st_ivas, /* i/o: encoder state structure */ - const int16_t idchan, /* i : channel ID */ - const int16_t var_SID_rate_flag, /* i : flag for variable SID update rate */ - const int16_t interval_SID, /* i : interval for SID update */ - const int16_t vad_only_flag, /* i : flag to indicate front-VAD structure */ - const ISM_MODE ism_mode, /* i : ISM mode */ - const int32_t element_brate /* i : element bitrate */ + Encoder_State *st, /* i/o: state structure */ + Encoder_Struct *st_ivas, /* i/o: encoder state structure */ + const int16_t idchan, /* i : channel ID */ + const int16_t vad_only_flag, /* i : flag to indicate front-VAD structure */ + const ISM_MODE ism_mode, /* i : ISM mode */ + const int32_t element_brate /* i : element bitrate */ ) { int16_t i, input_frame; @@ -438,7 +436,7 @@ ivas_error init_encoder( { return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for DTX variables\n" ) ); } - dtx_enc_init( st, var_SID_rate_flag, interval_SID ); + dtx_enc_init( st, st_ivas->hEncoderConfig->var_SID_rate_flag, st_ivas->hEncoderConfig->interval_SID ); } else { diff --git a/lib_enc/ivas_cpe_enc.c b/lib_enc/ivas_cpe_enc.c index 7c5b66cb8c..2c954fde2e 100644 --- a/lib_enc/ivas_cpe_enc.c +++ b/lib_enc/ivas_cpe_enc.c @@ -925,7 +925,7 @@ ivas_error create_cpe_enc( st->total_brate = hCPE->element_brate / ( st_ivas->nCPE > 1 ? 1 : CPE_CHANNELS ); /* dummy initialization for getting right pointers initialization of input buffers in init_coder_ace_plus() */ st->mct_chan_mode = MCT_CHAN_MODE_REGULAR; - if ( ( error = init_encoder( st, st_ivas, n, hEncoderConfig->var_SID_rate_flag, hEncoderConfig->interval_SID, 0, ISM_MODE_NONE, hCPE->element_brate ) ) != IVAS_ERR_OK ) + if ( ( error = init_encoder( st, st_ivas, n, 0, ISM_MODE_NONE, hCPE->element_brate ) ) != IVAS_ERR_OK ) { return error; } diff --git a/lib_enc/ivas_sce_enc.c b/lib_enc/ivas_sce_enc.c index 913fe90754..5d1f56ab30 100644 --- a/lib_enc/ivas_sce_enc.c +++ b/lib_enc/ivas_sce_enc.c @@ -367,7 +367,7 @@ ivas_error create_sce_enc( st->total_brate = hSCE->element_brate; /* dummy initialization for getting right pointers initialization of input buffers in init_coder_ace_plus() */ st->mct_chan_mode = MCT_CHAN_MODE_REGULAR; - if ( ( error = init_encoder( st, st_ivas, 0, st_ivas->hEncoderConfig->var_SID_rate_flag, st_ivas->hEncoderConfig->interval_SID, 0, st_ivas->ism_mode, hSCE->element_brate ) ) != IVAS_ERR_OK ) + if ( ( error = init_encoder( st, st_ivas, 0, 0, st_ivas->ism_mode, hSCE->element_brate ) ) != IVAS_ERR_OK ) { return error; } diff --git a/lib_enc/ivas_spar_encoder.c b/lib_enc/ivas_spar_encoder.c index 1d0459a333..79b00113fc 100644 --- a/lib_enc/ivas_spar_encoder.c +++ b/lib_enc/ivas_spar_encoder.c @@ -223,7 +223,7 @@ ivas_error ivas_spar_enc_open( hSpar->hCoreCoderVAD->total_brate = hEncoderConfig->ivas_total_brate; /* dummy initialization for getting right pointers initialization of input buffers in init_coder_ace_plus() */ hSpar->hCoreCoderVAD->mct_chan_mode = MCT_CHAN_MODE_IGNORE; - if ( ( error = init_encoder( hSpar->hCoreCoderVAD, st_ivas, 0, hEncoderConfig->var_SID_rate_flag, hEncoderConfig->interval_SID, 1, st_ivas->ism_mode, hSpar->hCoreCoderVAD->total_brate ) ) != IVAS_ERR_OK ) + if ( ( error = init_encoder( hSpar->hCoreCoderVAD, st_ivas, 0, 1, st_ivas->ism_mode, hSpar->hCoreCoderVAD->total_brate ) ) != IVAS_ERR_OK ) { return error; } -- GitLab From c6d94f6b59d9820ab2a40e750a68b04a8ee6f926 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Thu, 2 Oct 2025 11:23:26 +0200 Subject: [PATCH 187/340] remove evs-be-linux and codec-comparison jobs from main push pl --- .gitlab-ci.yml | 214 +++++++++++++++++++++++++------------------------ 1 file changed, 108 insertions(+), 106 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcfe8300ad..0473b241b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1118,113 +1118,115 @@ be-2-evs-windows: - cd evs_be_win_test - python ../ci/run_evs_be_win_test.py +# TODO: turn into manual job if needed # check bitexactness to EVS -be-2-evs-linux: - extends: - - .test-job-linux - - .rules-main-push - tags: - - be-2-evs-temp - stage: test - needs: ["build-codec-linux-cmake"] - timeout: "20 minutes" # To be revisited - script: - - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - - - mkdir build - - cd build - - cmake .. - - make -j - - cd .. - - # copy over to never change the testvector dir - - cp -r $EVS_BE_TEST_DIR ./evs_be_test - - cp build/IVAS_cod ./evs_be_test/bin/EVS_cod - - cp build/IVAS_dec ./evs_be_test/bin/EVS_dec - - - cd evs_be_test - - python3 ../ci/run_evs_be_test.py - -codec-comparison-on-main-push: - extends: - - .test-job-linux-needs-testv-dir - - .rules-main-push - stage: compare - needs: ["build-codec-linux-cmake"] - timeout: "30 minutes" # To be revisited - script: - - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - - latest_commit=$(git rev-parse HEAD) # Latest commit - - previous_merge_commit=$(git --no-pager log --merges HEAD~1 -n 1 --pretty=format:%H) - - echo "Comparing changes from $previous_merge_commit to $latest_commit" - - git --no-pager diff --stat $previous_merge_commit..$latest_commit - - # Rest is more or less placeholder adapted from MR self test. This should be replaced with more complex tests. - - ### build test binaries, initial clean for paranoia reasons - - make clean - - mkdir build - - cd build - - cmake .. - - make -j - - mv IVAS_cod ../IVAS_cod_test - - mv IVAS_dec ../IVAS_dec_test - - cd .. - - rm -rf build/* - - ### compare to the previous merge commit in the main branch - - git fetch origin main - - git checkout $previous_merge_commit - - echo "Building reference codec at commit $previous_merge_commit" - - ### build reference binaries - - cd build - - cmake .. - - make -j - - mv IVAS_cod ../IVAS_cod_ref - - mv IVAS_dec ../IVAS_dec_ref - - cd .. - - # helper variable - "|| true" to prevent failures from grep not finding anything - # write to temporary file as workaround for failures observed with piping echo - - echo $CI_COMMIT_MESSAGE > tmp.txt - - non_be_flag=$(grep -c --ignore-case "\[non[ -]*be\]" tmp.txt) || true - - ref_using_main=$(grep -c --ignore-case "\[ref[ -]*using[ -]*main\]" tmp.txt) || true - - ### re-checkout the latest commit in the main branch, if ref_using_main is not set - - if [ $ref_using_main == 0 ]; then git checkout $latest_commit;fi - - ### prepare pytest - # rename test binaries back - - mv IVAS_cod_test IVAS_cod - - mv IVAS_dec_test IVAS_dec - # create references - - testcase_timeout=60 - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --update_ref 1 --testcase_timeout=$testcase_timeout - - ### re-checkout the latest commit here, if ref_using_main is set - - if [ $ref_using_main -eq 1 ]; then git checkout $latest_commit;fi - - ### run pytest - - exit_code=0 - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$? - - if [ $exit_code -ne 0 ] && [ $non_be_flag == 0 ]; then echo "pytest run had failures and non-BE flag not present"; exit $EXIT_CODE_FAIL; fi - - zero_errors=$(cat report-junit.xml | grep -c 'errors="0"') || true - - if [ $exit_code -ne 0 ] && [ $zero_errors == 1 ]; then echo "pytest run had failures, but no errors and non-BE flag present"; exit $EXIT_CODE_NON_BE; fi - - if [ $exit_code -ne 0 ]; then echo "pytest run had errors"; exit $EXIT_CODE_FAIL; fi; - allow_failure: - exit_codes: - - 123 - artifacts: - name: "main-push--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" - expire_in: 1 week - when: always - paths: - - report-junit.xml - - report.html - expose_as: "Results of comparison to previous merge commit" - reports: - junit: report-junit.xml +# be-2-evs-linux: +# extends: +# - .test-job-linux +# - .rules-main-push +# tags: +# - be-2-evs-temp +# stage: test +# needs: ["build-codec-linux-cmake"] +# timeout: "20 minutes" # To be revisited +# script: +# - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh +# +# - mkdir build +# - cd build +# - cmake .. +# - make -j +# - cd .. +# +# # copy over to never change the testvector dir +# - cp -r $EVS_BE_TEST_DIR ./evs_be_test +# - cp build/IVAS_cod ./evs_be_test/bin/EVS_cod +# - cp build/IVAS_dec ./evs_be_test/bin/EVS_dec +# +# - cd evs_be_test +# - python3 ../ci/run_evs_be_test.py + +# TODO: do we still need this? +# codec-comparison-on-main-push: +# extends: +# - .test-job-linux-needs-testv-dir +# - .rules-main-push +# stage: compare +# needs: ["build-codec-linux-cmake"] +# timeout: "30 minutes" # To be revisited +# script: +# - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh +# - latest_commit=$(git rev-parse HEAD) # Latest commit +# - previous_merge_commit=$(git --no-pager log --merges HEAD~1 -n 1 --pretty=format:%H) +# - echo "Comparing changes from $previous_merge_commit to $latest_commit" +# - git --no-pager diff --stat $previous_merge_commit..$latest_commit +# +# # Rest is more or less placeholder adapted from MR self test. This should be replaced with more complex tests. +# +# ### build test binaries, initial clean for paranoia reasons +# - make clean +# - mkdir build +# - cd build +# - cmake .. +# - make -j +# - mv IVAS_cod ../IVAS_cod_test +# - mv IVAS_dec ../IVAS_dec_test +# - cd .. +# - rm -rf build/* +# +# ### compare to the previous merge commit in the main branch +# - git fetch origin main +# - git checkout $previous_merge_commit +# - echo "Building reference codec at commit $previous_merge_commit" +# +# ### build reference binaries +# - cd build +# - cmake .. +# - make -j +# - mv IVAS_cod ../IVAS_cod_ref +# - mv IVAS_dec ../IVAS_dec_ref +# - cd .. +# +# # helper variable - "|| true" to prevent failures from grep not finding anything +# # write to temporary file as workaround for failures observed with piping echo +# - echo $CI_COMMIT_MESSAGE > tmp.txt +# - non_be_flag=$(grep -c --ignore-case "\[non[ -]*be\]" tmp.txt) || true +# - ref_using_main=$(grep -c --ignore-case "\[ref[ -]*using[ -]*main\]" tmp.txt) || true +# +# ### re-checkout the latest commit in the main branch, if ref_using_main is not set +# - if [ $ref_using_main == 0 ]; then git checkout $latest_commit;fi +# +# ### prepare pytest +# # rename test binaries back +# - mv IVAS_cod_test IVAS_cod +# - mv IVAS_dec_test IVAS_dec +# # create references +# - testcase_timeout=60 +# - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --update_ref 1 --testcase_timeout=$testcase_timeout +# +# ### re-checkout the latest commit here, if ref_using_main is set +# - if [ $ref_using_main -eq 1 ]; then git checkout $latest_commit;fi +# +# ### run pytest +# - exit_code=0 +# - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$? +# - if [ $exit_code -ne 0 ] && [ $non_be_flag == 0 ]; then echo "pytest run had failures and non-BE flag not present"; exit $EXIT_CODE_FAIL; fi +# - zero_errors=$(cat report-junit.xml | grep -c 'errors="0"') || true +# - if [ $exit_code -ne 0 ] && [ $zero_errors == 1 ]; then echo "pytest run had failures, but no errors and non-BE flag present"; exit $EXIT_CODE_NON_BE; fi +# - if [ $exit_code -ne 0 ]; then echo "pytest run had errors"; exit $EXIT_CODE_FAIL; fi; +# allow_failure: +# exit_codes: +# - 123 +# artifacts: +# name: "main-push--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" +# expire_in: 1 week +# when: always +# paths: +# - report-junit.xml +# - report.html +# expose_as: "Results of comparison to previous merge commit" +# reports: +# junit: report-junit.xml # --------------------------------------------------------------- -- GitLab From 4ef010c7eb38bb957e0dd44e142e861b70c8df28 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Thu, 2 Oct 2025 11:25:15 +0200 Subject: [PATCH 188/340] Revert "tag renderer sanitizer tests for fast runners" This reverts commit 06e267f96e4c030d68ca586cdbca7a229eac5929. --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0473b241b4..9c53be7cae 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -549,8 +549,6 @@ renderer-smoke-test: - .test-job-linux - .rules-merge-request-to-main needs: ["build-codec-linux-cmake"] - tags: - - ivas-linux-fast stage: test timeout: "90 minutes" artifacts: -- GitLab From 1537ee94203bef414cf257c712b3de2b82439e96 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 2 Oct 2025 14:12:32 +1000 Subject: [PATCH 189/340] Code review comments, reorganize file api Signed-off-by: Ripinder Singh --- CMakeLists.txt | 2 +- apps/decoder.c | 459 ++-------------------------------- apps/encoder.c | 155 +++--------- lib_com/common_api_types.h | 20 -- lib_dec/jbm_jb4sb.h | 5 - lib_dec/lib_dec.c | 279 +++++++++++---------- lib_dec/lib_dec.h | 35 +-- lib_rend/ivas_rotation.c | 2 +- lib_util/ivas_rtp_file.c | 473 +++++++++++++++++++++++++++++++++++- lib_util/ivas_rtp_file.h | 61 +++-- lib_util/ivas_rtp_pi_data.h | 18 +- lib_util/rtpdump.c | 28 --- lib_util/rtpdump.h | 6 - readme.txt | 4 +- tests/rtp/ivasrtp.py | 3 +- 15 files changed, 724 insertions(+), 826 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de4727bec8..4dfb8a894c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,7 @@ file(GLOB libDecSrcs "lib_dec/*.c") file(GLOB libDecHeaders "lib_dec/*.h") add_library(lib_dec ${libDecSrcs} ${libDecHeaders}) target_link_libraries(lib_dec lib_com lib_rend lib_debug lib_isar) -target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar) +target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar lib_util) file(GLOB libUtilSrcs "lib_util/*.c") file(GLOB libUtilHeaders "lib_util/*.h") diff --git a/apps/decoder.c b/apps/decoder.c index da0fdd2fbd..0f5d0dd9a8 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -39,8 +39,6 @@ #include "evs_rtp_payload.h" #include "ism_file_writer.h" #ifdef IVAS_RTPDUMP -#include "ivas_rtp_api.h" -#include "ivas_rtp_pi_data.h" #include "ivas_rtp_file.h" #endif #include "jbm_file_writer.h" @@ -172,430 +170,6 @@ typedef struct } IVAS_DEC_HRTF_BINARY_WRAPPER; -#ifdef IVAS_RTPDUMP -#ifdef DEBUGGING -#define DEBUG_PRINT fprintf -#else -#define DEBUG_PRINT( ... ) -#endif - -typedef struct -{ - PIDATA data; - uint32_t timestamp; -} PIDATA_TS; - -typedef struct -{ - uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; - PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; - - IVAS_RTP_FILE_HANDLE hReader; - FILE *f_piDataOut; - IVAS_RTP_CODEC codecId; - uint32_t nReadPiData; - uint32_t nProcPiData; - uint32_t numFramesInPacket; - uint32_t numPiDataInPacket; - uint32_t remoteRequestBitmap; - bool speechLostIndicated; - bool restartNeeded; - bool isAMRWB_IOmode; - size_t numFramesProduced; - - IVAS_DATA_BUFFER rtpPacket; - IVAS_RTP_UNPACK_HANDLE hUnpack; - IVAS_RTP_UNPACK_CONFIG unpackCfg; - PI_DATA_DEPACKER_STATE piDataDepackerState; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO srInfo; -#endif -} IVAS_RTP; - -static const char *PiDataNames[IVAS_PI_MAX_ID] = { - "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", - "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", - "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", - "AUDIO_FOCUS_INDICATION", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", - "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", - "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", - "RESERVED30", "NO_DATA" -}; - - -static void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) -{ - uint32_t timestamp = ~0u; - if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) - { - return; - } - - if ( ftell( f_piDataOut ) > 2 ) - { - fprintf( f_piDataOut, ",\n" ); - } - - while ( nPiDataPresent-- > 0 ) - { - PIDATA_TS *cur = piData++; - - if ( timestamp != ( ~0u ) && timestamp != cur->timestamp ) - { - fprintf( f_piDataOut, "\n\t},\n\t\"%d\": {\n", cur->timestamp ); - } - else if ( timestamp != cur->timestamp ) - { - fprintf( f_piDataOut, "\t\"%d\": {\n", cur->timestamp ); - } - else - { - fprintf( f_piDataOut, ",\n" ); - } - fprintf( f_piDataOut, "\t\t\"%s\" : ", PiDataNames[cur->data.noPiData.piDataType] ); - switch ( cur->data.noPiData.piDataType ) - { - case IVAS_PI_SCENE_ORIENTATION: - case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: - case IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED: -#ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: - case IVAS_PI_HEAD_ORIENTATION: - case IVAS_PI_R_ISM_ORIENTATION: -#endif - { - fprintf( f_piDataOut, "{\n\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", - cur->data.scene.orientation.w, cur->data.scene.orientation.x, cur->data.scene.orientation.y, cur->data.scene.orientation.z ); - } - break; - - case IVAS_PI_ACOUSTIC_ENVIRONMENT: - { - fprintf( f_piDataOut, "{\n\t\t\t\"aeid\": %d", cur->data.acousticEnv.aeid ); - if ( cur->data.acousticEnv.availLateReverb ) - { - fprintf( f_piDataOut, ",\n\t\t\t\"rt60\": [ %f, %f, %f ],\n", cur->data.acousticEnv.rt60[0], cur->data.acousticEnv.rt60[1], cur->data.acousticEnv.rt60[2] ); - fprintf( f_piDataOut, "\t\t\t\"dsr\": [ %f, %f, %f ]", cur->data.acousticEnv.dsr[0], cur->data.acousticEnv.dsr[1], cur->data.acousticEnv.dsr[2] ); - } - if ( cur->data.acousticEnv.availEarlyReflections ) - { - fprintf( f_piDataOut, ",\n\t\t\t\"dim\": [ %f, %f, %f ],\n", cur->data.acousticEnv.roomDimensions.x, cur->data.acousticEnv.roomDimensions.y, cur->data.acousticEnv.roomDimensions.z ); - fprintf( f_piDataOut, "\t\t\t\"abscoeff\": [ %f, %f, %f, %f, %f, %f ]", cur->data.acousticEnv.absorbCoeffs[0], cur->data.acousticEnv.absorbCoeffs[1], cur->data.acousticEnv.absorbCoeffs[2], cur->data.acousticEnv.absorbCoeffs[3], cur->data.acousticEnv.absorbCoeffs[4], cur->data.acousticEnv.absorbCoeffs[5] ); - } - fprintf( f_piDataOut, "\n\t\t}" ); - } - break; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_LISTENER_POSITION: - case IVAS_PI_R_ISM_POSITION: - { - fprintf( f_piDataOut, "{\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", - cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); - } - break; - case IVAS_PI_AUDIO_DESCRIPTION: - { - uint32_t nEntries = cur->data.audioDesc.nValidEntries; - IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; - - fprintf( f_piDataOut, "[\n" ); - while ( nEntries-- > 0 ) - { - fprintf( f_piDataOut, "\t\t\t{\n" ); - fprintf( f_piDataOut, "\t\t\t\t\"isSpeech\": %s,\n", audioId->speech ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t\"isMusic\": %s,\n", audioId->music ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t\"isAmbiance\": %s,\n", audioId->ambiance ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t\"isEditable\": %s,\n", audioId->editable ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t\"isBinaural\": %s\n", audioId->binaural ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t}%c\n", ( nEntries == 0 ) ? ' ' : ',' ); - audioId++; - } - fprintf( f_piDataOut, "\t\t]" ); - } - break; - case IVAS_PI_DIEGETIC_TYPE: - { - bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; - fprintf( f_piDataOut, "{\n" ); - fprintf( f_piDataOut, "\t\t\t\"isDigetic\": [\n" ); - fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[0] ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[1] ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[2] ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[3] ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\t%s\n", isDiegetic[4] ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t]\n\t\t}" ); - } - break; - case IVAS_PI_AUDIO_FOCUS_INDICATION: - { - fprintf( f_piDataOut, "{" ); - if ( cur->data.focusIndication.availDirection ) - { - fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); - fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", - cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); - if ( cur->data.focusIndication.availLevel ) - { - fprintf( f_piDataOut, "," ); - } - } - if ( cur->data.focusIndication.availLevel ) - { - fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusIndication.flvl ); - } - fprintf( f_piDataOut, "\n\t\t}" ); - } - break; - case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: - { - IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; - fprintf( f_piDataOut, "{\n" ); - fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\"preferAmbiance\": %s,\n", das->ambiance ? "true" : "false" ); - fprintf( f_piDataOut, "\t\t\t\"level\": %d", das->sli ); - fprintf( f_piDataOut, "\n\t\t}" ); - } - break; - case IVAS_PI_RESERVED13: - case IVAS_PI_AUDIO_FOCUS_REQUEST: - { - fprintf( f_piDataOut, "{" ); - if ( cur->data.focusRequest.availDirection ) - { - fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); - fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", - cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); - if ( cur->data.focusRequest.availLevel ) - { - fprintf( f_piDataOut, "," ); - } - } - if ( cur->data.focusRequest.availLevel ) - { - fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusRequest.flvl ); - } - fprintf( f_piDataOut, "\n\t\t}" ); - } - break; - case IVAS_PI_RESERVED15: - case IVAS_PI_RESERVED27: - case IVAS_PI_RESERVED28: - case IVAS_PI_RESERVED29: - case IVAS_PI_RESERVED30: - { - fprintf( f_piDataOut, "{}" ); - } - break; - case IVAS_PI_ISM_NUM: - case IVAS_PI_ISM_ID: - case IVAS_PI_ISM_GAIN: - case IVAS_PI_ISM_ORIENTATION: - case IVAS_PI_ISM_POSITION: - case IVAS_PI_ISM_DISTANCE_ATTENUATION: - case IVAS_PI_ISM_DIRECTIVITY: - case IVAS_PI_PI_LATENCY: - case IVAS_PI_R_ISM_ID: - case IVAS_PI_R_ISM_GAIN: - case IVAS_PI_R_ISM_DIRECTION: -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - case IVAS_PI_NO_DATA: - { - fprintf( f_piDataOut, "{}" ); - } - break; - } - timestamp = cur->timestamp; - } - fprintf( f_piDataOut, "\n\t}" ); -} - -static void IVAS_RTP_Term( IVAS_RTP *rtp ) -{ - if ( NULL != rtp ) - { - IvasRtpFile_Close( &rtp->hReader ); - if ( rtp->f_piDataOut != NULL ) - { - fprintf( rtp->f_piDataOut, "\n}\n" ); - fclose( rtp->f_piDataOut ); - rtp->f_piDataOut = NULL; - } - IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); - } -} - -static ivas_error IVAS_RTP_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ) -{ - ivas_error error = IVAS_ERR_OK; - - memset( rtp, 0, sizeof( IVAS_RTP ) ); - - rtp->unpackCfg.maxFramesPerPacket = IVAS_MAX_FRAMES_PER_RTP_PACKET; - rtp->rtpPacket.buffer = rtp->packet; - rtp->rtpPacket.capacity = sizeof( rtp->packet ); - - error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); - if ( error == IVAS_ERR_OK ) - { - error = IvasRtpFile_Open( inputBitstreamFilename, false, &rtp->hReader ); - if ( error != IVAS_ERR_OK ) - { - return error; - } - - if ( piOutputFilename != NULL ) - { - rtp->f_piDataOut = fopen( piOutputFilename, "w" ); - if ( rtp->f_piDataOut == NULL ) - { - fprintf( stderr, "could not open: %s\n", piOutputFilename ); - return IVAS_ERR_FAILED_FILE_OPEN; - } - fprintf( rtp->f_piDataOut, "{\n" ); - } - } - - return error; -} - -static ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) -{ - ivas_error error = IVAS_ERR_OK; - IVAS_DATA_BUFFER packedFrame; - IVAS_RTP_CODEC codecId = IVAS_RTP_IVAS; - bool isAMRWB_IOmode = false; - - packedFrame.length = 0; - packedFrame.buffer = au; - packedFrame.capacity = ( IVAS_MAX_BITS_PER_FRAME / 8 ); - if ( rtp->numFramesInPacket == 0 ) - { - rtp->rtpPacket.length = 0; - if ( ( error = IvasRtpFile_Read( rtp->hReader, rtp->rtpPacket.buffer, &rtp->rtpPacket.length, rtp->rtpPacket.capacity ) ) != IVAS_ERR_OK ) - { - return error; - } - - if ( ( error = IVAS_RTP_UNPACK_PushPacket( rtp->hUnpack, &rtp->rtpPacket, - &rtp->numFramesInPacket, &rtp->numPiDataInPacket, - &rtp->remoteRequestBitmap ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "failed to unpack RTP packet error = %s\n", ivas_error_to_string( error ) ); - return error; - } - - rtp->nReadPiData = 0; - rtp->nProcPiData = 0; - - /* Pre-read all PI data */ - while ( rtp->numPiDataInPacket != 0 ) - { - PIDATA_TS *piData = &rtp->piData[rtp->nReadPiData]; - if ( ( error = IVAS_RTP_UNPACK_PullNextPiData( rtp->hUnpack, (IVAS_PIDATA_GENERIC *) &piData->data, sizeof( piData->data ), &piData->timestamp ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "failed to pull PI Data, error = %s\n", ivas_error_to_string( error ) ); - return error; - } - rtp->nReadPiData++; - rtp->numPiDataInPacket--; - } - IVAS_RTP_LogPiData( rtp->f_piDataOut, rtp->piData, rtp->nReadPiData ); - } - -#ifdef RTP_S4_251135_CR26253_0016_REV1 - error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &rtp->srInfo, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); -#else - error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); -#endif - if ( error != IVAS_ERR_OK ) - { - fprintf( stderr, "failed to pull frame after unpack\n" ); - return error; - } - - rtp->restartNeeded = false; - if ( *auSizeBits == 0 ) - { - /* NO_DATA_FRAME/SPEECH_LOST for IVAS and EVS is indicated by same bits - Do not restart decoder on codec/amrwb mode change in this case */ - } - else - { - rtp->restartNeeded = ( rtp->codecId != codecId ) || - ( codecId == IVAS_RTP_EVS && ( rtp->isAMRWB_IOmode != isAMRWB_IOmode ) ); - - if ( rtp->restartNeeded ) - { - fprintf( stdout, "\nRTP packet codec changed %s -> %s\n", - ( rtp->codecId == IVAS_RTP_EVS ) ? ( rtp->isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS", - ( codecId == IVAS_RTP_EVS ) ? ( isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS" ); - } - - rtp->codecId = codecId; - rtp->isAMRWB_IOmode = isAMRWB_IOmode; - } - - *qBit = !rtp->speechLostIndicated; - rtp->numFramesInPacket--; - rtp->numFramesProduced++; - *nextPacketRcvTime_ms += 20; - - return IVAS_ERR_OK; -} - -static ivas_error IVAS_RTP_ApplyPiData( IVAS_RTP *rtp, IVAS_DEC_HANDLE hIvasDec, uint32_t rtpTimeStamp ) -{ - ivas_error error = IVAS_ERR_OK; - while ( rtp->nProcPiData < rtp->nReadPiData && - rtp->piData[rtp->nProcPiData].timestamp <= rtpTimeStamp ) - { - PIDATA_TS *piData = &rtp->piData[rtp->nProcPiData++]; - if ( hIvasDec ) - { - uint32_t piDataType = ( (IVAS_PIDATA_GENERIC *) &piData->data )->piDataType; - switch ( piDataType ) - { - case IVAS_PI_SCENE_ORIENTATION: - { - IVAS_QUATERNION *quat = &piData->data.scene.orientation; - DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - } - break; - - case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: - { - IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; - DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - } - break; - -#ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_DIEGETIC_TYPE: - { - DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); - IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); - } - break; - -#endif - default: - { - fprintf( stderr, "Unhandled PI data of type : %s\n", PiDataNames[piDataType] ); - } - break; - } - if ( error != IVAS_ERR_OK ) - { - return error; - } - } - } - return error; -} -#endif /* IVAS_RTPDUMP */ /*------------------------------------------------------------------------------------------* * Local functions prototypes @@ -3463,16 +3037,19 @@ static ivas_error printBitstreamInfoVoip( ivas_error error = IVAS_ERR_OK; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp; + uint8_t au[( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3]; + int16_t auSizeBits; + uint8_t *auPtr = NULL; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; EVS_RTPDUMP_DEPACKER_ERROR rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_NO_ERROR; bool isAMRWB_IOmode; uint16_t frameTypeIndex; -#endif uint8_t au[( IVAS_MAX_BITS_PER_FRAME + 7 ) >> 3]; int16_t auSizeBits; uint8_t *auPtr = NULL; +#endif bool qBit; uint32_t nextPacketRcvTime_ms = 0; uint16_t rtpSequenceNumber; @@ -3486,9 +3063,9 @@ static ivas_error printBitstreamInfoVoip( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_Init( &ivasRtp, arg.inputBitstreamFilename, NULL ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); + fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; } #else @@ -3528,7 +3105,7 @@ static ivas_error printBitstreamInfoVoip( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP - error = readNextFrame( &ivasRtp, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); #endif @@ -3637,6 +3214,7 @@ static ivas_error decodeVoIP( IVAS_RTP ivasRtp = { 0 }; IVAS_DEC_HANDLE hIvasDec = *phIvasDec; int32_t initialTsOffsetSystemAndRTP = 0; + uint32_t nProcPiData = 0; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; @@ -3692,13 +3270,14 @@ static ivas_error decodeVoIP( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "error in IVAS_RTP_Init(): %d\n", error ); + fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; } #else f_rtpstream = fopen( arg.inputBitstreamFilename, "r" ); + if ( f_rtpstream == NULL ) { fprintf( stderr, "could not open: %s\n", arg.inputBitstreamFilename ); @@ -4009,11 +3588,21 @@ static ivas_error decodeVoIP( { /* Rudimentry Time Mapping to map system time to rtp timestamp */ uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; - if ( ( error = IVAS_RTP_ApplyPiData( &ivasRtp, hIvasDec, piTs ) ) != IVAS_ERR_OK ) + uint32_t numPiData = 0; + + while ( nProcPiData + numPiData < ivasRtp.nReadPiData && + ivasRtp.piData[nProcPiData + numPiData].timestamp <= piTs ) + { + numPiData++; + } + + if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[nProcPiData], numPiData ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } + + nProcPiData += numPiData; } #endif #ifdef SUPPORT_JBM_TRACEFILE @@ -4192,10 +3781,6 @@ static ivas_error decodeVoIP( } } -#ifdef IVAS_RTPDUMP - IVAS_DEC_resetExternalOrientations( hIvasDec ); -#endif - vec_pos_update = ( vec_pos_update + 1 ) % vec_pos_len; if ( vec_pos_update == 0 ) { diff --git a/apps/encoder.c b/apps/encoder.c index 147512afc9..66836a1726 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -40,8 +40,6 @@ #include "jbm_file_reader.h" #include "masa_file_reader.h" #ifdef IVAS_RTPDUMP -#include "ivas_rtp_api.h" -#include "ivas_rtp_pi_data.h" #include "rotation_file_reader.h" #include "ivas_rtp_file.h" #endif @@ -50,6 +48,7 @@ #endif #include "wmc_auto.h" + #define WMC_TOOL_SKIP /*------------------------------------------------------------------------------------------* @@ -223,19 +222,8 @@ int main( #endif #ifdef IVAS_RTPDUMP - IVAS_RTP_FILE_HANDLE hWriter = NULL; - IVAS_RTP_PACK_HANDLE hPack = NULL; - uint8_t au[IVAS_MAX_BITS_PER_FRAME / 8]; - uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; - IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; - IVAS_DATA_BUFFER rtpPacket = { 0, 0, NULL }; - uint32_t numFramesInPayload = 0; - - packedFrame.capacity = sizeof( au ); - packedFrame.buffer = au; - rtpPacket.capacity = sizeof( packet ); - rtpPacket.buffer = packet; + IVAS_RTP ivasRtp = { 0 }; #endif /*------------------------------------------------------------------------------------------* @@ -635,38 +623,9 @@ int main( if ( arg.rtpdumpOutput ) { - IVAS_RTP_PACK_CONFIG packCfg; - uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); - - packCfg.maxFramesPerPacket = arg.numFramesPerPacket; - - /* Open the output file for RTPDump writing */ - error = IvasRtpFile_Open( arg.outputBitstreamFilename, true, &hWriter ); - if ( error != IVAS_ERR_OK ) - { - fprintf( stderr, "could not open: %s\n", arg.outputBitstreamFilename ); - goto cleanup; - } - - error = IVAS_RTP_PACK_Open( &hPack, &packCfg ); - if ( error != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_WRITER_Init( &ivasRtp, arg.outputBitstreamFilename, arg.numFramesPerPacket ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "error in IVAS_RTP_PACK_Open(): %d\n", error ); - goto cleanup; - } - - - error = IVAS_RTP_PACK_UpdateHeader( - hPack, - SSRC, - 0, - NULL, - 0, - 0, - NULL ); - if ( error != IVAS_ERR_OK ) - { - fprintf( stderr, "error in IVAS_RTP_PACK_UpdateHeader(): %d\n", error ); + fprintf( stderr, "\nError: Can't open output bitstream file for RTP output %s \n\n", arg.outputBitstreamFilename ); goto cleanup; } } @@ -700,7 +659,6 @@ int main( int16_t numSamplesRead = 0; uint16_t bitStream[IVAS_MAX_BITS_PER_FRAME]; - uint16_t numBits = 0; #ifdef DEBUG_SBA #ifdef DEBUG_AGC @@ -864,47 +822,26 @@ int main( /* *** Encode one frame *** */ #ifdef IVAS_RTPDUMP - if ( hPack ) + if ( ivasRtp.hPack ) { - if ( ( error = IVAS_ENC_EncodeFrameToCompact( hIvasEnc, pcmBuf, pcmBufSize, packedFrame.buffer, &numBits ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nencodeFrame failed: %s\n\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } - - packedFrame.length = ( numBits + 7 ) / 8; - rtpPacket.length = 0; + bool isMono = ( arg.inputFormat == IVAS_ENC_INPUT_MONO ); + bool forcePacket = ( numSamplesRead < pcmBufSize ); /* If EoF force Packet generation */ - /* Push Encoded Stream to */ - if ( ( error = IVAS_RTP_PACK_PushFrame( hPack, - arg.inputFormat != IVAS_ENC_INPUT_MONO ? IVAS_RTP_IVAS : IVAS_RTP_EVS, -#ifdef RTP_S4_251135_CR26253_0016_REV1 - NULL, -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - &packedFrame ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while pushing audio frame to RTP pack\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } + ivasRtp.nWrittenPiData = 0; /* scene orientation */ if ( sceneOrientationFileReader ) { - IVAS_PIDATA_ORIENTATION sceneOrientation = { - .size = sizeof( IVAS_PIDATA_ORIENTATION ), - .piDataType = IVAS_PI_SCENE_ORIENTATION, - .orientation = { 0.0f, 0.0f, 0.0f, 0.0f } - }; + PIDATA_TS *piDataTs = &ivasRtp.piData[ivasRtp.nWrittenPiData++]; + IVAS_PIDATA_ORIENTATION *scene = &piDataTs->data.scene; - if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &sceneOrientation.orientation, NULL ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while reading scene orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( sceneOrientationFileReader ) ); - goto cleanup; - } + memset( piDataTs, 0, sizeof( PIDATA_TS ) ); + scene->size = sizeof( IVAS_PIDATA_ORIENTATION ); + scene->piDataType = IVAS_PI_SCENE_ORIENTATION; - if ( ( error = IVAS_RTP_PACK_PushPiData( hPack, (const IVAS_PIDATA_GENERIC *) &sceneOrientation ) ) != IVAS_ERR_OK ) + if ( ( error = HeadRotationFileReading( sceneOrientationFileReader, &scene->orientation, NULL ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nError %s while pushing scene orientation\n", IVAS_ENC_GetErrorMessage( error ) ); + fprintf( stderr, "\nError %s while reading scene orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( sceneOrientationFileReader ) ); goto cleanup; } } @@ -912,39 +849,30 @@ int main( /* device orientation */ if ( deviceOrientationFileReader ) { - IVAS_PIDATA_ORIENTATION deviceOrientation = { - .size = sizeof( IVAS_PIDATA_ORIENTATION ), - .piDataType = IVAS_PI_DEVICE_ORIENTATION_COMPENSATED, - .orientation = { 0.0f, 0.0f, 0.0f, 0.0f } - }; + PIDATA_TS *piDataTs = &ivasRtp.piData[ivasRtp.nWrittenPiData++]; + IVAS_PIDATA_ORIENTATION *device = &piDataTs->data.deviceUnCompensated; - if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &deviceOrientation.orientation, NULL ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while reading device orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( deviceOrientationFileReader ) ); - goto cleanup; - } + memset( piDataTs, 0, sizeof( PIDATA_TS ) ); + device->size = sizeof( IVAS_PIDATA_ORIENTATION ); + device->piDataType = IVAS_PI_DEVICE_ORIENTATION_COMPENSATED; - if ( ( error = IVAS_RTP_PACK_PushPiData( hPack, (const IVAS_PIDATA_GENERIC *) &deviceOrientation ) ) != IVAS_ERR_OK ) + if ( ( error = HeadRotationFileReading( deviceOrientationFileReader, &device->orientation, NULL ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nError %s while pushing scene orientation\n", IVAS_ENC_GetErrorMessage( error ) ); + fprintf( stderr, "\nError %s while reading device orientation from %s\n", IVAS_ENC_GetErrorMessage( error ), RotationFileReader_getFilePath( deviceOrientationFileReader ) ); goto cleanup; } } - if ( ( numSamplesRead < pcmBufSize ) || IVAS_RTP_PACK_GetNumFrames( hPack ) == arg.numFramesPerPacket ) + if ( ( error = IVAS_ENC_EncodeFrameToCompact( hIvasEnc, pcmBuf, pcmBufSize, au, &numBits ) ) != IVAS_ERR_OK ) { - /* Generate RTP Packet */ - if ( ( error = IVAS_RTP_PACK_GetPacket( hPack, &rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while packing RTP Header\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } + fprintf( stderr, "\nencodeFrame failed: %s\n\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; + } - if ( ( error = IvasRtpFile_Write( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } + if ( ( error = IVAS_RTP_WriteNextFrame( &ivasRtp, au, numBits, isMono, forcePacket ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing audio frame to RTP pack\n", IVAS_ENC_GetErrorMessage( error ) ); + goto cleanup; } } else @@ -1043,27 +971,6 @@ cleanup: } #ifdef IVAS_RTPDUMP - if ( hPack ) - { - /* Complete the last packet */ - if ( IVAS_RTP_PACK_GetNumFrames( hPack ) != 0 ) - { - if ( ( error = IVAS_RTP_PACK_GetPacket( hPack, &rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while packing RTP Packet\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } - - if ( ( error = IvasRtpFile_Write( hWriter, rtpPacket.buffer, rtpPacket.length ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError %s while writing RTP packet\n", IVAS_ENC_GetErrorMessage( error ) ); - goto cleanup; - } - } - - IVAS_RTP_PACK_Close( &hPack ); - } - if ( sceneOrientationFileReader ) { RotationFileReader_close( &sceneOrientationFileReader ); @@ -1074,7 +981,7 @@ cleanup: RotationFileReader_close( &deviceOrientationFileReader ); } - IvasRtpFile_Close( &hWriter ); + IVAS_RTP_Term( &ivasRtp ); #endif IVAS_ENC_Close( &hIvasEnc ); diff --git a/lib_com/common_api_types.h b/lib_com/common_api_types.h index ce2982b63b..f5b13c0dea 100644 --- a/lib_com/common_api_types.h +++ b/lib_com/common_api_types.h @@ -212,26 +212,6 @@ typedef struct _IVAS_JBM_TRACE_DATA } IVAS_JBM_TRACE_DATA; -#ifdef IVAS_RTPDUMP -typedef struct -{ - int16_t PIdataPresent; - char *sceneOrientation; - char *deviceOrientationCompensated; - char *deviceOrientationUncompensated; - char *acousticEnvironmentId; - char *acousticEnvironmentOnlyLateReverb; - char *acousticEnvironmentLateReverbAndEarlyReflections; -} PI_DATA_CURRENT_FRAME; - -typedef struct -{ - int16_t sceneOrientationSaved; - IVAS_QUATERNION sceneOrientationQuat; - int16_t deviceOrientationSaved; - IVAS_QUATERNION deviceOrientationQuat; -} PI_DATA_DEPACKER_STATE; -#endif typedef enum _ivas_binaural_renderer_type { IVAS_BIN_RENDERER_TYPE_NONE, diff --git a/lib_dec/jbm_jb4sb.h b/lib_dec/jbm_jb4sb.h index 2ef48dbfb3..8155d66ae9 100644 --- a/lib_dec/jbm_jb4sb.h +++ b/lib_dec/jbm_jb4sb.h @@ -63,13 +63,8 @@ struct JB4_DATAUNIT uint32_t rcvTime; /** true, if the data unit contains only silence */ bool silenceIndicator; -#ifdef IVAS_RTPDUMP - /** good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ - Word16 isGoodFrame; -#else /** Q bit for AMR-WB IO */ Word16 qBit; -#endif /** the binary encoded access unit */ uint8_t *data; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index c3b7e69a82..5c1edb06fc 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -338,10 +338,16 @@ void IVAS_DEC_Close( ( *phIvasDec )->hVoIP = NULL; } +#ifndef IVAS_RTPDUMP + ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); + +#endif /* destroy Split binaural renderer (ISAR) handle */ if ( ( *phIvasDec )->st_ivas ) { +#ifdef IVAS_RTPDUMP ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); +#endif ivas_destroy_dec( ( *phIvasDec )->st_ivas ); ( *phIvasDec )->st_ivas = NULL; } @@ -3233,11 +3239,7 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( const uint16_t rtpSequenceNumber, /* i : RTP sequence number (16 bits) */ const uint32_t rtpTimeStamp, /* i : RTP timestamp (32 bits) */ const uint32_t rcvTime_ms, /* i : receive time of the RTP packet in milliseconds */ -#ifdef IVAS_RTPDUMP - const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ -#else - const bool qBit /* i : Q bit for AMR-WB IO */ -#endif + const bool qBit /* i : Q bit for AMR-WB IO */ ) { JB4_DATAUNIT_HANDLE dataUnit; @@ -3279,11 +3281,7 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( dataUnit->timeStamp = rtpTimeStamp; dataUnit->partial_frame = 0; dataUnit->partialCopyOffset = partialCopyOffset; -#ifdef IVAS_RTPDUMP - dataUnit->isGoodFrame = isGoodFrame; -#else dataUnit->qBit = qBit; -#endif /* add the frame to the JBM */ result = JB4_PushDataUnit( hIvasDec->hVoIP->hJBM, dataUnit, rcvTime_ms ); @@ -3306,11 +3304,7 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( dataUnit->timeStamp = rtpTimeStamp - partialCopyOffset * dataUnit->duration; dataUnit->partial_frame = 1; dataUnit->partialCopyOffset = partialCopyOffset; -#ifdef IVAS_RTPDUMP - dataUnit->isGoodFrame = isGoodFrame; -#else dataUnit->qBit = qBit; -#endif /* add the frame to the JBM */ result = JB4_PushDataUnit( hIvasDec->hVoIP->hJBM, dataUnit, rcvTime_ms ); @@ -3734,125 +3728,6 @@ ivas_error IVAS_DEC_Flush( } -#ifdef IVAS_RTPDUMP -/*---------------------------------------------------------------------* - * IVAS_DEC_feedSinglePIorientation( ) - * - * Feed a single orientation PI data to external orientation handle. - *---------------------------------------------------------------------*/ - -ivas_error IVAS_DEC_feedSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ -) -{ - int16_t i; - ivas_error error = IVAS_ERR_OK; - IVAS_QUATERNION savedInvOrientation; - - if ( isOrientationSaved ) - { - if ( !hIvasDec->st_ivas->hExtOrientationData ) - { - if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) - { - return error; - } - } - - if ( !hIvasDec->st_ivas->hCombinedOrientationData ) - { - if ( ( error = ivas_combined_orientation_open( &( hIvasDec->st_ivas->hCombinedOrientationData ), hIvasDec->st_ivas->hDecoderConfig->output_Fs, hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) - { - return error; - } - } - - QuaternionInverse( *savedOrientation, &savedInvOrientation ); - - /* use the new PI orientation or the previously saved orientation in processing */ - for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) - { - QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], savedInvOrientation, - &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); - hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; - } - hIvasDec->updateOrientation = true; - } - return error; -} - - -/*---------------------------------------------------------------------* - * IVAS_DEC_feedPIdata( ) - * - * Feed PI data to the decoder handle - *---------------------------------------------------------------------*/ - -ivas_error IVAS_DEC_feedPIdata( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ -) -{ - ivas_error error = IVAS_ERR_OK; - - /* scene orientation */ - if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) != IVAS_ERR_OK ) - { - return error; - } - - /* device orientation */ - if ( ( error = IVAS_DEC_feedSinglePIorientation( hIvasDec, PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) != IVAS_ERR_OK ) - { - return error; - } - - return error; -} - - -/*---------------------------------------------------------------------* - * IVAS_DEC_resetExternalOrientations( ) - * - * Reset external orientations - *---------------------------------------------------------------------*/ - -void IVAS_DEC_resetExternalOrientations( - IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ -) -{ - ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); -} - - -#endif -#ifdef RTP_S4_251135_CR26253_0016_REV1 -/*---------------------------------------------------------------------* - * IVAS_DEC_setDiegeticInput( ) - * - * Set isDiegeticInput flag for combined orientation handle based on PI data. - *---------------------------------------------------------------------*/ - -void IVAS_DEC_setDiegeticInputPI( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *diegeticPIValues /* i : diegetic values for the input stream */ -) -{ - if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) - { - int8_t i; - for ( i = 0; i < (1 + IVAS_MAX_NUM_OBJECTS); i++ ) - { - hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; - } - hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPISet = true; - } -} - - -#endif /*---------------------------------------------------------------------* * IVAS_DEC_VoIP_IsEmpty( ) * @@ -5157,3 +5032,143 @@ ivas_error IVAS_DEC_is_split_rendering_coded_out( return IVAS_ERR_OK; } + +#ifdef IVAS_RTPDUMP +#ifdef DEBUGGING +#define DEBUG_PRINT fprintf +#else +#define DEBUG_PRINT( ... ) +#endif + +/*---------------------------------------------------------------------* + * IVAS_DEC_feedSinglePIorientation( ) + * + * Feed a single orientation PI data to external orientation handle. + *---------------------------------------------------------------------*/ + +static ivas_error IVAS_DEC_feedSinglePIorientation( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ +) +{ + int16_t i; + ivas_error error = IVAS_ERR_OK; + IVAS_QUATERNION savedInvOrientation; + + if ( isOrientationSaved ) + { + if ( !hIvasDec->st_ivas->hExtOrientationData ) + { + if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + if ( !hIvasDec->st_ivas->hCombinedOrientationData ) + { + if ( ( error = ivas_combined_orientation_open( &( hIvasDec->st_ivas->hCombinedOrientationData ), hIvasDec->st_ivas->hDecoderConfig->output_Fs, hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + QuaternionInverse( *savedOrientation, &savedInvOrientation ); + + /* use the new PI orientation or the previously saved orientation in processing */ + for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) + { + QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], savedInvOrientation, + &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); + hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; + } + hIvasDec->updateOrientation = true; + } + return error; +} + +/*---------------------------------------------------------------------* + * IVAS_DEC_resetExternalOrientations( ) + * + * Reset external orientations + *---------------------------------------------------------------------*/ + +void IVAS_DEC_resetExternalOrientations( + IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ +) +{ + ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); +} + +#ifdef RTP_S4_251135_CR26253_0016_REV1 +/*---------------------------------------------------------------------* + * IVAS_DEC_setDiegeticInput( ) + * + * Set isDiegeticInput flag for combined orientation handle based on PI data. + *---------------------------------------------------------------------*/ + +static void IVAS_DEC_setDiegeticInputPI( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const bool *diegeticPIValues /* i : diegetic values for the input stream */ +) +{ + if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) + { + int8_t i; + for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) + { + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; + } + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPISet = true; + } +} +#endif + +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) +{ + ivas_error error = IVAS_ERR_OK; + while ( numPiData-- ) + { + uint32_t piDataType = piData->data.noPiData.piDataType; + switch ( piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + { + IVAS_QUATERNION *quat = &piData->data.scene.orientation; + DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + } + break; + + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + { + IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; + DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + } + break; + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_DIEGETIC_TYPE: + { + DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); + IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); + } + break; +#endif + + default: + { + /* NOT HANDLED PI DATA - DO NOTHING */ + } + break; + } + if ( error != IVAS_ERR_OK ) + { + return error; + } + } + return error; +} +#endif \ No newline at end of file diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index caed7bd54b..242b601942 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -285,11 +285,7 @@ ivas_error IVAS_DEC_VoIP_FeedFrame( const uint16_t rtpSequenceNumber, /* i : RTP sequence number (16 bits) */ const uint32_t rtpTimeStamp, /* i : RTP timestamp (32 bits) */ const uint32_t rcvTime_ms, /* i : receive time of the RTP packet in milliseconds */ -#ifdef IVAS_RTPDUMP - const bool isGoodFrame /* i : Good frame indicator (Q bit for AMR-WB IO, otherwise set to true) */ -#else -const bool qBit /* i : Q bit for AMR-WB IO */ -#endif + const bool qBit /* i : Q bit for AMR-WB IO */ ); ivas_error IVAS_DEC_VoIP_SetScale( @@ -333,30 +329,6 @@ ivas_error IVAS_DEC_Flush( int16_t *nSamplesFlushed /* o : number of samples flushed */ ); -#ifdef IVAS_RTPDUMP -ivas_error IVAS_DEC_feedSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ -); - -ivas_error IVAS_DEC_feedPIdata( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - PI_DATA_DEPACKER_STATE *PIdataDepackerState /* i/o: data handle for the PI depacker state */ -); - -void IVAS_DEC_resetExternalOrientations( - IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ -); - -#endif -#ifdef RTP_S4_251135_CR26253_0016_REV1 -void IVAS_DEC_setDiegeticInputPI( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *diegeticPIValues /* i : diegetic values for the input stream */ -); - -#endif /* Setter functions - apply changes to decoder configuration */ /*! r: error code */ @@ -581,6 +553,11 @@ void IVAS_DEC_PrintDisclaimer( void ); +#ifdef IVAS_RTPDUMP +#include "ivas_rtp_pi_data.h" +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); +#endif + /* clang-format on */ #endif diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 8b62d7a111..6c6acb2653 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -864,7 +864,7 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; #ifdef RTP_S4_251135_CR26253_0016_REV1 - for ( i = 0; i < (1 + IVAS_MAX_NUM_OBJECTS); i++ ) + for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) { ( *hCombinedOrientationData )->isDiegeticInputPI[i] = true; } diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 0851a998f6..1c45869e69 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -31,8 +31,10 @@ *******************************************************************************************************/ #include #include +#include #include +#include "lib_dec.h" #include "ivas_rtp_file.h" #include "ivas_error_utils.h" @@ -42,7 +44,7 @@ struct IVAS_RTP_FILE FILE *f_rtpstream; }; -ivas_error IvasRtpFile_Open( +static ivas_error IvasRtpFile_Open( const char *filePath, /* i : path to CA config file */ bool isFileWriter, /* i : instance is a file writer else reader */ IVAS_RTP_FILE_HANDLE *phRtpFile /* o : pointer to an IVAS file reader handle */ @@ -65,7 +67,7 @@ ivas_error IvasRtpFile_Open( return IVAS_ERR_OK; } -ivas_error IvasRtpFile_Close( +static ivas_error IvasRtpFile_Close( IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ ) { @@ -83,17 +85,17 @@ ivas_error IvasRtpFile_Close( return IVAS_ERR_OK; } -ivas_error IvasRtpFile_Write( - IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ +static ivas_error IvasRtpFile_Write( + IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file writer handle */ const uint8_t *packet, size_t numBytes ) { ivas_error error = IVAS_ERR_OK; - if ( hReader->isFileWriter ) + if ( hRtpFile->isFileWriter ) { uint32_t length = (uint32_t) numBytes; /* Max packet length is < 32 bits*/ - fwrite( &length, sizeof( uint32_t ), 1, hReader->f_rtpstream ); - fwrite( packet, sizeof( uint8_t ), numBytes, hReader->f_rtpstream ); + fwrite( &length, sizeof( uint32_t ), 1, hRtpFile->f_rtpstream ); + fwrite( packet, sizeof( uint8_t ), numBytes, hRtpFile->f_rtpstream ); } else { @@ -102,20 +104,20 @@ ivas_error IvasRtpFile_Write( return error; } -ivas_error IvasRtpFile_Read( - IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ +static ivas_error IvasRtpFile_Read( + IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file writer handle */ uint8_t *packet, size_t *numBytes, size_t capacity ) { size_t nread = 0; uint32_t length = 0; - if ( hReader->isFileWriter ) + if ( hRtpFile->isFileWriter ) { return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "File open for writing cannot be read" ); } - nread = fread( &length, sizeof( uint32_t ), 1, hReader->f_rtpstream ); /* Read Packet Length */ + nread = fread( &length, sizeof( uint32_t ), 1, hRtpFile->f_rtpstream ); /* Read Packet Length */ if ( nread == 0 ) { return IVAS_ERR_END_OF_FILE; @@ -128,7 +130,7 @@ ivas_error IvasRtpFile_Read( return IVAS_ERR_INVALID_OUTPUT_BUFFER_SIZE; } - nread = fread( packet, sizeof( uint8_t ), ( *numBytes ), hReader->f_rtpstream ); /* Read Packet */ + nread = fread( packet, sizeof( uint8_t ), ( *numBytes ), hRtpFile->f_rtpstream ); /* Read Packet */ if ( nread < ( *numBytes ) ) { return IVAS_ERR_END_OF_FILE; @@ -136,3 +138,450 @@ ivas_error IvasRtpFile_Read( return IVAS_ERR_OK; } + +static const char *const PiDataNames[IVAS_PI_MAX_ID] = { + "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", + "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", + "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", + "AUDIO_FOCUS_INDICATION", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", + "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", + "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", + "RESERVED30", "NO_DATA" +}; + +void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) +{ + uint32_t timestamp = ~0u; + if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) + { + return; + } + + if ( ftell( f_piDataOut ) > 2 ) + { + fprintf( f_piDataOut, ",\n" ); + } + + while ( nPiDataPresent-- > 0 ) + { + PIDATA_TS *cur = piData++; + + if ( timestamp != ( ~0u ) && timestamp != cur->timestamp ) + { + fprintf( f_piDataOut, "\n\t},\n\t\"%d\": {\n", cur->timestamp ); + } + else if ( timestamp != cur->timestamp ) + { + fprintf( f_piDataOut, "\t\"%d\": {\n", cur->timestamp ); + } + else + { + fprintf( f_piDataOut, ",\n" ); + } + fprintf( f_piDataOut, "\t\t\"%s\" : ", PiDataNames[cur->data.noPiData.piDataType] ); + switch ( cur->data.noPiData.piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + case IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED: +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: + case IVAS_PI_HEAD_ORIENTATION: + case IVAS_PI_R_ISM_ORIENTATION: +#endif + { + fprintf( f_piDataOut, "{\n\t\t\t\"w\": %f,\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", + cur->data.scene.orientation.w, cur->data.scene.orientation.x, cur->data.scene.orientation.y, cur->data.scene.orientation.z ); + } + break; + + case IVAS_PI_ACOUSTIC_ENVIRONMENT: + { + fprintf( f_piDataOut, "{\n\t\t\t\"aeid\": %d", cur->data.acousticEnv.aeid ); + if ( cur->data.acousticEnv.availLateReverb ) + { + fprintf( f_piDataOut, ",\n\t\t\t\"rt60\": [ %f, %f, %f ],\n", cur->data.acousticEnv.rt60[0], cur->data.acousticEnv.rt60[1], cur->data.acousticEnv.rt60[2] ); + fprintf( f_piDataOut, "\t\t\t\"dsr\": [ %f, %f, %f ]", cur->data.acousticEnv.dsr[0], cur->data.acousticEnv.dsr[1], cur->data.acousticEnv.dsr[2] ); + } + if ( cur->data.acousticEnv.availEarlyReflections ) + { + fprintf( f_piDataOut, ",\n\t\t\t\"dim\": [ %f, %f, %f ],\n", cur->data.acousticEnv.roomDimensions.x, cur->data.acousticEnv.roomDimensions.y, cur->data.acousticEnv.roomDimensions.z ); + fprintf( f_piDataOut, "\t\t\t\"abscoeff\": [ %f, %f, %f, %f, %f, %f ]", cur->data.acousticEnv.absorbCoeffs[0], cur->data.acousticEnv.absorbCoeffs[1], cur->data.acousticEnv.absorbCoeffs[2], cur->data.acousticEnv.absorbCoeffs[3], cur->data.acousticEnv.absorbCoeffs[4], cur->data.acousticEnv.absorbCoeffs[5] ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_LISTENER_POSITION: + case IVAS_PI_R_ISM_POSITION: + { + fprintf( f_piDataOut, "{\n\t\t\t\"x\": %f,\n\t\t\t\"y\": %f,\n\t\t\t\"z\": %f \n\t\t}", + cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); + } + break; + case IVAS_PI_AUDIO_DESCRIPTION: + { + uint32_t nEntries = cur->data.audioDesc.nValidEntries; + IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; + + fprintf( f_piDataOut, "[\n" ); + while ( nEntries-- > 0 ) + { + fprintf( f_piDataOut, "\t\t\t{\n" ); + fprintf( f_piDataOut, "\t\t\t\t\"isSpeech\": %s,\n", audioId->speech ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isMusic\": %s,\n", audioId->music ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isAmbiance\": %s,\n", audioId->ambiance ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isEditable\": %s,\n", audioId->editable ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t\"isBinaural\": %s\n", audioId->binaural ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t}%c\n", ( nEntries == 0 ) ? ' ' : ',' ); + audioId++; + } + fprintf( f_piDataOut, "\t\t]" ); + } + break; + case IVAS_PI_DIEGETIC_TYPE: + { + bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; + fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "\t\t\t\"isDigetic\": [\n" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[0] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[1] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[2] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[3] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\t%s\n", isDiegetic[4] ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t]\n\t\t}" ); + } + break; + case IVAS_PI_AUDIO_FOCUS_INDICATION: + { + fprintf( f_piDataOut, "{" ); + if ( cur->data.focusIndication.availDirection ) + { + fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); + fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", + cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusIndication.flvl ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: + { + IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; + fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferAmbiance\": %s,\n", das->ambiance ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"level\": %d", das->sli ); + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; + case IVAS_PI_RESERVED13: + case IVAS_PI_AUDIO_FOCUS_REQUEST: + { + fprintf( f_piDataOut, "{" ); + if ( cur->data.focusRequest.availDirection ) + { + fprintf( f_piDataOut, "\n\t\t\t\"direction\": {\n" ); + fprintf( f_piDataOut, "\t\t\t\t\t\t\"w\": %f,\n\t\t\t\t\t\t\"x\": %f,\n\t\t\t\t\t\t\"y\": %f,\n\t\t\t\t\t\t\"z\": %f \n\t\t\t}", + cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "\n\t\t\t\"level\": %d", cur->data.focusRequest.flvl ); + } + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; + case IVAS_PI_RESERVED15: + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: + case IVAS_PI_RESERVED30: + { + fprintf( f_piDataOut, "{}" ); + } + break; + case IVAS_PI_ISM_NUM: + case IVAS_PI_ISM_ID: + case IVAS_PI_ISM_GAIN: + case IVAS_PI_ISM_ORIENTATION: + case IVAS_PI_ISM_POSITION: + case IVAS_PI_ISM_DISTANCE_ATTENUATION: + case IVAS_PI_ISM_DIRECTIVITY: + case IVAS_PI_PI_LATENCY: + case IVAS_PI_R_ISM_ID: + case IVAS_PI_R_ISM_GAIN: + case IVAS_PI_R_ISM_DIRECTION: +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + case IVAS_PI_NO_DATA: + { + fprintf( f_piDataOut, "{}" ); + } + break; + } + timestamp = cur->timestamp; + } + fprintf( f_piDataOut, "\n\t}" ); +} + +void IVAS_RTP_Term( IVAS_RTP *rtp ) +{ + if ( NULL != rtp ) + { + if ( rtp->hPack != NULL ) + { + /* Complete the last packet */ + if ( IVAS_RTP_PACK_GetNumFrames( rtp->hPack ) != 0 ) + { + ivas_error error = IVAS_ERR_OK; + uint32_t numFramesInPayload = 0; + + if ( ( error = IVAS_RTP_PACK_GetPacket( rtp->hPack, &rtp->rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while packing RTP Packet\n", ivas_error_to_string( error ) ); + } + else if ( numFramesInPayload > 0 ) + { + if ( ( error = IvasRtpFile_Write( rtp->hRtpFile, rtp->rtpPacket.buffer, rtp->rtpPacket.length ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while writing RTP packet\n", ivas_error_to_string( error ) ); + } + } + } + IVAS_RTP_PACK_Close( &rtp->hPack ); + } + + if ( rtp->hUnpack != NULL ) + { + IVAS_RTP_UNPACK_Close( &rtp->hUnpack ); + } + + if ( rtp->f_piDataOut != NULL ) + { + fprintf( rtp->f_piDataOut, "\n}\n" ); + fclose( rtp->f_piDataOut ); + rtp->f_piDataOut = NULL; + } + + if ( rtp->hRtpFile != NULL ) + { + IvasRtpFile_Close( &rtp->hRtpFile ); + } + } +} + +ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ) +{ + uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); + ivas_error error = IVAS_ERR_OK; + + memset( rtp, 0, sizeof( IVAS_RTP ) ); + + rtp->packCfg.maxFramesPerPacket = numFramesPerPacket; + rtp->rtpPacket.buffer = rtp->packet; + rtp->rtpPacket.capacity = sizeof( rtp->packet ); + + error = IVAS_RTP_PACK_Open( &rtp->hPack, &rtp->packCfg ); + if ( error == IVAS_ERR_OK ) + { + /* Open the output file for RTPDump writing */ + error = IvasRtpFile_Open( outputBitstreamFilename, true, &rtp->hRtpFile ); + if ( error != IVAS_ERR_OK ) + { + return error; + } + + error = IVAS_RTP_PACK_UpdateHeader( rtp->hPack, SSRC, 0, NULL, 0, 0, NULL ); + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_PACK_UpdateHeader(): %d\n", error ); + } + } + + return error; +} + +ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ) +{ + ivas_error error = IVAS_ERR_OK; + + memset( rtp, 0, sizeof( IVAS_RTP ) ); + + rtp->unpackCfg.maxFramesPerPacket = IVAS_MAX_FRAMES_PER_RTP_PACKET; + rtp->rtpPacket.buffer = rtp->packet; + rtp->rtpPacket.capacity = sizeof( rtp->packet ); + + error = IVAS_RTP_UNPACK_Open( &rtp->hUnpack, &rtp->unpackCfg ); + if ( error == IVAS_ERR_OK ) + { + error = IvasRtpFile_Open( inputBitstreamFilename, false, &rtp->hRtpFile ); + if ( error != IVAS_ERR_OK ) + { + return error; + } + + if ( piOutputFilename != NULL ) + { + rtp->f_piDataOut = fopen( piOutputFilename, "w" ); + if ( rtp->f_piDataOut == NULL ) + { + fprintf( stderr, "could not open: %s\n", piOutputFilename ); + return IVAS_ERR_FAILED_FILE_OPEN; + } + fprintf( rtp->f_piDataOut, "{\n" ); + } + } + + return error; +} + +ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ) +{ + ivas_error error = IVAS_ERR_OK; + uint32_t nProcPiData = 0; + IVAS_DATA_BUFFER packedFrame = { 0, 0, NULL }; + + packedFrame.capacity = ( auSizeBits + 7 ) / 8; + packedFrame.length = ( auSizeBits + 7 ) / 8; + packedFrame.buffer = (uint8_t *) au; + rtp->rtpPacket.length = 0; + + /* Push Encoded Stream to */ + error = IVAS_RTP_PACK_PushFrame( rtp->hPack, + isMono ? IVAS_RTP_EVS : IVAS_RTP_IVAS, +#ifdef RTP_S4_251135_CR26253_0016_REV1 + NULL, +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + &packedFrame ); + if ( error != IVAS_ERR_OK ) + { + return error; + } + + while ( rtp->nWrittenPiData-- > 0 ) + { + PIDATA_TS *piDataTs = &rtp->piData[nProcPiData++]; + if ( ( error = IVAS_RTP_PACK_PushPiData( rtp->hPack, (const IVAS_PIDATA_GENERIC *) &piDataTs->data ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing scene orientation\n", ivas_error_to_string( error ) ); + return error; + } + } + + if ( forcePacket || IVAS_RTP_PACK_GetNumFrames( rtp->hPack ) == rtp->packCfg.maxFramesPerPacket ) + { + uint32_t numFramesInPayload = 0; + + /* Generate RTP Packet */ + if ( ( error = IVAS_RTP_PACK_GetPacket( rtp->hPack, &rtp->rtpPacket, &numFramesInPayload ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while packing RTP Header\n", ivas_error_to_string( error ) ); + return error; + } + + if ( ( error = IvasRtpFile_Write( rtp->hRtpFile, rtp->rtpPacket.buffer, rtp->rtpPacket.length ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while writing RTP packet\n", ivas_error_to_string( error ) ); + return error; + } + } + + return error; +} + +ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) +{ + ivas_error error = IVAS_ERR_OK; + IVAS_DATA_BUFFER packedFrame; + IVAS_RTP_CODEC codecId = IVAS_RTP_IVAS; + bool isAMRWB_IOmode = false; + + packedFrame.length = 0; + packedFrame.buffer = au; + packedFrame.capacity = ( IVAS_MAX_BITS_PER_FRAME / 8 ); + if ( rtp->numFramesInPacket == 0 ) + { + rtp->rtpPacket.length = 0; + if ( ( error = IvasRtpFile_Read( rtp->hRtpFile, rtp->rtpPacket.buffer, &rtp->rtpPacket.length, rtp->rtpPacket.capacity ) ) != IVAS_ERR_OK ) + { + return error; + } + + if ( ( error = IVAS_RTP_UNPACK_PushPacket( rtp->hUnpack, &rtp->rtpPacket, + &rtp->numFramesInPacket, &rtp->numPiDataInPacket, + &rtp->remoteRequestBitmap ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to unpack RTP packet error = %s\n", ivas_error_to_string( error ) ); + return error; + } + + rtp->nReadPiData = 0; + rtp->nProcPiData = 0; + + /* Pre-read all PI data */ + while ( rtp->numPiDataInPacket != 0 ) + { + PIDATA_TS *piData = &rtp->piData[rtp->nReadPiData]; + if ( ( error = IVAS_RTP_UNPACK_PullNextPiData( rtp->hUnpack, (IVAS_PIDATA_GENERIC *) &piData->data, sizeof( piData->data ), &piData->timestamp ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to pull PI Data, error = %s\n", ivas_error_to_string( error ) ); + return error; + } + rtp->nReadPiData++; + rtp->numPiDataInPacket--; + } + IVAS_RTP_LogPiData( rtp->f_piDataOut, rtp->piData, rtp->nReadPiData ); + } + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &rtp->srInfo, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); +#else + error = IVAS_RTP_UNPACK_PullFrame( rtp->hUnpack, &codecId, &packedFrame, auSizeBits, rtpTimeStamp, rtpSequenceNumber, &rtp->speechLostIndicated, &isAMRWB_IOmode ); +#endif + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "failed to pull frame after unpack\n" ); + return error; + } + + rtp->restartNeeded = false; + if ( *auSizeBits == 0 ) + { + /* NO_DATA_FRAME/SPEECH_LOST for IVAS and EVS is indicated by same bits + Do not restart decoder on codec/amrwb mode change in this case */ + } + else + { + rtp->restartNeeded = ( rtp->codecId != codecId ) || + ( codecId == IVAS_RTP_EVS && ( rtp->isAMRWB_IOmode != isAMRWB_IOmode ) ); + + if ( rtp->restartNeeded ) + { + fprintf( stdout, "\nRTP packet codec changed %s -> %s\n", + ( rtp->codecId == IVAS_RTP_EVS ) ? ( rtp->isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS", + ( codecId == IVAS_RTP_EVS ) ? ( isAMRWB_IOmode ? "AMRWB_IO" : "EVS" ) : "IVAS" ); + } + + rtp->codecId = codecId; + rtp->isAMRWB_IOmode = isAMRWB_IOmode; + } + + *qBit = !rtp->speechLostIndicated; + rtp->numFramesInPacket--; + rtp->numFramesProduced++; + *nextPacketRcvTime_ms += 20; + + return IVAS_ERR_OK; +} diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 3156c34cb2..5d3e9df1ab 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -37,29 +37,46 @@ #include #include "common_api_types.h" +#include "ivas_rtp_api.h" +#include "ivas_rtp_pi_data.h" + typedef struct IVAS_RTP_FILE *IVAS_RTP_FILE_HANDLE; -ivas_error IvasRtpFile_Open( - const char *filePath, /* i : path to CA config file */ - bool isFileWriter, /* i : instance is a file writer else reader */ - IVAS_RTP_FILE_HANDLE *phRtpFile /* o : pointer to an IVAS file reader handle */ -); - -ivas_error IvasRtpFile_Close( - IVAS_RTP_FILE_HANDLE *phReader /* i : pointer to an IVAS file reader handle */ -); - -ivas_error IvasRtpFile_Write( - IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ - const uint8_t *packet, /* i : pointer to packed rtp packet */ - size_t numBytes /* i : length of the packet in bytes */ -); - -ivas_error IvasRtpFile_Read( - IVAS_RTP_FILE_HANDLE hReader, /* i : pointer to an IVAS file writer handle */ - uint8_t *packet, /* o : buffer where packet is to be written */ - size_t *numBytes, /* o : length of the packet in bytes */ - size_t capacity /* i : buffer capacity of the packet buffer */ -); +typedef struct +{ + uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; + PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + + IVAS_RTP_FILE_HANDLE hRtpFile; + FILE *f_piDataOut; + IVAS_RTP_CODEC codecId; + uint32_t nWrittenPiData; + uint32_t nReadPiData; + uint32_t nProcPiData; + uint32_t numFramesInPacket; + uint32_t numPiDataInPacket; + uint32_t remoteRequestBitmap; + bool speechLostIndicated; + bool restartNeeded; + bool isAMRWB_IOmode; + size_t numFramesProduced; + + IVAS_DATA_BUFFER rtpPacket; + IVAS_RTP_PACK_HANDLE hPack; + IVAS_RTP_UNPACK_HANDLE hUnpack; + IVAS_RTP_PACK_CONFIG packCfg; + IVAS_RTP_UNPACK_CONFIG unpackCfg; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO srInfo; +#endif +} IVAS_RTP; + +ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ); +ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ); +void IVAS_RTP_Term( IVAS_RTP *rtp ); +ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ); +ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ); +void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ); + #endif /* IVAS_RTP_FILE_H */ diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index aad50dd6b6..3787861f00 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -318,12 +318,12 @@ extern "C" typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_AUDIO_FOCUS) */ - uint32_t piDataType; /* IVAS_PI_AUDIO_FOCUS_INDCATION or IVAS_PI_AUDIO_FOCUS_REQUEST */ - bool availDirection; /* audio focus contains direction */ - bool availLevel; /* audio focus contains level */ - IVAS_QUATERNION direction; /* direction data expressed as quarternions */ - IVAS_FLVL flvl; /* audio focus level */ + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_FOCUS) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_FOCUS_INDCATION or IVAS_PI_AUDIO_FOCUS_REQUEST */ + bool availDirection; /* audio focus contains direction */ + bool availLevel; /* audio focus contains level */ + IVAS_QUATERNION direction; /* direction data expressed as quarternions */ + IVAS_FLVL flvl; /* audio focus level */ } IVAS_PIDATA_AUDIO_FOCUS; /* Listener position */ @@ -466,6 +466,12 @@ extern "C" IVAS_PIDATA_NO_DATA noPiData; } PIDATA; + typedef struct + { + PIDATA data; + uint32_t timestamp; + } PIDATA_TS; + #endif /* IVAS_RTPDUMP */ #ifdef __cplusplus diff --git a/lib_util/rtpdump.c b/lib_util/rtpdump.c index 7129a514fd..f89b36f82a 100644 --- a/lib_util/rtpdump.c +++ b/lib_util/rtpdump.c @@ -276,34 +276,6 @@ RTPDUMP_OpenForWriting( RTPDUMP_HANDLE *phRTPDUMP, const char *filename ) return RTPDUMP_NO_ERROR; } - -#ifdef IVAS_RTPDUMP -RTPDUMP_ERROR -RTPDUMP_OpenWithFileToWrite( RTPDUMP_HANDLE *phRTPDUMP, FILE *file ) -{ - *phRTPDUMP = (RTPDUMP_HANDLE) calloc( 1, sizeof( struct RTPDUMP ) ); - if ( !phRTPDUMP || !( *phRTPDUMP ) ) - { - return RTPDUMP_MEMORY_ERROR; - } - - /* open file stream */ - ( *phRTPDUMP )->file = file; - if ( ( *phRTPDUMP )->file == NULL ) - { - return RTPDUMP_FILE_NOT_FOUND; - } - - if ( writeHeader( *phRTPDUMP ) != 0 ) - { - return RTPDUMP_INIT_ERROR; - } - - return RTPDUMP_NO_ERROR; -} - - -#endif RTPDUMP_ERROR RTPDUMP_ReadPacket( RTPDUMP_HANDLE hRTPDUMP, RTPDUMP_RTPPACKET *packet, diff --git a/lib_util/rtpdump.h b/lib_util/rtpdump.h index 77e284eb84..5b8b31e8de 100644 --- a/lib_util/rtpdump.h +++ b/lib_util/rtpdump.h @@ -37,7 +37,6 @@ #pragma once #include #include -#include "options.h" #ifdef __cplusplus extern "C" @@ -84,11 +83,6 @@ extern "C" RTPDUMP_ERROR RTPDUMP_OpenForWriting( RTPDUMP_HANDLE *phRTPDUMP, const char *filename ); -#ifdef IVAS_RTPDUMP - RTPDUMP_ERROR - RTPDUMP_OpenWithFileToWrite( RTPDUMP_HANDLE *phRTPDUMP, FILE *file ); - -#endif RTPDUMP_ERROR RTPDUMP_ReadPacket( RTPDUMP_HANDLE hRTPDUMP, RTPDUMP_RTPPACKET *packet, diff --git a/readme.txt b/readme.txt index 9bd1c4d6fb..54b602c995 100644 --- a/readme.txt +++ b/readme.txt @@ -249,10 +249,10 @@ EVS mono is default, for IVAS choose one of the following: -stereo, -ism, -sba, -level level : Complexity level, level = (1, 2, 3), will be defined after characterisation. Currently, all values default to level 3 (full functionality). -q : Quiet mode, limit printouts to terminal, default is deactivated --rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the +-rtpdump : RTPDump output, hf_only=1 by default. The encoder will packetize the bitstream frames into TS26.253 Annex A IVAS RTP Payload Format packets and writes those to the output file. In EVS mono operating mode, TS26.445 Annex A.2.2 - EVS RTP Payload Format is used. + EVS RTP Payload Format is used. Optional N represents number of frames per RTP packet -scene_orientation : Scene orientation trajectory file. Only used with rtpdump output. -device_orientation : Device orientation trajectory file. Only used with rtpdump output. diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index 1eec45fdce..8d979a7e6d 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -616,7 +616,8 @@ def packAudioFocus(bitstrm: BitStream, data: any): if auFocus.direction is not None: packOrientations(bitstrm, [auFocus.direction]) if auFocus.level is not None: - bitstrm.append(f'uint:4={auFocus.level}') + level = int(auFocus.level) + bitstrm.append(f'uint:4={level}') bitstrm.append(f'uint:4=0') -- GitLab From 0527617eecee4d681ced10d2868c512eb1c786b2 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Thu, 2 Oct 2025 15:29:21 +0300 Subject: [PATCH 190/340] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Archit Tamarapu --- tests/renderer/test_renderer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/renderer/test_renderer.py b/tests/renderer/test_renderer.py index 392ee07c2a..1f09782367 100644 --- a/tests/renderer/test_renderer.py +++ b/tests/renderer/test_renderer.py @@ -641,8 +641,6 @@ def test_masa_prerend( get_odg_bin, split_comparison, ): - if fs != "48kHz": - pytest.skip("MASA Prerendering at 16 and 32 kHz WIP") run_renderer( record_property, props_to_record, -- GitLab From 6421effed9a2bc982582086b4122f03ef161d282 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 2 Oct 2025 16:26:06 +0300 Subject: [PATCH 191/340] Remove PI appliance --- apps/decoder.c | 30 ------ lib_com/ivas_prot.h | 12 --- lib_dec/ivas_binRenderer_internal.c | 4 - lib_dec/ivas_dirac_dec.c | 4 - lib_dec/ivas_init_dec.c | 4 - lib_dec/ivas_mc_param_dec.c | 4 - lib_dec/ivas_mc_paramupmix_dec.c | 4 - lib_dec/ivas_mct_dec.c | 4 - lib_dec/ivas_output_config.c | 20 ---- lib_dec/lib_dec.c | 140 ---------------------------- lib_dec/lib_dec.h | 4 - lib_rend/ivas_prot_rend.h | 8 -- lib_rend/ivas_rotation.c | 102 -------------------- lib_rend/ivas_stat_rend.h | 4 - lib_util/evs_rtp_payload.c | 4 +- lib_util/evs_rtp_payload.h | 2 +- 16 files changed, 2 insertions(+), 348 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 0f5d0dd9a8..07ee7ce18d 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -130,7 +130,6 @@ typedef struct bool tsmEnabled; IVAS_RENDER_FRAMESIZE renderFramesize; #ifdef IVAS_RTPDUMP - bool applyPiData; char *piOutputFilename; #endif #ifdef DEBUGGING @@ -1100,7 +1099,6 @@ static bool parseCmdlIVAS_dec( arg->enableExternalOrientation = false; arg->externalOrientationTrajFileName = NULL; #ifdef IVAS_RTPDUMP - arg->applyPiData = false; arg->piOutputFilename = NULL; #endif @@ -1202,11 +1200,6 @@ static bool parseCmdlIVAS_dec( arg->piOutputFilename = argv[i++]; } - else if ( strcmp( argv_to_upper, "-APPLYPIDATA" ) == 0 ) - { - arg->applyPiData = true; - i++; - } #endif #ifdef SUPPORT_JBM_TRACEFILE else if ( strcmp( argv_to_upper, "-TRACEFILE" ) == 0 ) @@ -1810,7 +1803,6 @@ static void usage_dec( void ) fprintf( stdout, " IVAS RTP Payload Format. The SDP parameter hf_only is required.\n" ); fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); fprintf( stdout, "-PiDataFile PF Log the timestampped PI data.\n" ); - fprintf( stdout, "-ApplyPiData Apply the PI data found in the rtp packet.\n" ); #else fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump\n" ); fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); @@ -3583,28 +3575,6 @@ static ivas_error decodeVoIP( /* decode and get samples */ while ( nSamplesRendered < nOutSamples ) { -#ifdef IVAS_RTPDUMP - if ( arg.applyPiData ) - { - /* Rudimentry Time Mapping to map system time to rtp timestamp */ - uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; - uint32_t numPiData = 0; - - while ( nProcPiData + numPiData < ivasRtp.nReadPiData && - ivasRtp.piData[nProcPiData + numPiData].timestamp <= piTs ) - { - numPiData++; - } - - if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[nProcPiData], numPiData ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - nProcPiData += numPiData; - } -#endif #ifdef SUPPORT_JBM_TRACEFILE if ( ( error = IVAS_DEC_VoIP_GetSamples( hIvasDec, nOutSamples, IVAS_DEC_PCM_INT16, (void *) pcmBuf, writeJbmTraceFileFrameWrapper, jbmTraceWriter, &bitstreamReadDone, &nSamplesRendered, ¶metersAvailableForEditing, systemTime_ms ) ) != IVAS_ERR_OK ) #else diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index ad1b34d2e2..895cb3ac07 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -806,19 +806,7 @@ void ivas_apply_non_diegetic_panning( const int16_t output_frame /* i : output frame length per channel */ ); -#ifdef IVAS_RTPDUMP -void QuaternionProduct( - const IVAS_QUATERNION q1, - const IVAS_QUATERNION q2, - IVAS_QUATERNION *const r -); - -void QuaternionInverse( - const IVAS_QUATERNION q, - IVAS_QUATERNION *const r -); -#endif /*----------------------------------------------------------------------------------* * JBM prototypes *----------------------------------------------------------------------------------*/ diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index 426d8e9a47..8cda55017c 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -1030,11 +1030,7 @@ ivas_error ivas_binRenderer_open( ivas_dirac_dec_get_response( (int16_t) ls_azimuth_CICP19[k], (int16_t) ls_elevation_CICP19[k], hBinRenderer->hReverb->foa_enc[k], 1 ); } } -#ifdef IVAS_RTPDUMP - else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) -#else else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) -#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index cdc22f80f6..88141656c8 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2072,11 +2072,7 @@ void ivas_dirac_dec_render_sf( { mvs2s( &hSpatParamRendCom->azimuth[md_idx][hDirAC->hConfig->enc_param_start_band], &azimuth[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); mvs2s( &hSpatParamRendCom->elevation[md_idx][hDirAC->hConfig->enc_param_start_band], &elevation[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); -#ifdef IVAS_RTPDUMP - if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) -#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) -#endif { num_freq_bands = hDirAC->band_grouping[hDirAC->hConfig->enc_param_start_band]; rotateAziEle_DirAC( azimuth, elevation, num_freq_bands, hSpatParamRendCom->num_freq_bands, p_Rmat ); diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 400243e877..2c2f814ab8 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -2145,11 +2145,7 @@ ivas_error ivas_init_decoder( } else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV || st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM ) { -#ifdef IVAS_RTPDUMP - if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) -#else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) -#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index b43af8b70b..ed7c9fb0b4 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -401,11 +401,7 @@ ivas_error ivas_param_mc_dec_open( ivas_param_mc_dec_compute_interpolator( 0, 0, DEFAULT_JBM_CLDFB_TIMESLOTS, hParamMC->h_output_synthesis_params.interpolator ); /* Head or external rotation */ -#ifdef IVAS_RTPDUMP - if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) -#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) -#endif { if ( ( hParamMC->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index 8df57ff8eb..5753d262a0 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -349,11 +349,7 @@ ivas_error ivas_mc_paramupmix_dec_open( } /* Head or external rotation */ -#ifdef IVAS_RTPDUMP - if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) -#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) -#endif { if ( ( hMCParamUpmix->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mct_dec.c b/lib_dec/ivas_mct_dec.c index 3a0ba42df4..1032d9d389 100644 --- a/lib_dec/ivas_mct_dec.c +++ b/lib_dec/ivas_mct_dec.c @@ -1143,11 +1143,7 @@ static ivas_error ivas_mc_dec_reconfig( if ( st_ivas->hBinRenderer != NULL && ( st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV_ROOM ) ) { ivas_binRenderer_close( &st_ivas->hBinRenderer ); -#ifdef IVAS_RTPDUMP - if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) -#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) -#endif { efap_free_data( &st_ivas->hEFAPdata ); } diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index febcf78d15..d8d6c93608 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -70,11 +70,7 @@ void ivas_renderer_select( /*-----------------------------------------------------------------* * Binaural rendering configurations *-----------------------------------------------------------------*/ -#ifdef IVAS_RTPDUMP - if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) -#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) -#endif { st_ivas->hCombinedOrientationData->shd_rot_max_order = -1; } @@ -158,11 +154,7 @@ void ivas_renderer_select( *internal_config = IVAS_AUDIO_CONFIG_7_1_4; } -#ifdef IVAS_RTPDUMP - if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) -#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) -#endif { nchan_internal = ivas_sba_get_nchan_metadata( st_ivas->sba_analysis_order, st_ivas->hDecoderConfig->ivas_total_brate ); @@ -206,17 +198,9 @@ void ivas_renderer_select( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) { #ifdef DEBUGGING -#ifdef IVAS_RTPDUMP - if ( ( ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) || ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) ) && st_ivas->mc_mode == MC_MODE_MCT && st_ivas->hDecoderConfig->force_rend != FORCE_CLDFB_RENDERER ) -#else if ( ( ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) || ( st_ivas->hDecoderConfig->force_rend == FORCE_TD_RENDERER ) ) && st_ivas->mc_mode == MC_MODE_MCT && st_ivas->hDecoderConfig->force_rend != FORCE_CLDFB_RENDERER ) -#endif -#else -#ifdef IVAS_RTPDUMP - if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->mc_mode == MC_MODE_MCT ) #else if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->mc_mode == MC_MODE_MCT ) -#endif #endif { *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; @@ -232,11 +216,7 @@ void ivas_renderer_select( *renderer_type = RENDERER_BINAURAL_FASTCONV; } -#ifdef IVAS_RTPDUMP - if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) -#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) -#endif { /* force HOA3 domain for rotation*/ *internal_config = IVAS_AUDIO_CONFIG_HOA3; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 5c1edb06fc..7143c2f652 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5032,143 +5032,3 @@ ivas_error IVAS_DEC_is_split_rendering_coded_out( return IVAS_ERR_OK; } - -#ifdef IVAS_RTPDUMP -#ifdef DEBUGGING -#define DEBUG_PRINT fprintf -#else -#define DEBUG_PRINT( ... ) -#endif - -/*---------------------------------------------------------------------* - * IVAS_DEC_feedSinglePIorientation( ) - * - * Feed a single orientation PI data to external orientation handle. - *---------------------------------------------------------------------*/ - -static ivas_error IVAS_DEC_feedSinglePIorientation( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ - IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ -) -{ - int16_t i; - ivas_error error = IVAS_ERR_OK; - IVAS_QUATERNION savedInvOrientation; - - if ( isOrientationSaved ) - { - if ( !hIvasDec->st_ivas->hExtOrientationData ) - { - if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) - { - return error; - } - } - - if ( !hIvasDec->st_ivas->hCombinedOrientationData ) - { - if ( ( error = ivas_combined_orientation_open( &( hIvasDec->st_ivas->hCombinedOrientationData ), hIvasDec->st_ivas->hDecoderConfig->output_Fs, hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) - { - return error; - } - } - - QuaternionInverse( *savedOrientation, &savedInvOrientation ); - - /* use the new PI orientation or the previously saved orientation in processing */ - for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) - { - QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], savedInvOrientation, - &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); - hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; - } - hIvasDec->updateOrientation = true; - } - return error; -} - -/*---------------------------------------------------------------------* - * IVAS_DEC_resetExternalOrientations( ) - * - * Reset external orientations - *---------------------------------------------------------------------*/ - -void IVAS_DEC_resetExternalOrientations( - IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ -) -{ - ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); -} - -#ifdef RTP_S4_251135_CR26253_0016_REV1 -/*---------------------------------------------------------------------* - * IVAS_DEC_setDiegeticInput( ) - * - * Set isDiegeticInput flag for combined orientation handle based on PI data. - *---------------------------------------------------------------------*/ - -static void IVAS_DEC_setDiegeticInputPI( - IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ - const bool *diegeticPIValues /* i : diegetic values for the input stream */ -) -{ - if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) - { - int8_t i; - for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) - { - hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; - } - hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPISet = true; - } -} -#endif - -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) -{ - ivas_error error = IVAS_ERR_OK; - while ( numPiData-- ) - { - uint32_t piDataType = piData->data.noPiData.piDataType; - switch ( piDataType ) - { - case IVAS_PI_SCENE_ORIENTATION: - { - IVAS_QUATERNION *quat = &piData->data.scene.orientation; - DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - } - break; - - case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: - { - IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; - DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - } - break; - -#ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_DIEGETIC_TYPE: - { - DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); - IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); - } - break; -#endif - - default: - { - /* NOT HANDLED PI DATA - DO NOTHING */ - } - break; - } - if ( error != IVAS_ERR_OK ) - { - return error; - } - } - return error; -} -#endif \ No newline at end of file diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 242b601942..dac3f43a77 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -553,10 +553,6 @@ void IVAS_DEC_PrintDisclaimer( void ); -#ifdef IVAS_RTPDUMP -#include "ivas_rtp_pi_data.h" -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); -#endif /* clang-format on */ diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 937923c338..32c2992c1a 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1305,12 +1305,6 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ); -#ifdef IVAS_RTPDUMP -void ivas_external_orientation_reset( - EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ -); - -#endif void ivas_external_orientation_close( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o: external orientation handle */ ); @@ -1358,7 +1352,6 @@ ivas_error ivas_render_config_init_from_rom( * Quaternion operations *----------------------------------------------------------------------------------*/ -#ifndef IVAS_RTPDUMP void QuaternionProduct( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, @@ -1370,7 +1363,6 @@ void QuaternionInverse( IVAS_QUATERNION *const r ); -#endif void QuaternionSlerp( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index 6c6acb2653..9385bc753e 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -693,7 +693,6 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ) { -#ifndef IVAS_RTPDUMP int16_t i; IVAS_QUATERNION identity; @@ -701,16 +700,12 @@ ivas_error ivas_external_orientation_open( identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; -#endif /* Allocate handle */ if ( ( *hExtOrientationData = (EXTERNAL_ORIENTATION_HANDLE) malloc( sizeof( EXTERNAL_ORIENTATION_DATA ) ) ) == NULL ) { return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for external orientation memory\n" ) ); } ( *hExtOrientationData )->num_subframes = num_subframes; -#ifdef IVAS_RTPDUMP - ivas_external_orientation_reset( hExtOrientationData ); -#else /* Enable head rotation and disable external orientation as default */ for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) { @@ -720,45 +715,10 @@ ivas_error ivas_external_orientation_open( ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; ( *hExtOrientationData )->Quaternions[i] = identity; } -#endif return IVAS_ERR_OK; } -#ifdef IVAS_RTPDUMP -/*-----------------------------------------------------------------------* - * ivas_external_orientation_reset() - * - * Reset external orientation values - *-----------------------------------------------------------------------*/ - -void ivas_external_orientation_reset( - EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ -) -{ - if ( *hExtOrientationData != NULL ) - { - int16_t i; - IVAS_QUATERNION identity; - - identity.w = 1.0f; - identity.x = 0.0f; - identity.y = 0.0f; - identity.z = 0.0f; - /* Enable head rotation and disable external orientation as default */ - for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) - { - ( *hExtOrientationData )->enableHeadRotation[i] = 1; - ( *hExtOrientationData )->enableExternalOrientation[i] = 0; - ( *hExtOrientationData )->enableRotationInterpolation[i] = 0; - ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; - ( *hExtOrientationData )->Quaternions[i] = identity; - } - } -} - - -#endif /*-----------------------------------------------------------------------* * ivas_external_orientation_close() * @@ -862,14 +822,6 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->subframe_idx = 0; ( *hCombinedOrientationData )->subframe_size = (int16_t) ( fs / ( FRAMES_PER_SEC * MAX_PARAM_SPATIAL_SUBFRAMES ) ); ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - - for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) - { - ( *hCombinedOrientationData )->isDiegeticInputPI[i] = true; - } - ( *hCombinedOrientationData )->isDiegeticInputPISet = false; -#endif return IVAS_ERR_OK; } @@ -1030,30 +982,11 @@ ivas_error combine_external_and_head_orientations( } else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { -#ifdef RTP_S4_251135_CR26253_0016_REV1 - /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ - if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) - { - for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) - { - hCombinedOrientationData->Quaternions[i] = identity; - } - } - else - { - /* Head rotation only */ - for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) - { - hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; - } - } -#else /* Head rotation only */ for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; } -#endif } if ( hExtOrientationData != NULL ) @@ -1130,40 +1063,6 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternion_frozen_head = identity; hCombinedOrientationData->isHeadRotationFrozen = 0; } -#ifdef RTP_S4_251135_CR26253_0016_REV1 - /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ - if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) - { - continue; - } - else - { - /* Use the most recent head rotation */ - if ( hExtOrientationData->enableHeadRotation[i] == 1 ) - { - if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) - { - QuaternionProduct( hCombinedOrientationData->Quaternions[i], headRotQuaternions[i], &hCombinedOrientationData->Quaternions[i] ); - } - else - { - hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; - } - } - /* Use the freezed head rotation */ - else if ( hExtOrientationData->enableHeadRotation[i] == 2 ) - { - if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) - { - QuaternionProduct( hCombinedOrientationData->Quaternions[i], hCombinedOrientationData->Quaternion_frozen_head, &hCombinedOrientationData->Quaternions[i] ); - } - else - { - hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; - } - } - } -#else /* Use the most recent head rotation */ if ( hExtOrientationData->enableHeadRotation[i] == 1 ) { @@ -1188,7 +1087,6 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; } } -#endif /* Reset the combined orientations to identity */ if ( hExtOrientationData->enableHeadRotation[i] == 0 && hExtOrientationData->enableExternalOrientation[i] == 0 ) diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index 3e6270e229..69fcb30fb8 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -710,10 +710,6 @@ typedef struct ivas_combined_orientation_struct int16_t cur_subframe_samples_rendered; int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - bool isDiegeticInputPI[1 + IVAS_MAX_NUM_OBJECTS]; - bool isDiegeticInputPISet; -#endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; /*----------------------------------------------------------------------------------* diff --git a/lib_util/evs_rtp_payload.c b/lib_util/evs_rtp_payload.c index 7fc0914892..809b6cc6f3 100644 --- a/lib_util/evs_rtp_payload.c +++ b/lib_util/evs_rtp_payload.c @@ -127,7 +127,7 @@ bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBy #endif #ifdef IVAS_RTPDUMP -void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, bool *isGoodFrame, int32_t *bitrate ) +void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) { bool evsModeBit = ( toc & 0x20 ) != 0; *isAMRWB_IOmode = evsModeBit; @@ -136,13 +136,11 @@ void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *fra if ( *isAMRWB_IOmode ) { *qBit = ( toc & 0x10 ) != 0; - *isGoodFrame = *qBit; *bitrate = AMRWB_IOmode2rate[*frameTypeIndex]; } else { *qBit = false; /* Q-bit is unused in EVS Primary mode */ - *isGoodFrame = true; /* assume good frame for EVS Primary */ *bitrate = PRIMARYmode2rate[*frameTypeIndex]; } } diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index aea968ddff..2905d717ef 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -163,7 +163,7 @@ extern "C" #endif #ifdef IVAS_RTPDUMP - void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, bool *isGoodFrame, int32_t *bitrate ); + void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #else static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); #endif -- GitLab From 5de2f27ba7ac5749f94dd3c64b083568d6bdc362 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 2 Oct 2025 16:33:08 +0300 Subject: [PATCH 192/340] Clean up --- lib_dec/ivas_mct_dec.c | 1 + lib_dec/ivas_output_config.c | 1 + lib_dec/lib_dec.h | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib_dec/ivas_mct_dec.c b/lib_dec/ivas_mct_dec.c index 1032d9d389..eaa094b68b 100644 --- a/lib_dec/ivas_mct_dec.c +++ b/lib_dec/ivas_mct_dec.c @@ -1143,6 +1143,7 @@ static ivas_error ivas_mc_dec_reconfig( if ( st_ivas->hBinRenderer != NULL && ( st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV && st_ivas->renderer_type != RENDERER_BINAURAL_FASTCONV_ROOM ) ) { ivas_binRenderer_close( &st_ivas->hBinRenderer ); + if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) { efap_free_data( &st_ivas->hEFAPdata ); diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index d8d6c93608..b0acefc17e 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -70,6 +70,7 @@ void ivas_renderer_select( /*-----------------------------------------------------------------* * Binaural rendering configurations *-----------------------------------------------------------------*/ + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) { st_ivas->hCombinedOrientationData->shd_rot_max_order = -1; diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index dac3f43a77..f28279d86b 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -553,7 +553,6 @@ void IVAS_DEC_PrintDisclaimer( void ); - /* clang-format on */ #endif -- GitLab From a630e5065803614bdfb17a890b1a2e1fb0fbff26 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Fri, 3 Oct 2025 00:33:24 +1000 Subject: [PATCH 193/340] Undo CMAKE and warning fixes Signed-off-by: Ripinder Singh --- CMakeLists.txt | 2 +- apps/decoder.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dfb8a894c..de4727bec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,7 @@ file(GLOB libDecSrcs "lib_dec/*.c") file(GLOB libDecHeaders "lib_dec/*.h") add_library(lib_dec ${libDecSrcs} ${libDecHeaders}) target_link_libraries(lib_dec lib_com lib_rend lib_debug lib_isar) -target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar lib_util) +target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar) file(GLOB libUtilSrcs "lib_util/*.c") file(GLOB libUtilHeaders "lib_util/*.h") diff --git a/apps/decoder.c b/apps/decoder.c index 07ee7ce18d..07b11d4f47 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3205,8 +3205,6 @@ static ivas_error decodeVoIP( #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; IVAS_DEC_HANDLE hIvasDec = *phIvasDec; - int32_t initialTsOffsetSystemAndRTP = 0; - uint32_t nProcPiData = 0; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; @@ -3326,7 +3324,6 @@ static ivas_error decodeVoIP( auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); - initialTsOffsetSystemAndRTP = rtpTimeStamp - systemTime_ms * 16; /* For time mapping */ #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif -- GitLab From aafe02741207a80467472286ac7e3f28377e3f88 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Fri, 3 Oct 2025 11:00:18 +1000 Subject: [PATCH 194/340] Fix for build errors Signed-off-by: Ripinder Singh --- lib_dec/lib_dec.c | 2 +- lib_util/evs_rtp_payload.c | 26 +------------------------- lib_util/evs_rtp_payload.h | 10 +--------- 3 files changed, 3 insertions(+), 35 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 2ce919a2c8..87d4d06d65 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -346,11 +346,11 @@ void IVAS_DEC_Close( ( *phIvasDec )->hVoIP = NULL; } + /* destroy Split binaural renderer (ISAR) handle */ #ifndef IVAS_RTPDUMP ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); #endif - /* destroy Split binaural renderer (ISAR) handle */ if ( ( *phIvasDec )->st_ivas ) { #ifdef IVAS_RTPDUMP diff --git a/lib_util/evs_rtp_payload.c b/lib_util/evs_rtp_payload.c index 292da1c478..a0c514bad3 100644 --- a/lib_util/evs_rtp_payload.c +++ b/lib_util/evs_rtp_payload.c @@ -37,9 +37,7 @@ #include #include #include "evs_rtp_payload.h" -#include "options.h" -#ifndef IVAS_RTPDUMP static void evsPayload_unpackFrame_compact_amrWbIo( const char *payload, uint16_t payloadSizeBits, uint16_t iProtectedSize, unsigned char **framePtr, uint16_t *frameSizeInBits ) { uint16_t i, iBit0; @@ -124,27 +122,7 @@ bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBy return evsHeaderFullPayload_unpackFrame( payload, payloadSizeBytes, frameIndex, isAMRWB_IOmode, frameFollowing, frameTypeIndex, qBit, framePtr, frameSizeInBits ); } -#endif -#ifdef IVAS_RTPDUMP -void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) -{ - bool evsModeBit = ( toc & 0x20 ) != 0; - *isAMRWB_IOmode = evsModeBit; - *frameFollowing = ( toc & 0x40 ) != 0; - *frameTypeIndex = toc & 0x0f; - if ( *isAMRWB_IOmode ) - { - *qBit = ( toc & 0x10 ) != 0; - *bitrate = AMRWB_IOmode2rate[*frameTypeIndex]; - } - else - { - *qBit = false; /* Q-bit is unused in EVS Primary mode */ - *bitrate = PRIMARYmode2rate[*frameTypeIndex]; - } -} -#else static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ) { bool evsModeBit = ( toc & 0x20 ) != 0; @@ -162,9 +140,8 @@ static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bo *bitrate = AMRWB_IOmode2rate[*frameTypeIndex]; } } -#endif -#ifndef IVAS_RTPDUMP + bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeInBits ) { bool someIsAMRWB_IOmode, someFrameFollowing = true, someQBit; @@ -296,4 +273,3 @@ void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ) } RTPDUMP_Close( &self->rtpdump, 0 ); } -#endif diff --git a/lib_util/evs_rtp_payload.h b/lib_util/evs_rtp_payload.h index 2905d717ef..357d985578 100644 --- a/lib_util/evs_rtp_payload.h +++ b/lib_util/evs_rtp_payload.h @@ -83,7 +83,6 @@ extern "C" 0 /* NO_DATA */ }; -#ifndef IVAS_RTPDUMP static const uint16_t evsPayloadProtectedSizes[22] = { 48, 56, @@ -160,14 +159,8 @@ extern "C" }; bool evsPayload_unpackFrame( bool hf_only, char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **framePtr, uint16_t *frameSizeBits ); -#endif -#ifdef IVAS_RTPDUMP - void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); -#else -static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, int32_t *bitrate ); -#endif -#ifndef IVAS_RTPDUMP + bool evsHeaderFullPayload_unpackFrame( char *payload, uint16_t payloadSizeBytes, uint16_t frameIndex, bool *isAMRWB_IOmode, bool *frameFollowing, uint16_t *frameTypeIndex, bool *qBit, unsigned char **frame, uint16_t *frameSizeBits ); typedef struct @@ -202,7 +195,6 @@ static void evsHeaderFullPayload_parseToc( uint8_t toc, bool *isAMRWB_IOmode, bo uint16_t *frameSizeBits ); void EVS_RTPDUMP_DEPACKER_close( EVS_RTPDUMP_DEPACKER *self ); -#endif #ifdef __cplusplus } -- GitLab From e588a2be09f0381ebfd84ba2b13ef2b930f239c1 Mon Sep 17 00:00:00 2001 From: lintervo Date: Fri, 3 Oct 2025 09:34:51 +0300 Subject: [PATCH 195/340] Resolve MR comments --- lib_dec/ivas_jbm_dec.c | 4 +- lib_dec/ivas_omasa_dec.c | 4 +- lib_rend/ivas_dirac_dec_binaural_functions.c | 60 ++++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 23fc1e041e..91522f3f3d 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -2907,9 +2907,9 @@ void ivas_dec_prepare_renderer( /* Gain MASA part, if edited */ if ( st_ivas->hMasaIsmData->masa_gain_is_edited ) { - for ( int16_t ch = 0; ch < 2; ch++ ) + for ( n = 0; n < CPE_CHANNELS; n++ ) { - v_multc( st_ivas->hTcBuffer->tc[ch], st_ivas->hMasaIsmData->gain_masa_edited, st_ivas->hTcBuffer->tc[ch], st_ivas->hTcBuffer->n_samples_available ); + v_multc( st_ivas->hTcBuffer->tc[n], st_ivas->hMasaIsmData->gain_masa_edited, st_ivas->hTcBuffer->tc[n], st_ivas->hTcBuffer->n_samples_available ); } } } diff --git a/lib_dec/ivas_omasa_dec.c b/lib_dec/ivas_omasa_dec.c index 346c6f591d..90b7c4632d 100644 --- a/lib_dec/ivas_omasa_dec.c +++ b/lib_dec/ivas_omasa_dec.c @@ -716,9 +716,9 @@ void ivas_omasa_dirac_rend_jbm( /* Gain MASA part, if edited in G192. MASA gaining with VOIP is done in ivas_dec_prepare_renderer() */ if ( !st_ivas->hDecoderConfig->Opt_tsm && st_ivas->hMasaIsmData->masa_gain_is_edited ) { - for ( int16_t ch = 0; ch < 2; ch++ ) + for ( n = 0; n < CPE_CHANNELS; n++ ) { - v_multc( output_f[ch], st_ivas->hMasaIsmData->gain_masa_edited, output_f[ch], *nSamplesRendered ); + v_multc( output_f[n], st_ivas->hMasaIsmData->gain_masa_edited, output_f[n], *nSamplesRendered ); } } } diff --git a/lib_rend/ivas_dirac_dec_binaural_functions.c b/lib_rend/ivas_dirac_dec_binaural_functions.c index 684180e239..6a162142e8 100644 --- a/lib_rend/ivas_dirac_dec_binaural_functions.c +++ b/lib_rend/ivas_dirac_dec_binaural_functions.c @@ -2766,7 +2766,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* MASA gaining */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { if ( masaGainEdited ) { @@ -2794,7 +2794,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( nSlotDiv = 1.0f / ( (float) nSlots ); /* Use diagonal mixing matrix as the instant mixing matrix, to slowly fade away the editing during dtx */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { ismPreprocMtxNew[ch][ch] = 1.0f; ismPreprocMtxNew[1 - ch][ch] = 0.0f; @@ -2808,12 +2808,12 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* Init out array */ - for ( int k = 0; k < nSlots; k++ ) + for ( slot = 0; slot < nSlots; slot++ ) { for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { - set_zero( outSlotRe[ch][k], CLDFB_NO_CHANNELS_MAX ); - set_zero( outSlotIm[ch][k], CLDFB_NO_CHANNELS_MAX ); + set_zero( outSlotRe[ch][slot], CLDFB_NO_CHANNELS_MAX ); + set_zero( outSlotIm[ch][slot], CLDFB_NO_CHANNELS_MAX ); } } @@ -2827,7 +2827,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( totalTargetEne = 0.0f; for ( slot = 0; slot < nSlots; slot++ ) { - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { for ( bin = bin_lo; bin < bin_hi; bin++ ) { @@ -2838,9 +2838,9 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* Get increment value for temporal interpolation */ - for ( inCh = 0; inCh < 2; inCh++ ) + for ( inCh = 0; inCh < BINAURAL_CHANNELS; inCh++ ) { - for ( outCh = 0; outCh < 2; outCh++ ) + for ( outCh = 0; outCh < BINAURAL_CHANNELS; outCh++ ) { ismPreprocMtxIncrement[outCh][inCh] = ( ismPreprocMtxNew[outCh][inCh] - hMasaIsmData->ismPreprocMatrix[outCh][inCh][band_idx] ) * nSlotDiv; } @@ -2851,11 +2851,11 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( hMasaIsmData->preprocEneRealized[band_idx] *= STEREO_PREPROCESS_IIR_FACTOR; hMasaIsmData->preprocEneTarget[band_idx] += totalTargetEne; - for ( outCh = 0; outCh < 2; outCh++ ) + for ( outCh = 0; outCh < BINAURAL_CHANNELS; outCh++ ) { for ( slot = 0; slot < nSlots; slot++ ) { - for ( inCh = 0; inCh < 2; inCh++ ) + for ( inCh = 0; inCh < BINAURAL_CHANNELS; inCh++ ) { hMasaIsmData->ismPreprocMatrix[outCh][inCh][band_idx] += ismPreprocMtxIncrement[outCh][inCh]; for ( bin = bin_lo; bin < bin_hi; bin++ ) @@ -2874,7 +2874,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( eqVal = fminf( 4.0f, sqrtf( hMasaIsmData->preprocEneTarget[band_idx] / fmaxf( 1e-12f, hMasaIsmData->preprocEneRealized[band_idx] ) ) ); - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { for ( slot = 0; slot < nSlots; slot++ ) { @@ -2950,14 +2950,14 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( else { /* When not edited, input and output pan gains are the same */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { panGainsOut[ismDirIndex][ch] = panGainsIn[ismDirIndex][ch]; } } /* Determine pan enes */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { panEnesOut[ismDirIndex][ch] = panGainsOut[ismDirIndex][ch] * panGainsOut[ismDirIndex][ch]; panEnesIn[ismDirIndex][ch] = panGainsIn[ismDirIndex][ch] * panGainsIn[ismDirIndex][ch]; @@ -2982,12 +2982,12 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* Init out array */ - for ( int k = 0; k < nSlots; k++ ) + for ( slot = 0; slot < nSlots; slot++ ) { for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { - set_zero( outSlotRe[ch][k], CLDFB_NO_CHANNELS_MAX ); - set_zero( outSlotIm[ch][k], CLDFB_NO_CHANNELS_MAX ); + set_zero( outSlotRe[ch][slot], CLDFB_NO_CHANNELS_MAX ); + set_zero( outSlotIm[ch][slot], CLDFB_NO_CHANNELS_MAX ); } } @@ -3017,7 +3017,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( /* Determine transport normalized energies and subframe energy */ for ( slot = 0; slot < nSlots; slot++ ) { - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { for ( bin = bin_lo; bin < bin_hi; bin++ ) { @@ -3040,7 +3040,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( ratioAccOrig += ratio; /* Calculate MASA energy as a residual of original channel energies subtracted with ISM energies */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { masaEneThisCh[ch] -= panEnesIn[ismDirIndex][ch] * ratio * subframeEne; } @@ -3058,7 +3058,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( ratio *= gainIsmThis * gainIsmThis; /* Determine panning energies and channel target energies */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { ismTargetEneThisCh[ch] = panEnesIn[ismDirIndex][ch] * ismTargetEneThis; /* Ism target energy per channel */ totalTargetEneCh[ch] -= panEnesIn[ismDirIndex][ch] * ismEneThis; /* Reduce original ism energy */ @@ -3099,7 +3099,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( ratio *= gainMasaPow2; /* Calculate MASA target energies and add to total target energy estimation */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { masaEneThisCh[ch] = fmaxf( masaEneThisCh[ch], 0.0f ); /* MASA original energy per channel */ masaTargetEneThisCh[ch] = gainMasaPow2 * masaEneThisCh[ch]; /* MASA target energy per channel */ @@ -3113,7 +3113,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( /* Limit target energies to non-negative values */ #ifdef NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { totalTargetEneCh[ch] = max( totalTargetEneCh[ch], 0.0f ); } @@ -3183,14 +3183,14 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( if ( enableCentering ) { centeringFactor = fmaxf( 0.0f, 2.0f * fabsf( panEnesIn[ismDirIndex][0] - panEnesOut[ismDirIndex][0] ) - 1.0f ); - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { panEnesOut[ismDirIndex][ch] *= ( 1.0f - centeringFactor ); panEnesOut[ismDirIndex][ch] += 0.5f * centeringFactor; } } - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { eneMoveThis = fmaxf( 0.0f, panEnesIn[ismDirIndex][ch] - panEnesOut[ismDirIndex][ch] ); enePreserveThis = panEnesIn[ismDirIndex][ch] - eneMoveThis; @@ -3207,7 +3207,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( remainderNormEne = fmaxf( 0.0f, ( 1.0f - ismRatioAcc ) - normEnes[0] - normEnes[1] ); /* Normalize */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { enePreserve[ch] += fmaxf( 0.0f, normEnes[ch] + remainderNormEne / 2.0f ); normVal = 1.0f / fmaxf( EPSILON, eneMove[ch] + enePreserve[ch] ); @@ -3222,7 +3222,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( /* Temporally average energy moving and preserving, and generate the transport signal preprocessing matrix for * gaining objects and moving objects between left and right */ - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { hMasaIsmData->eneMoveIIR[ch][band_idx] *= STEREO_PREPROCESS_IIR_FACTOR; hMasaIsmData->eneMoveIIR[ch][band_idx] += eneMove[ch] * totalTargetEne; @@ -3234,9 +3234,9 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( } /* Get increment value for temporal interpolation */ - for ( inCh = 0; inCh < 2; inCh++ ) + for ( inCh = 0; inCh < BINAURAL_CHANNELS; inCh++ ) { - for ( outCh = 0; outCh < 2; outCh++ ) + for ( outCh = 0; outCh < BINAURAL_CHANNELS; outCh++ ) { ismPreprocMtxIncrement[outCh][inCh] = ( ismPreprocMtxNew[outCh][inCh] - hMasaIsmData->ismPreprocMatrix[outCh][inCh][band_idx] ) * nSlotDiv; } @@ -3247,11 +3247,11 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( hMasaIsmData->preprocEneRealized[band_idx] *= STEREO_PREPROCESS_IIR_FACTOR; hMasaIsmData->preprocEneTarget[band_idx] += totalTargetEne; - for ( outCh = 0; outCh < 2; outCh++ ) + for ( outCh = 0; outCh < BINAURAL_CHANNELS; outCh++ ) { for ( slot = 0; slot < nSlots; slot++ ) { - for ( inCh = 0; inCh < 2; inCh++ ) + for ( inCh = 0; inCh < BINAURAL_CHANNELS; inCh++ ) { hMasaIsmData->ismPreprocMatrix[outCh][inCh][band_idx] += ismPreprocMtxIncrement[outCh][inCh]; for ( bin = bin_lo; bin < bin_hi; bin++ ) @@ -3270,7 +3270,7 @@ void ivas_omasa_preProcessStereoTransportsForEditedObjects( eqVal = fminf( 4.0f, sqrtf( hMasaIsmData->preprocEneTarget[band_idx] / fmaxf( 1e-12f, hMasaIsmData->preprocEneRealized[band_idx] ) ) ); - for ( ch = 0; ch < 2; ch++ ) + for ( ch = 0; ch < BINAURAL_CHANNELS; ch++ ) { for ( slot = 0; slot < nSlots; slot++ ) { -- GitLab From 38f52b41c0e7a1190cdbef74ec4a4009ea0187e9 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Sun, 5 Oct 2025 20:38:44 +0200 Subject: [PATCH 196/340] fix for TNS crash --- lib_com/options.h | 1 + lib_enc/cod_tcx.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index e69dd22ab8..47ad9f8459 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -173,6 +173,7 @@ #define FIX_1383_HEAD_TRACK_SANITIZER /* Nok: issue 1383: Fix head tracking struc values reading in renderer */ #define FIX_1385_INIT_IGF_STOP_FREQ /* FhG: Initialize infoIGFStopFreq in init_igf_dec() */ #define FIX_1387_INIT_PRM_SQQ /* FhG: initialize pointer prm_sqQ, which might be uninitialized in case of bfi == 1 */ +#define FIX_1349_TNS_CRASH /* FhG: Fix crash in TNS entropy coding, in case order of joint TNS coding is reduced to 0 */ /* #################### End BE switches ################################## */ diff --git a/lib_enc/cod_tcx.c b/lib_enc/cod_tcx.c index 89282ed8b4..fb2e7d9098 100644 --- a/lib_enc/cod_tcx.c +++ b/lib_enc/cod_tcx.c @@ -332,6 +332,15 @@ void TNSAnalysisStereo( pFilter[0]->order = pFilter[1]->order = maxOrder; } +#ifdef FIX_1349_TNS_CRASH + else + { + pFilter[0]->filterType = TNS_FILTER_OFF; + pFilter[1]->filterType = TNS_FILTER_OFF; + sts[0]->hTcxEnc->tnsData[k].nFilters = 0; + sts[1]->hTcxEnc->tnsData[k].nFilters = 0; + } +#endif } } } -- GitLab From d46964a3bae8384ed556613a687825dd72382680 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Sun, 5 Oct 2025 20:58:13 +0200 Subject: [PATCH 197/340] formatting --- lib_enc/cod_tcx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_enc/cod_tcx.c b/lib_enc/cod_tcx.c index fb2e7d9098..9a890ae3c9 100644 --- a/lib_enc/cod_tcx.c +++ b/lib_enc/cod_tcx.c @@ -332,7 +332,7 @@ void TNSAnalysisStereo( pFilter[0]->order = pFilter[1]->order = maxOrder; } -#ifdef FIX_1349_TNS_CRASH +#ifdef FIX_1349_TNS_CRASH else { pFilter[0]->filterType = TNS_FILTER_OFF; -- GitLab From 68f57a5dd85747cdb5e88853f50f178a47a6b58e Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Mon, 6 Oct 2025 10:07:36 +0300 Subject: [PATCH 198/340] Add folder for IVAS conformance scripts. --- scripts/ivas_conformance/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 scripts/ivas_conformance/README.md diff --git a/scripts/ivas_conformance/README.md b/scripts/ivas_conformance/README.md new file mode 100644 index 0000000000..43952b6598 --- /dev/null +++ b/scripts/ivas_conformance/README.md @@ -0,0 +1,3 @@ +# IVAS conformance scripts + +This folder contains scripts for running IVAS conformance tests. This is a placeholder file for instructions. -- GitLab From f294a1fcf923682bf145f1bc68e10712a0fd9d73 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 09:39:27 +0200 Subject: [PATCH 199/340] reduce pytest verbosity in conformance test otherwise, too much is being printed and the results are not shown --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e68613b54..91017da125 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1380,10 +1380,10 @@ ivas-conformance-linux: # Reference creation - python3 scripts/prepare_combined_format_inputs.py - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" - - python3 -m pytest $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files + - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files # Output creation - - python3 -m pytest $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html + - python3 -m pytest -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt # Copy input data and output ref data @@ -1431,7 +1431,7 @@ ivas-conformance-linux: # Test run generated scripts in testvec - cd testvec - exit_code=0 - - python3 -m pytest conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? + - python3 -m pytest -q conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? - mv report.html .. - mv report-junit.xml .. -- GitLab From 4a5bdb48afec429d7fc0b39d6d5e25db294faf69 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 6 Oct 2025 11:48:45 +0300 Subject: [PATCH 200/340] Add xfail for rtpdump tests --- tests/codec_be_on_mr_nonselection/test_param_file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/codec_be_on_mr_nonselection/test_param_file.py b/tests/codec_be_on_mr_nonselection/test_param_file.py index d2acacd39e..0e0e082c63 100644 --- a/tests/codec_be_on_mr_nonselection/test_param_file.py +++ b/tests/codec_be_on_mr_nonselection/test_param_file.py @@ -228,6 +228,9 @@ def test_param_file_tests( ): enc_opts, dec_opts, sim_opts, eid_opts = param_file_test_dict[test_tag] + if "rtpdump" in test_tag: + pytest.xfail("Skip RTP tests for now") + run_test( test_info, props_to_record, -- GitLab From a5f96a1cae65296945a89b1a5c90b4ef77cf41e9 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 10:55:08 +0200 Subject: [PATCH 201/340] add pl type "coverage" + trigger conformance test with that --- .gitlab-ci.yml | 9 +++++++-- .gitlab-ci/variables.yml | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 91017da125..2053cf2688 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,6 +48,9 @@ workflow: - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-branch-vs-input-passthrough' variables: IVAS_PIPELINE_NAME: "Pass-through comparison vs input: $CI_COMMIT_BRANCH" + - if: $MANUAL_PIPELINE_TYPE == 'coverage' + variables: + IVAS_PIPELINE_NAME: "Coverage measurement" # --------------------------------------------------------------- # Generic script anchors @@ -1366,6 +1369,8 @@ ivas-conformance-linux: timeout: "90 minutes" rules: - if: ($CI_PIPELINE_SOURCE == 'web' || $CI_PIPELINE_SOURCE == 'trigger') && $MANUAL_PIPELINE_TYPE == 'ivas-conformance-linux' + - if: $MANUAL_PIPELINE_TYPE == "coverage" + - if: $COVERAGE_TEST allow_failure: exit_codes: - 123 @@ -2194,8 +2199,8 @@ sanitizer-test-osba-planar-hoa3-ism4: - ivas-linux-fast stage: test rules: - # only run in scheduled pipeline that passes this env vars - if: $COVERAGE_TEST + - if: $MANUAL_PIPELINE_TYPE == "coverage" timeout: 3 hours before_script: - !reference [.job-linux, before_script] @@ -2232,8 +2237,8 @@ coverage-merge: - ivas-linux-fast stage: test rules: - # only run in scheduled pipeline that passes this env vars - if: $COVERAGE_TEST + - if: $MANUAL_PIPELINE_TYPE == "coverage" needs: - job: coverage-test-on-main-scheduled-stv artifacts: true diff --git a/.gitlab-ci/variables.yml b/.gitlab-ci/variables.yml index 9e60194a6c..52b811a001 100644 --- a/.gitlab-ci/variables.yml +++ b/.gitlab-ci/variables.yml @@ -18,6 +18,7 @@ variables: - 'ivas-conformance-linux' - 'check-clipping' - 'test-branch-vs-input-passthrough' + - 'coverage' PYTEST_ARGS: "" LONG_TEST_SUITE: "tests/codec_be_on_mr_nonselection tests/renderer --param_file scripts/config/self_test_ltv.prm --use_ltv" -- GitLab From bbecfe429b1d1069a94899dbbd702647019af0f8 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 11:02:21 +0200 Subject: [PATCH 202/340] change error tolerance to +/- 1LSB max --- tests/test_be_ambi_converter_fixed_to_float.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 8fdd81e3cc..0d0b1f1ade 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -51,7 +51,7 @@ INPUT_FILES = [TESTV_DIR / f"spectral_test_{ch}ch_48kHz.wav" for ch in INPUT_CH_ CONVENTIONS = [c for c in AMBI_CONVENTION] AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" -THRESHOLD_FAIL = 2 +THRESHOLD_FAIL = 1 CONVENTIONS_FULL_COMBI = list(itertools.product(CONVENTIONS, CONVENTIONS)) CONVENTIONS_TEST_PARAMS = [ -- GitLab From 2d92741efbef349f62ccbd315b6361d99b1f9403 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 11:09:29 +0200 Subject: [PATCH 203/340] add other sampling rates --- tests/test_be_ambi_converter_fixed_to_float.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_be_ambi_converter_fixed_to_float.py b/tests/test_be_ambi_converter_fixed_to_float.py index 0d0b1f1ade..aa4202d3d1 100644 --- a/tests/test_be_ambi_converter_fixed_to_float.py +++ b/tests/test_be_ambi_converter_fixed_to_float.py @@ -47,7 +47,12 @@ def run_ambi_converter( # test all ambisonics orders from 1 to 3 INPUT_CH_NUM = [4, 9, 16] -INPUT_FILES = [TESTV_DIR / f"spectral_test_{ch}ch_48kHz.wav" for ch in INPUT_CH_NUM] +INPUT_FS = [16, 32, 48] +INPUT_FILES = [ + TESTV_DIR / f"spectral_test_{ch}ch_{fs}kHz.wav" + for ch in INPUT_CH_NUM + for fs in INPUT_FS +] CONVENTIONS = [c for c in AMBI_CONVENTION] AMBI_CONVERTER_PATH_FLOAT = HERE.parent / "ambi_converter_flt" AMBI_CONVERTER_PATH_FIXED = HERE.parent / "ambi_converter_fx" -- GitLab From b0e2ba6357c8478edcf8ce780d8150b09ab42e8e Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 12:51:11 +0200 Subject: [PATCH 204/340] use variables for coverage files/dirs + fix pwd stripping --- .gitlab-ci.yml | 44 ++++++++++++++++++++++++---------------- .gitlab-ci/variables.yml | 4 ++++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2053cf2688..f9e6e15c80 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1374,6 +1374,10 @@ ivas-conformance-linux: allow_failure: exit_codes: - 123 + variables: + COVERAGE_OUTPUT_FILE: "$COVERAGE_OUTPUT_FILE_CONFORMANCE" + COVERAGE_OUTPUT_DIR: "coverage_conformance" + COVERAGE_TITLE: "conformance test test_26252.py" script: - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh - make -j @@ -1442,13 +1446,13 @@ ivas-conformance-linux: # Collect coverage - cd - - - lcov -c -d obj -o coverage.info - - lcov -r coverage.info "*apps*" -o coverage.info - - lcov -r coverage.info "*lib_util*" -o coverage.info + - lcov -c -d obj -o $COVERAGE_OUTPUT_FILE + - lcov -r $COVERAGE_OUTPUT_FILE "*apps*" -o $COVERAGE_OUTPUT_FILE + - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - commit_sha=$(git rev-parse HEAD) # replace hardcoded path with relative path to allow easy merging of .info files - - sed -i "s|$(pwd)|..|g" coverage.info - - genhtml coverage.info -o coverage -t "Coverage on main @ $commit_sha" + - sed -i "s|$(pwd)|.|g" coverage.info + - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" # Check for failures - if [ $exit_code -ne 0 ]; then echo "Test failures encountered"; exit $EXIT_CODE_FAIL; fi @@ -1467,8 +1471,8 @@ ivas-conformance-linux: - Readme_IVAS_JBM_dec.txt - Readme_IVAS_ISAR_dec.txt - Readme_IVAS_ISAR_post_rend.txt - - coverage.info - - coverage + - $COVERAGE_OUTPUT_FILE + - $COVERAGE_OUTPUT_DIR expose_as: "Draft IVAS conformance -- Linux" reports: junit: report-junit.xml @@ -2224,7 +2228,7 @@ sanitizer-test-osba-planar-hoa3-ism4: - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - commit_sha=$(git rev-parse HEAD) # replace hardcoded path with relative path to allow easy merging of .info files - - sed -i "s|$(pwd)|..|g" $COVERAGE_OUTPUT_FILE + - sed -i "s|$(pwd)|.|g" $COVERAGE_OUTPUT_FILE - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" artifacts: name: "main-$COVERAGE_OUTPUT_DIR-sha-$CI_COMMIT_SHORT_SHA" @@ -2246,23 +2250,27 @@ coverage-merge: artifacts: true - job: ivas-conformance-linux artifacts: true + variables: + COVERAGE_OUTPUT_FILE: "$COVERAGE_OUTPUT_FILE_MERGED" + COVERAGE_OUTPUT_DIR: "coverage-merged" + COVERAGE_TITLE: "merged total coverage" script: - echo "Merging coverage reports..." - - lcov -a coverage.info -a coverage_stv.info -a coverage_ltv.info -o coverage_merged.info + - lcov -a $COVERAGE_OUTPUT_FILE_CONFORMANCE -a $COVERAGE_OUTPUT_FILE_STV -a $COVERAGE_OUTPUT_FILE_LTV -o "$COVERAGE_OUTPUT_FILE_MERGED" - commit_sha=$(git rev-parse HEAD) - - genhtml coverage_merged.info -o coverage_merged -t "Merged Coverage Report @ $commit_sha" + - genhtml $COVERAGE_OUTPUT_FILE_MERGED -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" artifacts: name: "merged-coverage-sha-$CI_COMMIT_SHORT_SHA" when: always expire_in: 1 week paths: - - coverage_merged.info - - coverage_merged + - $COVERAGE_OUTPUT_FILE + - $COVERAGE_OUTPUT_DIR coverage-test-on-main-scheduled-stv: extends: .coverage-test-template variables: - COVERAGE_OUTPUT_FILE: "coverage_stv.info" + COVERAGE_OUTPUT_FILE: "$COVERAGE_OUTPUT_FILE_STV" COVERAGE_OUTPUT_DIR: "coverage_stv" COVERAGE_TITLE: "short test vectors" script: @@ -2298,13 +2306,13 @@ coverage-test-on-main-scheduled-stv: # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job artifacts: paths: - - coverage_stv.info - - coverage_stv + - $COVERAGE_OUTPUT_FILE + - $COVERAGE_OUTPUT_DIR coverage-test-on-main-scheduled-ltv: extends: .coverage-test-template variables: - COVERAGE_OUTPUT_FILE: "coverage_ltv.info" + COVERAGE_OUTPUT_FILE: "$COVERAGE_OUTPUT_FILE_LTV" COVERAGE_OUTPUT_DIR: "coverage_ltv" COVERAGE_TITLE: "long test vectors" script: @@ -2327,8 +2335,8 @@ coverage-test-on-main-scheduled-ltv: # VoIP BE tests don't support LTV - skipped here artifacts: paths: - - coverage_ltv.info - - coverage_ltv + - $COVERAGE_OUTPUT_FILE + - $COVERAGE_OUTPUT_DIR # --------------------------------------------------------------- # Complexity measurement jobs diff --git a/.gitlab-ci/variables.yml b/.gitlab-ci/variables.yml index 52b811a001..b70de66596 100644 --- a/.gitlab-ci/variables.yml +++ b/.gitlab-ci/variables.yml @@ -29,3 +29,7 @@ variables: TEST_SUITE: "" # note: currently overwrites default value from ci repo TESTCASE_TIMEOUT_STV_SANITIZERS: 240 + COVERAGE_OUTPUT_FILE_STV: "coverage-stv.info" + COVERAGE_OUTPUT_FILE_LTV: "coverage-ltv.info" + COVERAGE_OUTPUT_FILE_CONFORMANCE: "coverage-conformance.info" + COVERAGE_OUTPUT_FILE_MERGED: "coverage-merged.info" -- GitLab From 40a41ba041a2d9941cc83bcb7a748f33da3fdb61 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 13:28:35 +0200 Subject: [PATCH 205/340] fix - use variable also in this spot --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9e6e15c80..5682b3205e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1451,7 +1451,7 @@ ivas-conformance-linux: - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - commit_sha=$(git rev-parse HEAD) # replace hardcoded path with relative path to allow easy merging of .info files - - sed -i "s|$(pwd)|.|g" coverage.info + - sed -i "s|$(pwd)|.|g" $COVERAGE_OUTPUT_FILE - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" # Check for failures -- GitLab From 8345f5f0d2b2fbd41ddca93aa035202b480298bd Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 13:30:47 +0200 Subject: [PATCH 206/340] [revert-me] reduce number of tests for faster iteration --- .gitlab-ci.yml | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5682b3205e..9b5b45dd0d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1389,10 +1389,10 @@ ivas-conformance-linux: # Reference creation - python3 scripts/prepare_combined_format_inputs.py - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" - - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files + - python3 -m pytest -k stereo -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files # Output creation - - python3 -m pytest -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html + - python3 -m pytest -k stereo -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt # Copy input data and output ref data @@ -1440,7 +1440,7 @@ ivas-conformance-linux: # Test run generated scripts in testvec - cd testvec - exit_code=0 - - python3 -m pytest -q conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? + - python3 -m pytest -k stereo -q conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? - mv report.html .. - mv report-junit.xml .. @@ -2276,33 +2276,33 @@ coverage-test-on-main-scheduled-stv: script: # -- Run short test vector suite to check coverage of that first # codec smoke test - - bash ci/smoke_test.sh coverage + # - bash ci/smoke_test.sh coverage # default renderer framesize is 20ms - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only - # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true - # run renderer smoke test - needed for REFVEC tests - - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py - # run renderer test only with REF creation stage - no CUT needed since executables are identical - - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --create_ref - # run split rendering smoke test and REF creation - - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py - - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py --create_ref - # VoIP BE test - - python3 -m pytest -q --tb=no tests/test_be_for_jbm_neutral_dly_profile.py - # split rendering VoIP BE test - - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_voip_be_splitrend_vs_binaural.py - # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted - # - bash ci/run-first-frame-is-sid-test.sh - # TODO consider HRTF binary loading test, or probably already covered by PRM files - # - python3 -m pytest -q --tb=no tests/hrtf_binary_loading - # run BE vs EXT test - - python3 tests/create_short_testvectors.py --cut_len 1.0 - - python3 -m pytest -q --tb=no tests/test_be_for_ext_outputs.py - # run LC3plus wrapper unit test - - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test + - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only + # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true + # # run renderer smoke test - needed for REFVEC tests + # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py + # # run renderer test only with REF creation stage - no CUT needed since executables are identical + # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --create_ref + # # run split rendering smoke test and REF creation + # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py + # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py --create_ref + # # VoIP BE test + # - python3 -m pytest -q --tb=no tests/test_be_for_jbm_neutral_dly_profile.py + # # split rendering VoIP BE test + # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_voip_be_splitrend_vs_binaural.py + # # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted + # # - bash ci/run-first-frame-is-sid-test.sh + # # TODO consider HRTF binary loading test, or probably already covered by PRM files + # # - python3 -m pytest -q --tb=no tests/hrtf_binary_loading + # # run BE vs EXT test + # - python3 tests/create_short_testvectors.py --cut_len 1.0 + # - python3 -m pytest -q --tb=no tests/test_be_for_ext_outputs.py + # # run LC3plus wrapper unit test + # - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job artifacts: paths: @@ -2317,20 +2317,20 @@ coverage-test-on-main-scheduled-ltv: COVERAGE_TITLE: "long test vectors" script: # -- Add extended coverage of EVS test and long test vectors - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # default renderer framesize is 20ms - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only - # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true - # run renderer smoke test - needed for REFVEC tests - - ls -altr scripts/testv - # LTV smoketest is the same as REF creation stage for renderer - - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv + - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # # default renderer framesize is 20ms + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only + # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true + # # run renderer smoke test - needed for REFVEC tests + # - ls -altr scripts/testv + # # LTV smoketest is the same as REF creation stage for renderer + # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here artifacts: -- GitLab From 0f6023ff51c0f8802f1e8894b28595e8d8767221 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 13:40:02 +0200 Subject: [PATCH 207/340] fix reduction of testcase in LTV test --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b5b45dd0d..1fcf22f5c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2317,9 +2317,9 @@ coverage-test-on-main-scheduled-ltv: COVERAGE_TITLE: "long test vectors" script: # -- Add extended coverage of EVS test and long test vectors - - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # # default renderer framesize is 20ms - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true -- GitLab From 8a9bdb93103c565c823b059ff42b23c8f335b1e4 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 14:18:11 +0200 Subject: [PATCH 208/340] use ci snippet for coverage collection --- .gitlab-ci.yml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1fcf22f5c1..1710466465 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1446,13 +1446,7 @@ ivas-conformance-linux: # Collect coverage - cd - - - lcov -c -d obj -o $COVERAGE_OUTPUT_FILE - - lcov -r $COVERAGE_OUTPUT_FILE "*apps*" -o $COVERAGE_OUTPUT_FILE - - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - - commit_sha=$(git rev-parse HEAD) - # replace hardcoded path with relative path to allow easy merging of .info files - - sed -i "s|$(pwd)|.|g" $COVERAGE_OUTPUT_FILE - - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" + - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/collect-coverage.sh --obj-dir obj --output-file $COVERAGE_OUTPUT_FILE --output-dir $COVERAGE_OUTPUT_DIR --title "Coverage on main -- $COVERAGE_TITLE" # Check for failures - if [ $exit_code -ne 0 ]; then echo "Test failures encountered"; exit $EXIT_CODE_FAIL; fi @@ -2222,14 +2216,7 @@ sanitizer-test-osba-planar-hoa3-ism4: - cp IVAS_rend IVAS_rend_ref - cp ISAR_post_rend ISAR_post_rend_ref after_script: - - lcov -c -d cmake-build -o $COVERAGE_OUTPUT_FILE - # remove apps and lib_util files from coverage - - lcov -r $COVERAGE_OUTPUT_FILE "*apps*" -o $COVERAGE_OUTPUT_FILE - - lcov -r $COVERAGE_OUTPUT_FILE "*lib_util*" -o $COVERAGE_OUTPUT_FILE - - commit_sha=$(git rev-parse HEAD) - # replace hardcoded path with relative path to allow easy merging of .info files - - sed -i "s|$(pwd)|.|g" $COVERAGE_OUTPUT_FILE - - genhtml $COVERAGE_OUTPUT_FILE -o $COVERAGE_OUTPUT_DIR -t "Coverage on main -- $COVERAGE_TITLE @ $commit_sha" + - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/collect-coverage.sh --obj-dir cmake-build --output-file $COVERAGE_OUTPUT_FILE --output-dir $COVERAGE_OUTPUT_DIR --title "Coverage on main -- $COVERAGE_TITLE" artifacts: name: "main-$COVERAGE_OUTPUT_DIR-sha-$CI_COMMIT_SHORT_SHA" when: always -- GitLab From cecfb716842d67174996a8e8a984a91a4bef775d Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 14:18:51 +0200 Subject: [PATCH 209/340] [revert-me] change CI ref for testing new snippet --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1710466465..22e926b7a9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ variables: # note: GitLab cannot reference variables defined by users in the include ref:, we need to use a YAML anchor for this # see https://docs.gitlab.com/ci/yaml/includes/#use-variables-with-include for more information - IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF main + IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF add-snippet-for-coverage-collection include: - local: .gitlab-ci/variables.yml -- GitLab From 511469b346da152b37873b8151d190cea04c5722 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 6 Oct 2025 15:03:20 +0300 Subject: [PATCH 210/340] Fix windows build issues --- Workspace_msvc/lib_dec.vcxproj | 2 +- Workspace_msvc/lib_util.vcxproj | 15 ++++++--- Workspace_msvc/lib_util.vcxproj.filters | 43 ++++++++++++++++++------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index 3c7ea3e597..a1710ca86b 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -68,7 +68,7 @@ Disabled - ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;%(AdditionalIncludeDirectories) + ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;..\lib_util;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;$(Macros);WIN32;%(PreprocessorDefinitions) EnableFastChecks diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 7c10648b72..85859e257c 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -110,11 +110,12 @@ - - + + + @@ -128,7 +129,6 @@ - @@ -144,6 +144,14 @@ + + + + + + + + @@ -157,7 +165,6 @@ - diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index ed062708ad..07c8fdfe43 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -67,9 +67,6 @@ util_c - - util_c - util_c @@ -82,19 +79,22 @@ util_c - + util_c - + util_c - + util_c - + util_c - + + util_c + + util_c @@ -165,9 +165,6 @@ util_h - - util_h - util_h @@ -186,6 +183,30 @@ util_h + + util_h + + + util_h + + + util_h + + + util_h + + + util_h + + + util_h + + + util_h + + + util_h + -- GitLab From c6ebdc86dec5efbb7676e32cef83d4457b8a1e7c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 6 Oct 2025 15:24:45 +0300 Subject: [PATCH 211/340] Remove lib_util from include directories for lib_dec windows build --- Workspace_msvc/lib_dec.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index a1710ca86b..3c7ea3e597 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -68,7 +68,7 @@ Disabled - ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;..\lib_util;%(AdditionalIncludeDirectories) + ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;$(Macros);WIN32;%(PreprocessorDefinitions) EnableFastChecks -- GitLab From d8e91ec508be3476ab07e5bfa488b6cfd2715a6c Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 15:07:58 +0200 Subject: [PATCH 212/340] Revert "[revert-me] change CI ref for testing new snippet" This reverts commit cecfb716842d67174996a8e8a984a91a4bef775d. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22e926b7a9..1710466465 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ variables: # note: GitLab cannot reference variables defined by users in the include ref:, we need to use a YAML anchor for this # see https://docs.gitlab.com/ci/yaml/includes/#use-variables-with-include for more information - IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF add-snippet-for-coverage-collection + IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF main include: - local: .gitlab-ci/variables.yml -- GitLab From 3cd7de857159c593d28d6106ebf3f83745bd9cd9 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 15:09:18 +0200 Subject: [PATCH 213/340] Revert "fix reduction of testcase in LTV test" This reverts commit 0f6023ff51c0f8802f1e8894b28595e8d8767221. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1710466465..68df34351a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2304,9 +2304,9 @@ coverage-test-on-main-scheduled-ltv: COVERAGE_TITLE: "long test vectors" script: # -- Add extended coverage of EVS test and long test vectors - # - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # # default renderer framesize is 20ms - - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true -- GitLab From fd6dda87d84ac0300ae107a6fd9d2b057e4e7c65 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 15:09:26 +0200 Subject: [PATCH 214/340] Revert "[revert-me] reduce number of tests for faster iteration" This reverts commit 8345f5f0d2b2fbd41ddca93aa035202b480298bd. --- .gitlab-ci.yml | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 68df34351a..8036827b99 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1389,10 +1389,10 @@ ivas-conformance-linux: # Reference creation - python3 scripts/prepare_combined_format_inputs.py - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" - - python3 -m pytest -k stereo -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files + - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files # Output creation - - python3 -m pytest -k stereo -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html + - python3 -m pytest -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt # Copy input data and output ref data @@ -1440,7 +1440,7 @@ ivas-conformance-linux: # Test run generated scripts in testvec - cd testvec - exit_code=0 - - python3 -m pytest -k stereo -q conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? + - python3 -m pytest -q conformance-test/test_26252.py --junit-xml=report-junit.xml --html=report.html --self-contained-html || exit_code=$? - mv report.html .. - mv report-junit.xml .. @@ -2263,33 +2263,33 @@ coverage-test-on-main-scheduled-stv: script: # -- Run short test vector suite to check coverage of that first # codec smoke test - # - bash ci/smoke_test.sh coverage + - bash ci/smoke_test.sh coverage # default renderer framesize is 20ms - - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only - # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true - # # run renderer smoke test - needed for REFVEC tests - # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py - # # run renderer test only with REF creation stage - no CUT needed since executables are identical - # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --create_ref - # # run split rendering smoke test and REF creation - # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py - # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py --create_ref - # # VoIP BE test - # - python3 -m pytest -q --tb=no tests/test_be_for_jbm_neutral_dly_profile.py - # # split rendering VoIP BE test - # - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_voip_be_splitrend_vs_binaural.py - # # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted - # # - bash ci/run-first-frame-is-sid-test.sh - # # TODO consider HRTF binary loading test, or probably already covered by PRM files - # # - python3 -m pytest -q --tb=no tests/hrtf_binary_loading - # # run BE vs EXT test - # - python3 tests/create_short_testvectors.py --cut_len 1.0 - # - python3 -m pytest -q --tb=no tests/test_be_for_ext_outputs.py - # # run LC3plus wrapper unit test - # - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only + # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only || true + # run renderer smoke test - needed for REFVEC tests + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py + # run renderer test only with REF creation stage - no CUT needed since executables are identical + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --create_ref + # run split rendering smoke test and REF creation + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_split_rendering.py --create_ref + # VoIP BE test + - python3 -m pytest -q --tb=no tests/test_be_for_jbm_neutral_dly_profile.py + # split rendering VoIP BE test + - python3 -m pytest -n auto -q --tb=no tests/split_rendering/test_voip_be_splitrend_vs_binaural.py + # TODO first frame SID - this script runs a `make clean` and needs to be checked/adapted + # - bash ci/run-first-frame-is-sid-test.sh + # TODO consider HRTF binary loading test, or probably already covered by PRM files + # - python3 -m pytest -q --tb=no tests/hrtf_binary_loading + # run BE vs EXT test + - python3 tests/create_short_testvectors.py --cut_len 1.0 + - python3 -m pytest -q --tb=no tests/test_be_for_ext_outputs.py + # run LC3plus wrapper unit test + - scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test # TODO consider including check-clipping, but this scales ALL testv files. could be added at end of this job artifacts: paths: @@ -2304,20 +2304,20 @@ coverage-test-on-main-scheduled-ltv: COVERAGE_TITLE: "long test vectors" script: # -- Add extended coverage of EVS test and long test vectors - - python3 -m pytest -k stereo $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # # default renderer framesize is 20ms - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec - # # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only - # # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true - # - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true - # # run renderer smoke test - needed for REFVEC tests - # - ls -altr scripts/testv - # # LTV smoketest is the same as REF creation stage for renderer - # - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_evs.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # default renderer framesize is 20ms + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --update_ref 1 --param_file scripts/config/self_test_ltv.prm --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec + # for different renderer framesizes, only the decoder needs to be tested, so use --decoder_only + # need to ignore non-zero exit codes as limiter is active and thus the different framesiszes will not be BE in all cases + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 5 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_param_file.py -v -n auto -q --tb=no --param_file scripts/config/self_test_ltv.prm --dut_decoder_path ./IVAS_dec --decoder_only --dut_fr 10 || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --update_ref 1 --ref_encoder_path ./IVAS_cod --ref_decoder_path ./IVAS_dec --use_ltv + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 5 --decoder_only --use_ltv || true + - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR/test_sba.py -v -n auto -q --tb=no --dut_encoder_path ./IVAS_cod --dut_decoder_path ./IVAS_dec --dut_fr 10 --decoder_only --use_ltv || true + # run renderer smoke test - needed for REFVEC tests + - ls -altr scripts/testv + # LTV smoketest is the same as REF creation stage for renderer + - python3 -m pytest -n auto -q --tb=no tests/renderer/test_renderer.py --use_ltv # split rendering test doesn't support LTV - skipped here # VoIP BE tests don't support LTV - skipped here artifacts: -- GitLab From 35f451dcab00bec6534df1fc19f7ec203f33afa1 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 6 Oct 2025 16:15:42 +0200 Subject: [PATCH 215/340] integrate new coverage jobs into gtlab pages setup --- ci/index-pages.html | 5 +---- ci/setup_pages.py | 35 +++++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ci/index-pages.html b/ci/index-pages.html index 510e203115..390f06631d 100644 --- a/ci/index-pages.html +++ b/ci/index-pages.html @@ -11,9 +11,6 @@

Test Coverage

- + {} diff --git a/ci/setup_pages.py b/ci/setup_pages.py index df350d8568..1a1c1ea735 100755 --- a/ci/setup_pages.py +++ b/ci/setup_pages.py @@ -40,7 +40,10 @@ JOBS_FLOAT_REPO = { "complexity-osba-in-binaural_room_ir-out": "OSBA in, BINAURAL_ROOM_IR out", "complexity-StereoDmxEVS-stereo-in-mono-out": "StereoDmxEVS, Stereo in, Mono out", # "timeless" jobs (not complexity) - "coverage-test-on-main-scheduled": "Coverage", + "coverage-test-on-main-scheduled-stv": "Short test vector coverage (~ merge request pipelines)", + "coverage-test-on-main-scheduled-ltv": "Long test vector coverage (for test_param_file.py, test_sba.py and test_renderer.py)", + "ivas-conformance-linux": "Conformance test coverage", + "coverage-merge": "Merged coverage (all of the above combined)", } JOBS_BASOP_REPO = { "ivas-pytest-compare_ref-long-dec": "Pytest decoder compare to ref LTV", @@ -77,6 +80,13 @@ JOBS_FOR_PROJECT_ID = { PROJECT_ID_BASOP_REPO: JOBS_BASOP_REPO, } +ARTIFACT_FOLDER_4_COVERAGE_JOBS = { + "coverage-test-on-main-scheduled-stv": "coverage_stv", + "coverage-test-on-main-scheduled-ltv": "coverage_ltv", + "ivas-conformance-linux": "coverage_conformance", + "coverage-merge": "coverage-merged", +} + ARTIFACTS = "artifacts.zip" API_URL_BASE = "https://forge.3gpp.org/rep/api/v4/projects/{}/jobs" PUBLIC_FOLDER = pathlib.Path("./public").absolute() @@ -104,15 +114,20 @@ def main(): def create_landing_page(jobs, index_html, project_id): # dynamically create the complexity links on the landing page - link_html = list() - link_html = ["
    "] + link_html_complexity = ["
      "] + link_html_coverage = ["
        "] for job, link_text in jobs.items(): if job.startswith("complexity"): line = f'
      • {link_text}
      • ' - link_html.append(line) - link_html.append("
      ") - link_html_text = "\n".join(link_html) + link_html_complexity.append(line) + elif job.startswith("coverage") or job == "ivas-conformance-linux": + artifact_folder = ARTIFACT_FOLDER_4_COVERAGE_JOBS[job] + line = f'
    • {link_text}
    • ' + link_html_coverage.append(line) + + link_html_complexity.append("
    ") + link_html_coverage.append("
") if project_id == PROJECT_ID_FLOAT_REPO: index_template = "index-pages.html" @@ -126,14 +141,18 @@ def create_landing_page(jobs, index_html, project_id): with open(index_pages_tmpl_path) as f: index_pages_tmpl = f.read() - index_pages_tmpl = index_pages_tmpl.format(link_html_text) + link_html_complexity_text = "\n".join(link_html_complexity) + link_html_coverage_text = "\n".join(link_html_coverage) + index_pages_tmpl = index_pages_tmpl.format( + link_html_complexity_text, link_html_coverage_text + ) with open(index_html, "w") as f: f.write(index_pages_tmpl) def get_artifacts_for_jobs_and_return_num_failed( - jobs: list, project_id: int, success_only: bool + jobs: dict, project_id: int, success_only: bool ) -> int: """ Get specified artifact folders for all jobs given and put them into the public folder. -- GitLab From 96f25dcd72cdcb477426a6600605838e56a14eb0 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Tue, 7 Oct 2025 17:59:32 +1100 Subject: [PATCH 216/340] Remove restart functionality and bugfix in lib_dec free and code review Signed-off-by: Ripinder Singh --- apps/decoder.c | 151 ++---------------------------------- lib_dec/lib_dec.c | 5 -- lib_util/ivas_bpool.c | 1 - lib_util/ivas_queue.c | 1 - lib_util/ivas_rtp_file.c | 67 +++++++++++----- lib_util/ivas_rtp_file.h | 3 +- lib_util/ivas_rtp_payload.c | 2 - lib_util/ivas_rtp_pi_data.c | 10 +-- lib_util/ivas_rtp_pi_data.h | 51 ++++++------ lib_util/mutex.h | 2 +- 10 files changed, 82 insertions(+), 211 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 6e04fadb7f..ed96f79196 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -179,16 +179,11 @@ typedef struct static bool parseCmdlIVAS_dec( int16_t argc, char **argv, DecArguments *arg ); static void usage_dec( void ); static ivas_error decodeG192( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); -static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, #ifdef FIX_1119_SPLIT_RENDERING_VOIP - ISAR_SPLIT_REND_BITS_DATA *splitRendBits, -#endif -#ifdef IVAS_RTPDUMP - IVAS_DEC_HANDLE *phIvasDec, +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, ISAR_SPLIT_REND_BITS_DATA *splitRendBits, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); #else - IVAS_DEC_HANDLE hIvasDec, +static ivas_error decodeVoIP( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtf, RotFileReader *headRotReader, RotFileReader *externalOrientationFileReader, RotFileReader *refRotReader, Vector3PairFileReader *referenceVectorReader, ObjectEditFileReader *objectEditFileReader, IVAS_DEC_HANDLE hIvasDec, int16_t *pcmBuf ); #endif - int16_t *pcmBuf ); static ivas_error load_hrtf_from_file( IVAS_DEC_HRTF_BINARY_WRAPPER *hHrtfBinary, IVAS_DEC_HANDLE hIvasDec, const IVAS_AUDIO_CONFIG OutputConfig, const int32_t output_Fs ); #ifdef DEBUGGING static ivas_error printBitstreamInfoVoip( DecArguments arg, BS_READER_HANDLE hBsReader, IVAS_DEC_HANDLE hIvasDec ); @@ -196,112 +191,6 @@ static int16_t app_own_random( int16_t *seed ); #endif static void do_object_editing( IVAS_EDITABLE_PARAMETERS *editableParameters, ObjectEditFileReader *objectEditFileReader ); -#ifdef IVAS_RTPDUMP -static ivas_error restartDecoder( - IVAS_DEC_HANDLE *phIvasDec, - IVAS_DEC_MODE codec, - DecArguments *arg, - IVAS_RENDER_CONFIG_DATA *renderConfig, - IVAS_CUSTOM_LS_DATA *hLsCustomData ) -{ - ivas_error error = IVAS_ERR_OK; - IVAS_DEC_HANDLE hIvasDec; - - if ( phIvasDec == NULL ) - { - return IVAS_ERR_UNEXPECTED_NULL_POINTER; - } - - if ( NULL != *phIvasDec ) - { - IVAS_DEC_Close( phIvasDec ); - } - - if ( ( error = IVAS_DEC_Open( phIvasDec, codec ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "Open failed: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - arg->decMode = codec; - - hIvasDec = *phIvasDec; - - uint16_t aeID = arg->aeSequence.count > 0 ? arg->aeSequence.pID[0] : 65535; - - IVAS_AUDIO_CONFIG outputConfig = ( codec == IVAS_DEC_MODE_IVAS ) ? arg->outputConfig : IVAS_AUDIO_CONFIG_MONO; - if ( ( error = IVAS_DEC_Configure( hIvasDec, arg->output_Fs, outputConfig, arg->renderFramesize, arg->customLsOutputEnabled, arg->hrtfReaderEnabled, - arg->enableHeadRotation, arg->enableExternalOrientation, arg->orientation_tracking, arg->renderConfigEnabled, arg->non_diegetic_pan_enabled, - arg->non_diegetic_pan_gain, arg->dpidEnabled, aeID, arg->objEditEnabled, arg->delayCompensationEnabled ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - if ( ( error = IVAS_DEC_GetRenderFramesize( hIvasDec, &arg->renderFramesize ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - if ( arg->outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || arg->outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM ) - { - if ( ( error = IVAS_DEC_EnableSplitRendering( hIvasDec ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nSplit rendering configure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - if ( ( error = IVAS_DEC_GetRenderFramesize( hIvasDec, &arg->renderFramesize ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nConfigure failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - arg->enableHeadRotation = true; - } - - if ( arg->voipMode ) - { - if ( ( error = IVAS_DEC_EnableVoIP( hIvasDec, 60, arg->inputFormat ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nCould not enable VOIP: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - } - - if ( ( error = IVAS_DEC_PrintConfig( hIvasDec, 1, arg->voipMode ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nIVAS_DEC_PrintConfig failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - - if ( arg->renderConfigEnabled && renderConfig != NULL ) - { - if ( ( error = IVAS_DEC_FeedRenderConfig( hIvasDec, *renderConfig ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nIVAS_DEC_FeedRenderConfig failed: %s\n\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - } - - if ( arg->customLsOutputEnabled && hLsCustomData != NULL ) - { - if ( ( error = IVAS_DEC_FeedCustomLsData( hIvasDec, *hLsCustomData ) ) != IVAS_ERR_OK ) - { - fprintf( stderr, "\nIVAS_DEC_FeedCustomLsData failed: %s\n", IVAS_DEC_GetErrorMessage( error ) ); - goto cleanup; - } - } - - return IVAS_ERR_OK; - -cleanup: - IVAS_DEC_Close( phIvasDec ); - return error; -} -#endif - /*------------------------------------------------------------------------------------------* * main() * @@ -855,16 +744,12 @@ int main( if ( arg.voipMode ) { - error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, + #ifdef FIX_1119_SPLIT_RENDERING_VOIP - &splitRendBits, -#endif -#ifdef IVAS_RTPDUMP - &hIvasDec, + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, &splitRendBits, hIvasDec, pcmBuf ); #else - hIvasDec, + error = decodeVoIP( arg, hBsReader, &hHrtfBinary, headRotReader, externalOrientationFileReader, refRotReader, referenceVectorReader, objectEditFileReader, hIvasDec, pcmBuf ); #endif - pcmBuf ); } else { @@ -3171,11 +3056,7 @@ static ivas_error decodeVoIP( #ifdef FIX_1119_SPLIT_RENDERING_VOIP ISAR_SPLIT_REND_BITS_DATA *splitRendBits, #endif -#ifdef IVAS_RTPDUMP - IVAS_DEC_HANDLE *phIvasDec, -#else IVAS_DEC_HANDLE hIvasDec, -#endif int16_t *pcmBuf ) { bool decodingFailed = true; /* Assume failure until cleanup is reached without errors */ @@ -3209,7 +3090,6 @@ static ivas_error decodeVoIP( int16_t i; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; - IVAS_DEC_HANDLE hIvasDec = *phIvasDec; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; @@ -3392,27 +3272,6 @@ static ivas_error decodeVoIP( { nSamplesRendered = 0; -#ifdef IVAS_RTPDUMP - if ( ivasRtp.restartNeeded ) - { - IVAS_DEC_MODE newCodecInPacket = ( ivasRtp.codecId == IVAS_RTP_EVS ) ? IVAS_DEC_MODE_EVS : IVAS_DEC_MODE_IVAS; - error = restartDecoder( - &hIvasDec, - newCodecInPacket, - &arg, - NULL, /* ToDo : Provide rendererConfig */ - NULL /* ToDo : Provide LS Custom Data */ - ); - if ( error != IVAS_ERR_OK ) - { - fprintf( stderr, "\nFailed to restart decoder from %d to %d\n", arg.decMode, newCodecInPacket ); - goto cleanup; - } - *phIvasDec = hIvasDec; /* Update for main()' s free */ - ivasRtp.restartNeeded = false; - } -#endif - /* reference vector */ if ( arg.enableReferenceVectorTracking && vec_pos_update == 0 ) { diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 87d4d06d65..e0fc232dc2 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -347,15 +347,10 @@ void IVAS_DEC_Close( } /* destroy Split binaural renderer (ISAR) handle */ -#ifndef IVAS_RTPDUMP ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); -#endif if ( ( *phIvasDec )->st_ivas ) { -#ifdef IVAS_RTPDUMP - ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); -#endif ivas_destroy_dec( ( *phIvasDec )->st_ivas ); ( *phIvasDec )->st_ivas = NULL; } diff --git a/lib_util/ivas_bpool.c b/lib_util/ivas_bpool.c index 827422fac3..9db909f535 100644 --- a/lib_util/ivas_bpool.c +++ b/lib_util/ivas_bpool.c @@ -29,7 +29,6 @@ the United Nations Convention on Contracts on the International Sales of Goods. *******************************************************************************************************/ -#include #include #include #include "ivas_bpool.h" diff --git a/lib_util/ivas_queue.c b/lib_util/ivas_queue.c index c5806e0307..b17cf3e21a 100644 --- a/lib_util/ivas_queue.c +++ b/lib_util/ivas_queue.c @@ -30,7 +30,6 @@ *******************************************************************************************************/ -#include #include #include "ivas_queue.h" #include "ivas_error_utils.h" diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 1c45869e69..8555480efd 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -29,12 +29,8 @@ the United Nations Convention on Contracts on the International Sales of Goods. *******************************************************************************************************/ -#include #include #include -#include - -#include "lib_dec.h" #include "ivas_rtp_file.h" #include "ivas_error_utils.h" @@ -86,9 +82,9 @@ static ivas_error IvasRtpFile_Close( } static ivas_error IvasRtpFile_Write( - IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file writer handle */ - const uint8_t *packet, - size_t numBytes ) + IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file writer handle */ + const uint8_t *packet, /* i : rtp packet to be written to rtpdump file */ + size_t numBytes ) /* i : size in bytes of the rtp packet */ { ivas_error error = IVAS_ERR_OK; if ( hRtpFile->isFileWriter ) @@ -105,10 +101,11 @@ static ivas_error IvasRtpFile_Write( } static ivas_error IvasRtpFile_Read( - IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file writer handle */ - uint8_t *packet, - size_t *numBytes, - size_t capacity ) + IVAS_RTP_FILE_HANDLE hRtpFile, /* i : pointer to an IVAS file reader handle */ + uint8_t *packet, /* o : read rtp packet */ + size_t *numBytes, /* o : no of bytes in packet */ + size_t capacity /* i : max capacity of the packet buffer */ +) { size_t nread = 0; uint32_t length = 0; @@ -149,7 +146,11 @@ static const char *const PiDataNames[IVAS_PI_MAX_ID] = { "RESERVED30", "NO_DATA" }; -void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ) +void IVAS_RTP_LogPiData( + FILE *f_piDataOut, /* i/o : Output json file handle to dump PI data for debug/test */ + const PIDATA_TS *piData, /* i : PI Data + Timestamp array containing all PI data in current packet */ + uint32_t nPiDataPresent /* i : Number of valid elements in the piData array */ +) { uint32_t timestamp = ~0u; if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) @@ -164,7 +165,7 @@ void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataP while ( nPiDataPresent-- > 0 ) { - PIDATA_TS *cur = piData++; + const PIDATA_TS *cur = piData++; if ( timestamp != ( ~0u ) && timestamp != cur->timestamp ) { @@ -222,7 +223,7 @@ void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataP case IVAS_PI_AUDIO_DESCRIPTION: { uint32_t nEntries = cur->data.audioDesc.nValidEntries; - IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; + const IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; fprintf( f_piDataOut, "[\n" ); while ( nEntries-- > 0 ) @@ -241,7 +242,7 @@ void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataP break; case IVAS_PI_DIEGETIC_TYPE: { - bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; + const bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; fprintf( f_piDataOut, "{\n" ); fprintf( f_piDataOut, "\t\t\t\"isDigetic\": [\n" ); fprintf( f_piDataOut, "\t\t\t\t%s,\n", isDiegetic[0] ? "true" : "false" ); @@ -274,7 +275,7 @@ void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataP break; case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: { - IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; fprintf( f_piDataOut, "{\n" ); fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); @@ -336,7 +337,9 @@ void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataP fprintf( f_piDataOut, "\n\t}" ); } -void IVAS_RTP_Term( IVAS_RTP *rtp ) +void IVAS_RTP_Term( + IVAS_RTP *rtp /* i/o : IVAS RTP File reader/writer handle */ +) { if ( NULL != rtp ) { @@ -382,7 +385,11 @@ void IVAS_RTP_Term( IVAS_RTP *rtp ) } } -ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ) +ivas_error IVAS_RTP_WRITER_Init( + IVAS_RTP *rtp, /* i/o : IVAS RTP File writer handle */ + const char *outputBitstreamFilename, /* i : RTP Dump filename */ + uint32_t numFramesPerPacket /* i : No. of frames per packet desired */ +) { uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); ivas_error error = IVAS_ERR_OK; @@ -413,7 +420,11 @@ ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilen return error; } -ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ) +ivas_error IVAS_RTP_READER_Init( + IVAS_RTP *rtp, /* i/o : IVAS RTP File reader handle */ + const char *inputBitstreamFilename, /* i : Input rtpdump filename */ + const char *piOutputFilename /* i : Output PI data json filename */ +) { ivas_error error = IVAS_ERR_OK; @@ -447,7 +458,13 @@ ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilena return error; } -ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ) +ivas_error IVAS_RTP_WriteNextFrame( + IVAS_RTP *rtp, /* i/o : IVAS RTP File writer handle */ + uint8_t *au, /* i : IVAS Compressed AU (Packed frame) */ + int16_t auSizeBits, /* i : Frame size in bits */ + bool isMono, /* i : input was evs(true) or ivas(false) */ + bool forcePacket /* i : force packets with whatever frames pushed so far */ +) { ivas_error error = IVAS_ERR_OK; uint32_t nProcPiData = 0; @@ -501,7 +518,15 @@ ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBi return error; } -ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ) +ivas_error IVAS_RTP_ReadNextFrame( + IVAS_RTP *rtp, /* i/o : IVAS RTP File reader handle */ + uint8_t *au, /* o : Read next IVAS Compressed AU (Packed frame) */ + int16_t *auSizeBits, /* o : Reported Frame size in bits */ + uint32_t *rtpTimeStamp, /* o : RTP Timestamp for this frame */ + uint16_t *rtpSequenceNumber, /* o : RTP sequence number for this packet */ + uint32_t *nextPacketRcvTime_ms, /* i/o : Clock indicating packet receive times need in JBM */ + bool *qBit /* o : AMRWB Q bite as indicated in the RTP packet */ +) { ivas_error error = IVAS_ERR_OK; IVAS_DATA_BUFFER packedFrame; diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 5d3e9df1ab..c0757834f0 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -33,7 +33,6 @@ #ifndef IVAS_RTP_FILE_H #define IVAS_RTP_FILE_H -#include #include #include "common_api_types.h" @@ -76,7 +75,7 @@ ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilena void IVAS_RTP_Term( IVAS_RTP *rtp ); ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ); ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ); -void IVAS_RTP_LogPiData( FILE *f_piDataOut, PIDATA_TS *piData, uint32_t nPiDataPresent ); +void IVAS_RTP_LogPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent ); #endif /* IVAS_RTP_FILE_H */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 468dce0d17..4e4b11f2aa 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -37,8 +37,6 @@ #ifdef IVAS_RTPDUMP -#include "ivas_rtp_api.h" -#include "ivas_rtp_pi_data.h" #include "ivas_rtp_internal.h" #include "ivas_bpool.h" #include "ivas_queue.h" diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index a3d8c5fe63..7150cfe4a8 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -30,8 +30,6 @@ *******************************************************************************************************/ -#include -#include "ivas_rtp_pi_data.h" #include "ivas_error_utils.h" #include "ivas_rtp_internal.h" @@ -331,7 +329,7 @@ static ivas_error packAudioDescription( const IVAS_PIDATA_GENERIC *piData, uint8 *nBytesWritten = 0; - if ( audioDesc->nValidEntries > ( IVAS_PI_MAX_OBJECTS + 1 ) ) + if ( audioDesc->nValidEntries > ( IVAS_MAX_NUM_OBJECTS + 1 ) ) { return IVAS_ERROR( IVAS_ERR_RTP_UNSUPPORTED_FRAME, "Audio Description cannot have more than 5 entries" ); } @@ -364,7 +362,7 @@ static ivas_error unpackAudioDescription( const uint8_t *buffer, uint32_t numDat IVAS_PIDATA_AUDIO_DESC *audioDesc = (IVAS_PIDATA_AUDIO_DESC *) piData; /* Audio Description data is max 5 bytes */ - if ( numDataBytes > ( IVAS_PI_MAX_OBJECTS + 1 ) ) + if ( numDataBytes > ( IVAS_MAX_NUM_OBJECTS + 1 ) ) { return IVAS_ERROR( IVAS_ERR_RTP_UNPACK_PI_DATA, "Incorrect size to unpack Orientation PI data" ); } @@ -505,7 +503,7 @@ static ivas_error packDiegetic( const IVAS_PIDATA_GENERIC *piData, uint8_t *buff } /* Valid bits must be based on active bits defined for the input format */ - for ( n = 0; n < ( IVAS_PI_MAX_OBJECTS + 1 ); n++ ) + for ( n = 0; n < ( IVAS_MAX_NUM_OBJECTS + 1 ); n++ ) { byte <<= 1; byte |= ( diegetic->isDiegetic[n] ); @@ -536,7 +534,7 @@ static ivas_error unpackDiegetic( const uint8_t *buffer, uint32_t numDataBytes, byte = buffer[0]; /* Valid bits must be based on active bits defined for the input format */ - for ( n = 0; n < ( IVAS_PI_MAX_OBJECTS + 1 ); n++ ) + for ( n = 0; n < ( IVAS_MAX_NUM_OBJECTS + 1 ); n++ ) { diegetic->isDiegetic[n] = ( ( byte >> ( 7 - n ) ) & 1 ) != 0; } diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 3787861f00..6a84b8f2e3 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -43,7 +43,6 @@ extern "C" #ifdef IVAS_RTPDUMP #define IVAS_PI_MAX_DATA_SIZE ( 32 + 2 ) /* max packed PI data bytes + pi header bytes */ -#define IVAS_PI_MAX_OBJECTS ( 4 ) /* max ISM objects in PI data */ /* IVAS PI Data Types */ typedef enum @@ -183,10 +182,10 @@ extern "C" typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_AUDIO_DESC) */ - uint32_t piDataType; /* IVAS_PI_AUDIO_DESCRIPTION */ - uint32_t nValidEntries; /* Number of valid audio IDs */ - IVAS_AUDIO_ID audioId[1 + IVAS_PI_MAX_OBJECTS]; /* audio id as per format */ + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_DESC) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_DESCRIPTION */ + uint32_t nValidEntries; /* Number of valid audio IDs */ + IVAS_AUDIO_ID audioId[1 + IVAS_MAX_NUM_OBJECTS]; /* audio id as per format */ } IVAS_PIDATA_AUDIO_DESC; /* ISM specific PI data related to PI types : - @@ -211,33 +210,33 @@ extern "C" /* ISM ID */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ID) */ - uint32_t piDataType; /* IVAS_PI_ISM_ID */ - uint8_t id[IVAS_PI_MAX_OBJECTS]; /* 8-bit ISM id of object */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ID) */ + uint32_t piDataType; /* IVAS_PI_ISM_ID */ + uint8_t id[IVAS_MAX_NUM_OBJECTS]; /* 8-bit ISM id of object */ } IVAS_PIDATA_ISM_ID; /* ISM gain */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_GAIN) */ - uint32_t piDataType; /* IVAS_PI_ISM_GAIN */ - int8_t dB[IVAS_PI_MAX_OBJECTS]; /* ISM gain in dB per object [-96, +3] */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_GAIN) */ + uint32_t piDataType; /* IVAS_PI_ISM_GAIN */ + int8_t dB[IVAS_MAX_NUM_OBJECTS]; /* ISM gain in dB per object [-96, +3] */ } IVAS_PIDATA_ISM_GAIN; /* ISM orientation */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ORIENTATION) */ - uint32_t piDataType; /* IVAS_PI_ISM_ORIENTATION */ - IVAS_QUATERNION orientation[IVAS_PI_MAX_OBJECTS]; /* Orientation of audio objects in ISM(s) */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ORIENTATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_ORIENTATION */ + IVAS_QUATERNION orientation[IVAS_MAX_NUM_OBJECTS]; /* Orientation of audio objects in ISM(s) */ } IVAS_PIDATA_ISM_ORIENTATION; /* ISM position */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_POSITION) */ - uint32_t piDataType; /* IVAS_PI_ISM_POSITION */ - IVAS_COORDINATE position[IVAS_PI_MAX_OBJECTS]; /* Position of audio objects in ISM(s) */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_POSITION) */ + uint32_t piDataType; /* IVAS_PI_ISM_POSITION */ + IVAS_COORDINATE position[IVAS_MAX_NUM_OBJECTS]; /* Position of audio objects in ISM(s) */ } IVAS_PIDATA_ISM_POSITION; /* ISM distance attenuation comprising of following gains per ISM @@ -254,9 +253,9 @@ extern "C" typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ATTENUATION) */ - uint32_t piDataType; /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ - IVAS_DIST_ATTEN distAtten[IVAS_PI_MAX_OBJECTS]; /* Distance attenuation of audio objects */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ATTENUATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ + IVAS_DIST_ATTEN distAtten[IVAS_MAX_NUM_OBJECTS]; /* Distance attenuation of audio objects */ } IVAS_PIDATA_ISM_ATTENUATION; /* ISM Directivity comprising of following per ISM :- @@ -273,9 +272,9 @@ extern "C" typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_ISM_DIRECTIVITY) */ - uint32_t piDataType; /* IVAS_PI_ISM_DIRECTIVITY */ - IVAS_ISM_DIRECTIVITY directivity[IVAS_PI_MAX_OBJECTS]; /* Directivity of audio objects */ + size_t size; /* sizeof(IVAS_PIDATA_ISM_DIRECTIVITY) */ + uint32_t piDataType; /* IVAS_PI_ISM_DIRECTIVITY */ + IVAS_ISM_DIRECTIVITY directivity[IVAS_MAX_NUM_OBJECTS]; /* Directivity of audio objects */ } IVAS_PIDATA_ISM_DIRECTIVITY; /* Diegetic and non-diegetic indication flag as per audio format @@ -287,9 +286,9 @@ extern "C" */ typedef struct { - size_t size; /* sizeof(IVAS_PIDATA_DIEGETIC) */ - uint32_t piDataType; /* IVAS_PI_DIEGETIC_TYPE */ - bool isDiegetic[1 + IVAS_PI_MAX_OBJECTS]; /* diegetic indication as per audio format */ + size_t size; /* sizeof(IVAS_PIDATA_DIEGETIC) */ + uint32_t piDataType; /* IVAS_PI_DIEGETIC_TYPE */ + bool isDiegetic[1 + IVAS_MAX_NUM_OBJECTS]; /* diegetic indication as per audio format */ } IVAS_PIDATA_DIEGETIC; /* Audio focus direction indicates a direction of interest. diff --git a/lib_util/mutex.h b/lib_util/mutex.h index caf0145e2e..82094b61c8 100644 --- a/lib_util/mutex.h +++ b/lib_util/mutex.h @@ -1,6 +1,6 @@ /****************************************************************************************************** - (C) 2022-2024 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -- GitLab From 17ecc8ea85dc59ba2ec3408bca8d95528fe1f90f Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 7 Oct 2025 14:16:06 +0300 Subject: [PATCH 217/340] Add rtpdump test to pipeline --- .gitlab-ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c53be7cae..f0369c2eff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -697,6 +697,28 @@ split-rendering-voip-be-to-binaural: junit: - report-junit.xml +# test rtpdump +rtpdump-test: + extends: + - .test-job-linux + - .rules-merge-request-to-main + needs: ["build-codec-linux-make"] + stage: test + script: + - make -j + - testcase_timeout=30 + - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout + artifacts: + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" + expire_in: 1 week + when: always + paths: + - report-junit.xml + expose_as: "rtpdump pytest results" + reports: + junit: + - report-junit.xml + lc3-wrapper-unit-test: extends: - .test-job-linux -- GitLab From 02b494f06479926b36ee2b8c375c802f4b46b55e Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Wed, 8 Oct 2025 10:14:03 +1100 Subject: [PATCH 218/340] Remove soundfile, replace with audiofile from pyaudio3dtools Signed-off-by: Ripinder Singh --- tests/requirements.txt | 1 - tests/rtp/test_rtp.py | 27 +++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index a8d8f7b064..00100fae88 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,4 +3,3 @@ pytest-xdist>=1.31.0 scipy>=1.5.2 numpy>=1.19.2 bitstring>=4.3.1 -soundfile>=0.13 diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 4e394ee478..c1832202bb 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -31,7 +31,24 @@ the United Nations Convention on Contracts on the International Sales of Goods. """ __doc__ = """ -To configure test modules. +This test does the following procedure:- + +DECODER TEST +============ +* Encode a input for given format, bitrate and dtx info using IVAS Encoder to g192 +* Generate random PI data to be used for RTP packaging +* Use g192 and random PI data to pack a stream into RTPDump stream using the reference rtp packer implementation in python +* Provide the rtpdump stream to IVAS decoder to decode the stream and generate a PI data dump in a JSON file +* Decode G192 stream using IVAS decoder and compare against output of RTPdump for bit-exactness +* Validate dumped JSON agains original random PI data to check for similar data after deocde + +ENCODER TEST +============ +* Encode the input for given format, bitrate and dtx info using IVAS Encoder to rtpdump directly +* Validate the generated rtp dump using reference python implementation of RTP depacker for following:- + * RTP Header consistency (Timestamp, seq number, etc) + * IVAS Payload (no of frames in packet, bitrate indicated, bitexactness of frames in packet) + * PI Data verification (Timestamp, Pidata Type, similarity of PI data) """ import pytest @@ -43,8 +60,8 @@ import random from tempfile import TemporaryDirectory from pathlib import Path from ivasrtp import * -import soundfile as sf import numpy as np +from pyaudio3dtools.audiofile import readfile ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) sys.path.append(ROOT_DIR) @@ -433,8 +450,10 @@ def run_rtp_bitstream_tests ( add_option_list= ["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)] ) - decAudio, fs = sf.read(pcmOut) - g192Audio, Fs = sf.read(pcmOutG192) + decAudio, fs = readfile(pcmOut) + g192Audio, Fs = readfile(pcmOutG192) + decAudio /= 32768.0 # readfile reuturns 16 bit int + g192Audio /= 32768.0 # readfile reuturns 16 bit int decAudio = decAudio[4*960:] assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= (4 * 960), "Decoded PCM Audio is not same length as input" minSamples = min(decAudio.shape[0], g192Audio.shape[0]) -- GitLab From c982c3d232b62ea702a77e6e125707ee974fcf00 Mon Sep 17 00:00:00 2001 From: Markus Multrus Date: Wed, 8 Oct 2025 11:42:43 +0200 Subject: [PATCH 219/340] first version of script to detect FLP assembled instructions in the executable --- scripts/find_fp_insn.sh | 93 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 scripts/find_fp_insn.sh diff --git a/scripts/find_fp_insn.sh b/scripts/find_fp_insn.sh new file mode 100755 index 0000000000..fe0090a55e --- /dev/null +++ b/scripts/find_fp_insn.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# +# (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +# contributors to this repository. All Rights Reserved. +# +# This software is protected by copyright law and by international treaties. +# The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +# Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +# Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +# Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +# contributors to this repository retain full ownership rights in their respective contributions in +# the software. This notice grants no license of any kind, including but not limited to patent +# license, nor is any license granted by implication, estoppel or otherwise. +# +# Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +# contributions. +# +# This software is provided "AS IS", without any express or implied warranties. The software is in the +# development stage. It is intended exclusively for experts who have experience with such software and +# solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +# and fitness for a particular purpose are hereby disclaimed and excluded. +# +# Any dispute, controversy or claim arising under or in relation to providing this software shall be +# submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +# accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +# the United Nations Convention on Contracts on the International Sales of Goods. +# + +# fp_insn_finder.sh +# Usage: ./find_fp_insn.sh +# disassembles executable, lists common floating-point instructions + +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Find floating-point instructions in an executable based on assembler instructions" + echo "Pre-requisites: Perl or gawk" + exit 1 +fi + +EXE="$1" +if [ ! -x "$EXE" ]; then + echo "Error: '$EXE' is no executable" + exit 2 +fi + +perl_avail=0 +# check, whether we have perl installed +if command -v perl >/dev/null 2>&1; then + perl_avail=1 +fi + +gawk_avail=0 +awk_option="" +# check, whether we have awk (POSIX) or gawk (GNU) on the system +if command -v gawk >/dev/null 2>&1; then + gawk_avail=1 + awk_option="--posix" +fi + + +if [ $perl_avail -ne 0 ]; then + + objdump -d -Mintel "$EXE" \ + | perl -nE ' + # Regex for floating-point instructions + our $fp = qr/\b(?:fadd|fsub|fmul|fdiv|fld|fst|movss|movsd|addss|addsd|subss|subsd|mulss|mulsd|divss|divsd)\b/; + if (/^[0-9a-f]+ <([^>]+)>:/) { $func = $1 } + elsif (/$fp/) { say "$func: $_" } + ' + +else + + objdump -d -Mintel "$EXE" | + awk ${awk_option} ' + /^[0-9a-f]+ :.*$/, "") + func = $0 + next + } + /(^|[^[:alnum:]_])(fadd|fsub|fmul|fdiv|fld|fst|movss|movsd|addss|addsd|subss|subsd|mulss|mulsd|divss|divsd)($|[^[:alnum:]_])/ { + print func ": " $0 + } + ' + +fi -- GitLab From 02b5cc7c6655d9dc3e29eb0e337667752a563dab Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 8 Oct 2025 13:41:13 +0300 Subject: [PATCH 220/340] Move rtpdump tests to the end of the self_test_prm file --- scripts/config/self_test.prm | 73 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/scripts/config/self_test.prm b/scripts/config/self_test.prm index 7bfe33e4de..496a23e70f 100644 --- a/scripts/config/self_test.prm +++ b/scripts/config/self_test.prm @@ -306,10 +306,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit ../IVAS_dec EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT.tst -// stereo bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, EXT out, rtpdump -../IVAS_cod -rtpdump 3 -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit -../IVAS_dec -VOIP_hf_only=1 EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT_rtpdump.tst - // 1 ISM with metadata at 13.2 kbps, 48 kHz in, 48 kHz out, EXT out ../IVAS_cod -ism 1 testv/stvISM1.csv 13200 48 testv/stv1ISM48s.wav bit @@ -583,10 +579,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism +4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv 128000 48 testv/stv4ISM48n.wav bit ../IVAS_dec BINAURAL_ROOM_REVERB 48 bit testv/stv4ISM48n.wav_BINAURAL_ROOM_REVERB_128000_48-48.tst -// 4 ISM with metadata bitrate switching from 24.4 kbps to 512 kbps, 48 kHz in, 48 kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case00_3000_q.csv -dtx -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_24k4_512k.bin 48 testv/stv4ISM48s.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv4ISM48s.wav_brate_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst - // SBA at 13.2 kbps, 32kHz in, 32kHz out, HOA3 out, bandwidth switching ../IVAS_cod -max_band testv/ivas_bws_20fr_start_WB.txt -sba 3 13200 32 testv/stv3OA32c.wav bit @@ -964,10 +956,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -sba 3 96000 48 testv/stv3OA48c.wav bit ../IVAS_dec -render_config testv/rend_config_recreation.cfg BINAURAL_ROOM_REVERB 48 bit testv/stv3OA48c.wav_BINAURAL_ROOM_REVERB_96000_48-48_custom_configuration.tst -// SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit -../IVAS_dec -VOIP_hf_only=1 FOA 48 bit testv/stvFOA48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst - // MASA 1dir 1TC at 13.2 kbps, 48kHz in, 48kHz out, BINAURAL out, bandwidth switching ../IVAS_cod -max_band testv/ivas_bws_20fr_start_SWB.txt -masa 1 testv/stv1MASA1TC48c.met 13200 48 testv/stv1MASA1TC48c.wav bit @@ -1247,10 +1235,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -masa 1 testv/stv1MASA1TC48c.met 256000 48 testv/stv1MASA1TC48c.wav bit ../IVAS_dec -render_config testv/rend_config_combined.cfg -t testv/headrot.csv BINAURAL_ROOM_REVERB 48 bit testv/stv1MASA1TC48c.wav_BINAURAL_ROOM_REVERB_256000_48-48_Headrot_custom_config.tst -// MASA 2dir 2TC bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case03_3000_q.csv -device_orientation testv/headrot.csv -dtx -masa 2 testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv2MASA2TC48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv2MASA2TC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst - // Multi-channel 5_1 at 13.2 kbps, 48kHz in, 48kHz out ../IVAS_cod -mc 5_1 13200 48 testv/stv51MC48c.wav bit @@ -1573,10 +1557,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -mc 7_1_4 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv714MC48c.wav bit ../IVAS_dec EXT 48 bit testv/stv714MC48c.wav_sw_48-48_EXT.tst -// Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_BINAURAL_rtpdump_PIdata.tst - // Stereo downmix to bit-exact EVS at 13200 kbps, 32kHz in, 32kHz out ../IVAS_cod -stereo_dmx_evs 13200 32 testv/stvST32c.wav bit @@ -1586,10 +1566,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit ../IVAS_dec 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48.tst -// Stereo downmix to bit-exact EVS at 24400 kbps, 48kHz in, 48kHz out, rtpdump -../IVAS_cod -rtpdump 3 -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit -../IVAS_dec -VOIP_hf_only=1 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48_rtpdump.tst - // EVS non-diegetic panning at 64 kbps, 48kHz in, 48kHz out, STEREO out ../IVAS_cod 64000 48 testv/stv48c.wav bit @@ -1599,10 +1575,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism 1 testv/stvISM1.csv 32000 48 testv/stv1ISM48s.wav bit ../IVAS_dec -non_diegetic_pan 80 STEREO 48 bit testv/stv1ISM48s.pcm_ISM_32000_48-48_STEREO_NON-DIEGETIC-PAN_80.tst -// EVS at 13.2 kbps, 48kHz in, 48kHz out, STEREO out, rtpdump -../IVAS_cod -rtpdump 3 13200 48 testv/stv48c.wav bit -../IVAS_dec -VOIP_hf_only=1 48 bit testv/stv48c.pcm_EVS_13200_48-48_STEREO_rtpdump.tst - // stereo at 32 kbps, 48 kHz in, 32 kHz out, DTX on, JBM Prof 0 ../IVAS_cod -stereo -dtx 32000 48 testv/stvST48n.wav bit @@ -1879,10 +1851,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_cod -ism_masa 4 2 testv/stvISM1.csv NULL testv/stvISM3.csv testv/stvISM4.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_80k_omasatechs_4ism.bin 48 testv/stvOMASA_4ISM_2MASA2TC48c.wav bit ../IVAS_dec EXT 48 bit testv/stvOMASA_4ISM_2MASA2TC48c.wav_EXT_sw_48-48.tst -// OMASA 2Dir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case00_3000_q.csv -ism_masa 3 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_160k_omasatechs_3ism.bin 48 testv/stvOMASA_3ISM_2MASA2TC48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOMASA_3ISM_2MASA2TC48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst - // OSBA FOA 1ISM at 32 kbps, 48kHz in, 48kHz out, BINAURAL out ../IVAS_cod -ism_sba 1 1 testv/stvISM1.csv 32000 48 testv/stvOSBA_1ISM_FOA48c.wav bit @@ -2023,10 +1991,6 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_10pct.g networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 ../IVAS_dec -no_delay_cmp -Tracefile tracefile_dec -VOIP BINAURAL_ROOM_REVERB 48 netsimoutput testv/stvOSBA_2ISM_2OA32c.wav_BINAURAL_brsw_32-48_JBM5.tst -// OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data -../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit -../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst - // OMASA 2Dir2TC 4ISM at 80 kbps, 48kHz in, 48kHz out, BINAURAL out, default object editing, 1SEP-PARAM ../IVAS_cod -ism_masa 4 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv testv/stv2MASA2TC48c.met 80000 48 testv/stvOMASA_4ISM_2MASA2TC48c.wav bit @@ -2086,3 +2050,40 @@ networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit ../IVAS_cod -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_48-32k_10fr.bin 48 testv/stv4ISM48s.wav bit networkSimulator_g192 ../scripts/dly_error_profiles/dly_error_profile_5.dat bit netsimoutput tracefile_sim 2 0 ../IVAS_dec -obj_edit ../scripts/object_edit/combined_edit.txt -no_delay_cmp -Tracefile tracefile_dec -VOIP BINAURAL 48 netsimoutput testv/stv4ISM48s.wav_sw_48-48_BINAURAL_OE_JBM5.tst + + +// stereo bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, EXT out, rtpdump +../IVAS_cod -rtpdump 3 -dtx -stereo ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stvST48c.wav bit +../IVAS_dec -VOIP_hf_only=1 EXT 48 bit testv/stvST48c.wav_stereo_sw_48-48_DTX_EXT_rtpdump.tst + +// 4 ISM with metadata bitrate switching from 24.4 kbps to 512 kbps, 48 kHz in, 48 kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case00_3000_q.csv -dtx -ism 4 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stvISM4.csv ../scripts/switchPaths/sw_24k4_512k.bin 48 testv/stv4ISM48s.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv4ISM48s.wav_brate_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst + +// SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case02_3000_q.csv -dtx -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit +../IVAS_dec -VOIP_hf_only=1 FOA 48 bit testv/stvFOA48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst + +// MASA 2dir 2TC bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, DTX on, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case03_3000_q.csv -device_orientation testv/headrot.csv -dtx -masa 2 testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv2MASA2TC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv2MASA2TC48c.wav_sw_48-48_DTX_BINAURAL_rtpdump_PIdata.tst + +// Multi-channel 5_1 bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot.csv -device_orientation testv/headrot_case02_3000_q.csv -mc 5_1 ../scripts/switchPaths/sw_mctech_5fr.bin 48 testv/stv51MC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stv51MC48c.wav_sw_48-48_BINAURAL_rtpdump_PIdata.tst + +// Stereo downmix to bit-exact EVS at 24400 kbps, 48kHz in, 48kHz out, rtpdump +../IVAS_cod -rtpdump 3 -stereo_dmx_evs 24400 48 testv/stvST48c.wav bit +../IVAS_dec -VOIP_hf_only=1 48 bit testv/stvST48c.wav_StereoDmxEVS_24400_48-48_rtpdump.tst + +// EVS at 13.2 kbps, 48kHz in, 48kHz out, STEREO out, rtpdump +../IVAS_cod -rtpdump 3 13200 48 testv/stv48c.wav bit +../IVAS_dec -VOIP_hf_only=1 48 bit testv/stv48c.pcm_EVS_13200_48-48_STEREO_rtpdump.tst + +// OMASA 2Dir2TC 3ISM at br sw techs 13.2 to 512 kbps start 160 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case01_3000_q.csv -device_orientation testv/headrot_case00_3000_q.csv -ism_masa 3 2 testv/stvISM1.csv testv/stvISM2.csv testv/stvISM3.csv testv/stv2MASA2TC48c.met ../scripts/switchPaths/sw_13k2_512k_2fr_start_160k_omasatechs_3ism.bin 48 testv/stvOMASA_3ISM_2MASA2TC48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOMASA_3ISM_2MASA2TC48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst + +// OSBA 2ISM 2OA at bitrate switching 13.2 to 512 kbps, 48kHz in, 48kHz out, BINAURAL out, rtpdump, PI data +../IVAS_cod -rtpdump 3 -scene_orientation testv/headrot_case00_3000_q.csv -device_orientation testv/headrot_case03_3000_q.csv -ism_sba 2 2 testv/stvISM1.csv testv/stvISM2.csv ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvOSBA_2ISM_2OA48c.wav bit +../IVAS_dec -VOIP_hf_only=1 BINAURAL 48 bit testv/stvOSBA_2ISM_2OA48c.wav_BINAURAL_sw_48-48_rtpdump_PIdata.tst -- GitLab From 39422bbbdcba18afa8211d4f4897f76936d030f8 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Wed, 8 Oct 2025 13:55:03 +0200 Subject: [PATCH 221/340] Update wav-diff to v0.4.0 --- scripts/tools/Darwin/.gitattributes | 1 + .../licenses_wav-diff.html | 271 +----------------- scripts/tools/Darwin/wav-diff | Bin 1701424 -> 132 bytes scripts/tools/Linux/.gitattributes | 1 + .../licenses_wav-diff.html | 271 +----------------- scripts/tools/Linux/wav-diff | Bin 944536 -> 131 bytes scripts/tools/Win32/.gitattributes | 1 + .../licenses_wav-diff.html | 271 +----------------- scripts/tools/Win32/wav-diff.exe | 4 +- 9 files changed, 44 insertions(+), 776 deletions(-) create mode 100644 scripts/tools/Darwin/.gitattributes create mode 100644 scripts/tools/Linux/.gitattributes create mode 100644 scripts/tools/Win32/.gitattributes diff --git a/scripts/tools/Darwin/.gitattributes b/scripts/tools/Darwin/.gitattributes new file mode 100644 index 0000000000..4cbca5d865 --- /dev/null +++ b/scripts/tools/Darwin/.gitattributes @@ -0,0 +1 @@ +wav-diff filter=lfs diff=lfs merge=lfs -text diff --git a/scripts/tools/Darwin/thirdPartyLegalnotices/licenses_wav-diff.html b/scripts/tools/Darwin/thirdPartyLegalnotices/licenses_wav-diff.html index 0b17bd169b..dd386d5ea2 100644 --- a/scripts/tools/Darwin/thirdPartyLegalnotices/licenses_wav-diff.html +++ b/scripts/tools/Darwin/thirdPartyLegalnotices/licenses_wav-diff.html @@ -44,8 +44,8 @@

Overview of licenses:

@@ -678,199 +678,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - - -
  • -

    Apache License 2.0

    -

    Used by:

    - -
                                  Apache License
    -                        Version 2.0, January 2004
    -                     http://www.apache.org/licenses/
    -
    -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    -
    -1. Definitions.
    -
    -   "License" shall mean the terms and conditions for use, reproduction,
    -   and distribution as defined by Sections 1 through 9 of this document.
    -
    -   "Licensor" shall mean the copyright owner or entity authorized by
    -   the copyright owner that is granting the License.
    -
    -   "Legal Entity" shall mean the union of the acting entity and all
    -   other entities that control, are controlled by, or are under common
    -   control with that entity. For the purposes of this definition,
    -   "control" means (i) the power, direct or indirect, to cause the
    -   direction or management of such entity, whether by contract or
    -   otherwise, or (ii) ownership of fifty percent (50%) or more of the
    -   outstanding shares, or (iii) beneficial ownership of such entity.
    -
    -   "You" (or "Your") shall mean an individual or Legal Entity
    -   exercising permissions granted by this License.
    -
    -   "Source" form shall mean the preferred form for making modifications,
    -   including but not limited to software source code, documentation
    -   source, and configuration files.
    -
    -   "Object" form shall mean any form resulting from mechanical
    -   transformation or translation of a Source form, including but
    -   not limited to compiled object code, generated documentation,
    -   and conversions to other media types.
    -
    -   "Work" shall mean the work of authorship, whether in Source or
    -   Object form, made available under the License, as indicated by a
    -   copyright notice that is included in or attached to the work
    -   (an example is provided in the Appendix below).
    -
    -   "Derivative Works" shall mean any work, whether in Source or Object
    -   form, that is based on (or derived from) the Work and for which the
    -   editorial revisions, annotations, elaborations, or other modifications
    -   represent, as a whole, an original work of authorship. For the purposes
    -   of this License, Derivative Works shall not include works that remain
    -   separable from, or merely link (or bind by name) to the interfaces of,
    -   the Work and Derivative Works thereof.
    -
    -   "Contribution" shall mean any work of authorship, including
    -   the original version of the Work and any modifications or additions
    -   to that Work or Derivative Works thereof, that is intentionally
    -   submitted to Licensor for inclusion in the Work by the copyright owner
    -   or by an individual or Legal Entity authorized to submit on behalf of
    -   the copyright owner. For the purposes of this definition, "submitted"
    -   means any form of electronic, verbal, or written communication sent
    -   to the Licensor or its representatives, including but not limited to
    -   communication on electronic mailing lists, source code control systems,
    -   and issue tracking systems that are managed by, or on behalf of, the
    -   Licensor for the purpose of discussing and improving the Work, but
    -   excluding communication that is conspicuously marked or otherwise
    -   designated in writing by the copyright owner as "Not a Contribution."
    -
    -   "Contributor" shall mean Licensor and any individual or Legal Entity
    -   on behalf of whom a Contribution has been received by Licensor and
    -   subsequently incorporated within the Work.
    -
    -2. Grant of Copyright License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   copyright license to reproduce, prepare Derivative Works of,
    -   publicly display, publicly perform, sublicense, and distribute the
    -   Work and such Derivative Works in Source or Object form.
    -
    -3. Grant of Patent License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   (except as stated in this section) patent license to make, have made,
    -   use, offer to sell, sell, import, and otherwise transfer the Work,
    -   where such license applies only to those patent claims licensable
    -   by such Contributor that are necessarily infringed by their
    -   Contribution(s) alone or by combination of their Contribution(s)
    -   with the Work to which such Contribution(s) was submitted. If You
    -   institute patent litigation against any entity (including a
    -   cross-claim or counterclaim in a lawsuit) alleging that the Work
    -   or a Contribution incorporated within the Work constitutes direct
    -   or contributory patent infringement, then any patent licenses
    -   granted to You under this License for that Work shall terminate
    -   as of the date such litigation is filed.
    -
    -4. Redistribution. You may reproduce and distribute copies of the
    -   Work or Derivative Works thereof in any medium, with or without
    -   modifications, and in Source or Object form, provided that You
    -   meet the following conditions:
    -
    -   (a) You must give any other recipients of the Work or
    -       Derivative Works a copy of this License; and
    -
    -   (b) You must cause any modified files to carry prominent notices
    -       stating that You changed the files; and
    -
    -   (c) You must retain, in the Source form of any Derivative Works
    -       that You distribute, all copyright, patent, trademark, and
    -       attribution notices from the Source form of the Work,
    -       excluding those notices that do not pertain to any part of
    -       the Derivative Works; and
    -
    -   (d) If the Work includes a "NOTICE" text file as part of its
    -       distribution, then any Derivative Works that You distribute must
    -       include a readable copy of the attribution notices contained
    -       within such NOTICE file, excluding those notices that do not
    -       pertain to any part of the Derivative Works, in at least one
    -       of the following places: within a NOTICE text file distributed
    -       as part of the Derivative Works; within the Source form or
    -       documentation, if provided along with the Derivative Works; or,
    -       within a display generated by the Derivative Works, if and
    -       wherever such third-party notices normally appear. The contents
    -       of the NOTICE file are for informational purposes only and
    -       do not modify the License. You may add Your own attribution
    -       notices within Derivative Works that You distribute, alongside
    -       or as an addendum to the NOTICE text from the Work, provided
    -       that such additional attribution notices cannot be construed
    -       as modifying the License.
    -
    -   You may add Your own copyright statement to Your modifications and
    -   may provide additional or different license terms and conditions
    -   for use, reproduction, or distribution of Your modifications, or
    -   for any such Derivative Works as a whole, provided Your use,
    -   reproduction, and distribution of the Work otherwise complies with
    -   the conditions stated in this License.
    -
    -5. Submission of Contributions. Unless You explicitly state otherwise,
    -   any Contribution intentionally submitted for inclusion in the Work
    -   by You to the Licensor shall be under the terms and conditions of
    -   this License, without any additional terms or conditions.
    -   Notwithstanding the above, nothing herein shall supersede or modify
    -   the terms of any separate license agreement you may have executed
    -   with Licensor regarding such Contributions.
    -
    -6. Trademarks. This License does not grant permission to use the trade
    -   names, trademarks, service marks, or product names of the Licensor,
    -   except as required for reasonable and customary use in describing the
    -   origin of the Work and reproducing the content of the NOTICE file.
    -
    -7. Disclaimer of Warranty. Unless required by applicable law or
    -   agreed to in writing, Licensor provides the Work (and each
    -   Contributor provides its Contributions) on an "AS IS" BASIS,
    -   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    -   implied, including, without limitation, any warranties or conditions
    -   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    -   PARTICULAR PURPOSE. You are solely responsible for determining the
    -   appropriateness of using or redistributing the Work and assume any
    -   risks associated with Your exercise of permissions under this License.
    -
    -8. Limitation of Liability. In no event and under no legal theory,
    -   whether in tort (including negligence), contract, or otherwise,
    -   unless required by applicable law (such as deliberate and grossly
    -   negligent acts) or agreed to in writing, shall any Contributor be
    -   liable to You for damages, including any direct, indirect, special,
    -   incidental, or consequential damages of any character arising as a
    -   result of this License or out of the use or inability to use the
    -   Work (including but not limited to damages for loss of goodwill,
    -   work stoppage, computer failure or malfunction, or any and all
    -   other commercial damages or losses), even if such Contributor
    -   has been advised of the possibility of such damages.
    -
    -9. Accepting Warranty or Additional Liability. While redistributing
    -   the Work or Derivative Works thereof, You may choose to offer,
    -   and charge a fee for, acceptance of support, warranty, indemnity,
    -   or other liability obligations and/or rights consistent with this
    -   License. However, in accepting such obligations, You may act only
    -   on Your own behalf and on Your sole responsibility, not on behalf
    -   of any other Contributor, and only if You agree to indemnify,
    -   defend, and hold each Contributor harmless for any liability
    -   incurred by, or claims asserted against, such Contributor by reason
    -   of your accepting any such warranty or additional liability.
    -
    -END OF TERMS AND CONDITIONS
     
  • @@ -1725,19 +1532,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    // Licensed under the Apache License, Version 2.0
    -// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
    -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
    -// All files in the project carrying such notice may not be copied, modified, or distributed
    -// except according to those terms.
     
  • @@ -1746,6 +1540,17 @@ limitations under the License. @@ -1822,44 +1627,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Copyright 2015 Nicholas Allegra (comex).
    -
    -Licensed under the Apache License, Version 2.0 (the "License");
    -you may not use this file except in compliance with the License.
    -You may obtain a copy of the License at
    -
    -    http://www.apache.org/licenses/LICENSE-2.0
    -
    -Unless required by applicable law or agreed to in writing, software
    -distributed under the License is distributed on an "AS IS" BASIS,
    -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    -See the License for the specific language governing permissions and
    -limitations under the License.
    -
    -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Licensed under the Apache License, Version 2.0
    -<LICENSE-APACHE or
    -http://www.apache.org/licenses/LICENSE-2.0> or the MIT
    -license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
    -at your option. All files in the project carrying such
    -notice may not be copied, modified, or distributed except
    -according to those terms.
     
  • @@ -2037,18 +1804,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -
  • -
  • -

    MIT License

    -

    Used by:

    - -
    This project is dual-licensed under the Unlicense and MIT licenses.
    -
    -You may use this code under the terms of either license.
     
  • diff --git a/scripts/tools/Darwin/wav-diff b/scripts/tools/Darwin/wav-diff index 8cc94205eef6c3df56a89a3a7041b10815ed6635..e1cc92d32e32cd5f05328098218046a35d39533a 100755 GIT binary patch literal 132 zcmXR*Eh^5;&r`@KDJdwn($`PVEYZzLE7nWTEXgQM(o4?I)h{kcP1Y|ndG jNHI=HHcvH9OEWe%Ni?-c<0{UqN>wm4H#D~}H{t>SK1?Vp literal 1701424 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 z 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 Y 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%0A&Tx_=`tx4I8KtbHSt6h4}$!$nAkg=dIUq;P#6v z|F)Zs4IddD4?bq~WxV6czwM;s!H29qjHf`M;@j;d;nV3a;oI#c;M3_Z;M>iGh%J6s z{%r>w4?bY^>~&#y09sDaeEfmu|Ff`x%NP{_pY9?7#|?~*8yS5&ANg8-E|T);=5hqL zJ9tza`6nIp?R?~_aI=)zv-1)pWyC>KMjRq#gmHjQI_{1T@BvNtcb@XK{LJ6v0`g-w zBgiZtO9s$%jA!R5SA`q=t)QuV&(1?Hl|bRqd{6;gLO6krKRV!HdCGx*@^O%A-_D0V z{7$EQy8|VBI>RJQ8fy9Ir_Jvl&EX!&%CfjsBKcmds;f;OF+}6?L8gnumJ-B6tQ91L!WbZb-wRJM_Yf zUE7!#Kr=`Wz|*CW@p5oM)E#cHk&TBQFUUvtdZhr@#-tYtu%R~Ir4)|F5C-6@?q;SZW-}MmxwnHAxADCVE zx1EIdE=N&=+($cIQE#&I3@>isV7Q2OShXLe=~4 z?gOY5?x1kp!QiJ4zw2l4l<5!f&TzyD3&;gFWPX_7{(b{+cbusG@hl#_Jf5HddHnn1 zw=5vAKfZVt3&RVeTF|H(e1CsDNKCF4-~N7fk6s=IkLI7OkglZmdIU9Mnh*T*X#T-k?(Nab3p$Me zzG2y;JDdaD2#P(7VDWE*3-WJ=GvP`-S}#>F#U3_15PSIA|Ns9V{{R2~%>V!Y5B~rE z{|%Ie^+!PV^KUz}QR@E*(Aod(pzWVNy{vCRHFGjggD>Mj&`M}f$JvhqJlnGsG^qCf zq(^T6=t2{ZPCpL*Z44fr?i|P1{bLVT8hN0GHh^T8h;y`Z5tF_3Zm@FkN4YzLnSa9#tgn0%(; zaqt0$2j>S5(27a8(>8+cJ7ehJ=ihd!)1UE}J3DB74`_FFr#naNVcGxx{}+L3ga458 zI$l88AbJ*Pu^Q5PiiTetr5*^MvV)yZuCoy4FqfHPgw{rXko!CMV-Fu=7e=`GIOsSo zP-he5bSMWQ3tEch&H>VdIX?;-333^@`3eKf`HCNi`HB~4^A+EGu+LX~EK_)G25H}d z_u`!JxcI`8@tAMt8OZSC$8rh7Z;qgqjSS!s{$^Fs{s?ha28IdVp!8G#p7R0ET=;gL2MuDogZgNo$qUHfxJ%~`pI#pZM)3A-f3n(;tkN`@jg() z1Z;cDGH_vk*rU6&!=u;a7}yoQoi`xi?b~?}WSZrPqCk)T2RxdONqAZwFMau1&!-bZ z2dI35t1nhWRu4Kst@DS6wd(=T&Kvwq6`)mx0=}I;;Ua0ETQ4my7fXUWY@pSgMS{Ma zhdnzFdR~0_@-28?;FwRZj~OF!1UN8&`X3ix_;j8FPX*bSd1xN@0!KM)Cg>kzCg=z9 zOb|GcG<^rnIQOzZ!`a3G-&~MSr|$xv&aI%iAdk+{hSyHMo$p}tLEk}h5q)bwr9Wam z$fp~$Lb0>~X-){N8#I-}-&Te)CzJ=8*8$B5tpN|Z`*yzfy!akICnOJ=6Vh<$d;y&k zl0}&l0_`_Jgh<*1&@_bpotuh?$UG9hU#?pJeD$DH2vb$%K|zwgu$m*#lZtIZ}eyt69atS=zdwQ2V~9&++XwT^?l*l9isBW@SEX( zPwUVZp1mO|FC6$M9rNIKx(Mpscyxm%xIF|sy178}EFPVp+KI!Xm#5yhH|OjNk6xZK z-`amw_F9!108q)niX2|@;I0WnH7R=hw$k<#^18_@BjbM`5w=1Uk1YihL=37 z`S>Rt)E%{4c2=0?5yMP?PT-l6$$d_HM!>5d`tp7y_5a-|No8? zpy>DQ%{j{8Yk9VG?aMH*@sR!tWPrsFV}7S}1%LYjO6C~A>xt249uVu#!0m63#y6m| zn|(TcH+X=XT#z}2f1o)A{$^KDHUQ5tfUei^?fm0;@eOoF;R1it6VQH^ZZ`?fZa)vh zONQS(t=ae|9|A>;XK$T{XRn)qr?sDfPcKWEcdyAYpI)9I(BeUUr|aImJm)<4oql+L zW&p(fyLry}TAuRkJnh5pbj#bC<(Ln@(>)J}z?-gWvf)*n#{`*FCzK zJgu2QWhcMWAy0m%%U-=KPa#D!q{#5;HL(B}9o;S#5|C{YK9(M?hL?OgQ$dS^KtdkY zES8S^lR5y*#@? z$Emnjb9i>MfiB$g=wz`5yTrv>0;CtDNCRS~i?xDhHxo#}z!U5Y7i$Yoa8J+0+QGA% z5hUZ`3AWzFI>56V}?u97mgy7Dj zKAkUpG(Uo@@Yu0>6$E(juRj4A2lwoD6@ac3@$7bG@a*;l&EtU9c6xMzM=?B_Yd3(; z0dU>m(aqp#?aIL447%>w$1?Q*e^V(t=w3RW6i|WR3E2_Z%X8Wl)cFBzoeJFnslPpX zO@cgoO}>F9S#0>_L1ULMJi0A>x*a$?t!o)PJ5T!XJDv9L6{+{>ju7zgjJYV_-|2D@ zH1u}Sv-vuMNAm#*56cfl$9*gxl^A*ViqwNf(mgs$F8)9aj9>f!5_sX!eDDQGy-(+( z*X_{q%&V8>FC@2!IDmVn_g*9~1r4h_^U?eVI*t*tn-5fcd-mEK^X&C!^t3$V0oiX{ zDhOI<3Z9aK^D7$a3 zBa^4$LH}MJPycR_(;#>FcgLIt4U>XKPZb~nKBqy0avC1Z#|=E14_yE?Z7dI#t_9Dy z=y~_@ICy@);N2_Y;MrYs8MMq;dDx>9v|*|i6#0!7|`QCp}e0o@3C_3(I`Mksk6vN)VB4yAt z0P;21D38t>kY`OUd;UKL(&5|r{B3n7QAEdx@H*|(X-lMxvz@xKBz@yvHr`r)U$Hw8&Y2?vc&g5fw-=o*2-nX0OC&+By zZl9kZX$hZBF9nc*XLrp{(0qu7NAn>AkLH6HK>N@vKa{Qo4{)k`fLfK`uYsy}&+d?u zpjnSYpjnTTp4~ntL9-r*K(ii)d@aB5H{E7N%y?`7&3O2BUgK{r0IiUa22Xf^Cd8qU z52=t}xPWFnJee6l6FQ)B8ZvqWn%V`=bRa4n76;F6A2tDxP9J6g&u$(z&+ZU5P&#Ag z@aW`W-US*EMU;unw?F~c>%<6c1bOuGfEq!sE#d1SJUVaqbTasKgBSYwbo(~=bUJW= zZfEnceD2x#1GFI8qxqnOkL7dz=3dZHz`^It9=$B}KAp#XyIt5oGaSqUzTGTrzTG}- zpsi=j96p^a%s$pG%rA;}`!Ie0&B%FlgX$jF1|P^uHP24OdReb-7w#Wkoi5x0Al3`N z?hx(|ew`uQ9A2F)+@8%pCHPw+L1S*cjxYSIUAQlJb{;G`?ZJ5CrQ84i{}G`A>K5@> zdsu#gOv?B$e)sA1g8AB~m&F0rfAr`);nT_C16hCQ(;eCXn!51m3=r_J`~+GI(tMG@ z6I|qdg8BCm$iG3L^>f`m>;gWWK8ylB-8}3*-68B8KA`zipH3b|A8Q}R7sb0h7=M6f zxDoyZ@AdQTJOmGKuWlDXkbeb1{uO-T*BvAH!LKt$5aeG$&*oon|2nHp7^#DW?#KHY|{hL>CoZ+n1N0h;qqc0Is9$#q8&vq$GYpYAjP-_8U9 zpKdjXkp&#SoeDnPMn2sdzLxJC_kbz^&*m%tJvxtpCg7Ta!Q-XZUV4M*<^vMoTxM$s zTBzn>c^owP3mWwF>^$Ye?{pnBo8{XX0h_b%?F``X?UeBEW%2atwK?w5Yf|RX%k$C0 z@_1>CNAn>G56g?C!Jy4s3=H7&gP@zYJbGDW=YeMsK%HlgkSHV)7+X;5PX zHYN?anhrEf>D6mu?bR!y3?6@e`C=s~1UnD=GJf}E`~q%&z{aHeK{DO77d$(UfyNxW zxjigT@J~AA*=^=w`GJ4Z0fP%3mLK^i9W=P;$?tlv<$wpj>wR#44YX7renKxweF_>s zbOetdCwO%8gBJDbgN7l&(;~-RK?4~KpvDb&K=`;TXe$Ybf5W3Q^Z|H$<~57qNzi^9 z(55xt6CRzeFFbl18Q=$9w>eJavn%qubY4eXbnWS zp9Co83Ozbcd4OFAl65sa0BRq0??-anM~}`^KAMNX6DH13_xFS3J-R!Irgnl=HXr&? zddTBA8#v2#)^UKYaRvFv12iejz~Bm+K>UUjv`tGv-Tuz6$f?hTfx)MHImlnWtxx#D z=Y4wqKjy-}eZK%B1H+43vp`wyd77*B9u-gw6<-1^9fd|sYbt2)p!4BtKcCL;kkBr2 zPMhEXIyRsI?80s$s1C47NCbK`AOBIhA2clOaoi1bf+mAUCunY7*{8FZ!>6-a;I%a< zl*6H+ToPvZ4P+n0mjA~*EI*Zk!q-Fd(`!4=&VN3g-(N_8wyhlpoe9Z+Hi-i^)rv@0FJMh zv;Y48|DtIoc$eM8Y8HkUGIfv=&~`S6#|P#itb>j}hXsQ&R`YRz7kfeZxcT@065$UX z|Gha%dBDfJ#l^nVn+ZD540JAI=P?hE0)B`B4p5F2oC%7^?=RA4AeWus^oJ;a6hP;& z^_Jd1*beI7ff9V@y%(aOHT~VLH$eN?K;_2^eB}pdm%tl-fdt+4}=MO-hu<}$qP0WCxg|3k}1(4lOg zU3TAGFEH}AfUb1_oo=VWzr7u_83(+~0yMts+xgwI^Y6=7D9ai7p-8*=b**4A3*#fczFO; z-`zf2316aPhfiSfLtOROc^T8jb`@x%*KqpJezAkr?7wOT)orhm+nhF}L zZU)PC-UrQwzI^SDR^EV;hbJiZL21JSv?l#I*go9p!}N78DA&UdQG_)gUb=vt{rqJP zXhliqbDvId^5Jia0Sz5b@aXOb2N`+k=cPDk+V0U5aQXDHjD_JvO(nE^`VHdcfq4ku zBg&`dHwGZrcC+K2Kej1N_39OYOb_6nKxPIH7IwN`z%hZmy^+8KvKXi^HH`yB3w-jM z2_&W%hgt%nmPa1VZ!$c({Xm;zOH`ZdJQ(>~*MJJ#ZZ{2&?yvw4%?pstrr_HWUV`@1 zdVogdK&_n}ppkdbG;f%IPj{SzN4J-StKrFpDo@bX)q@_|UIu=>JSTk_5Bg|(X?Qjt zWby2kVet5W!t?(DSIF6pknsgq{%r-|^Z(c!8~!ntySwsl`vy7~vH5wu=jG?VoWERK zzVUa0&S?Vgc|73JeBgi4De$coo}IT|%XmQCYS3)OSQ_Nhoh9JW9VPKP7JPj&thI35 z4b(1Y@Hp-k0ckO~fwmkmcy#iEmbJYE-6{cI_sZwd=>|GBpT8BfHt8j3*x#c&3Vg5~ zfAecl1E~3sfM@4bkKQl^k4{hv)OCYTXYLM<&e#JUouMZ@T5p$>gLeIZwsmGCfTR`nfm@QyKn-Bjl0v$KE05y1}Cp;WH0V&3_yk0MGzQJI?OU@bWjPiQ35q3Iot4Oe4=;um7Ib-U7w89@#ew zJei|8UakTsj#f~A#-rO8G&gm@qmvzUdc;d`MSa{2H2nAh8 z=^K1OUM6Uot~(lZ_~LF*nd{p5$MN73X3yitKtpf*@(iGwOvBH5%4g{Q&!dhFcNskY zAM;>|D~+2#eF;fc{aahE!Q;3aXz2t4$p4HU%||j&R*Q+=%wd5X zg8^z%d?~`#{`c(s=h0lx!BE2C*=_e?)&x+y8MHF4PQtf0ozbK7{shnFKa3?JKE2yP zyPQ4$pLt;oInMs|Gf)FS473Wf8M(LgGIRpy1XpCC!=9bT;r(;Z|7S`>e0$e{j03d* z-Y_#T_;$W~{T$>-$qC>R{zZO26T^#D<;dq1K>CZ|-T0st8Ib!Ne0ukT_I$qs40 z4-`JnU-jM?0&JrHqZxl!ugTxeG>;j23R~vxu z%c!<^u?l2dcX5PAcXR@@-DnHSA+5Jdgg|_7#DaFLzia^O1Vt){3yM%sSpjasrGgCU z{QqKiFDQXwl+O|#ouwB*hZdKCsvA(L3(8py;3=Qp%KsjXhdn^Y((VQ2cc0z@#@BL) zxCFIlp$a_z9|fI_TWsKCxp~Lm|Nm>A`}8gbF?>3ozmRzP@4rWP^#PCW;u9~FK~C+h zzVPBx`@jFM4|p^mkwDHCpe5MddsH|;O{wlJDgsOl3@<+PGBJ2I9+3bYH`V$4#m@GB z|9z};FL;8d;u#ni8cIb$`*2^}=wV`jyXSn#YadJFlJ}r=tN!HQ|JP4n{3u}p`#(6K z*KZ%U>Y-L6H)B7@nVC{ejNk9^LJr1sLE24>AtKd6E11|NrLd1pSg2kLKOr zMd)CwKxgT_SPeFAH+YN!EDG8N{$eUb^aa=fAag*+2EM3=h%$hpsYJk|dp8FoXlN-e z_GM}ha!2NH0w_GbgS(pFUo7uNt-X==6NiBNNZ8!G^Aps~5twfN-VF*wbT>bQh@!dq zG(;5QW{^)nZho;9A_~f14E!yi!>fF{H(vmqWdRz$0PTtL>E3+<%>Q3v>f4*f@md6t zBtiWf2~ht=7}~FS5AN3_bTcu$egZ1b!n#2{n%ys;2AKGCZ+-w`gUsOp?H_7Bq5#!z zqry<~!Lz$Y<;M#JxPCE+^FDwBy4t6EGibj*s4<%F)4dyXMgXYT_4NfEyUh#AAH|$cz-_{Ju5HQuNAGS>XumMl z1f5<8QBrc(qjxvRqc3_FgN`AD+FZhqu-rXS1i|HPqvkjU-IRq-vy&J@NVfO)d(yQqN z2Nha&NrQ-@CA|QMC|c690*Q9s2RZGfUMG0e6dZ0Re*gau$~ID+pyK`ei-Hb_T<854 zpo<|uQvW+3QlTA83@;`WfQnj3`xEMaP+5a5FXg_6dI($o@4)7vsSr^#57k3NAs#}_ z{~cKJ|1Z$Za^GJpZ^xbgLF2!m^$?%|!_I4-ptFvCdo=#42VGLro(L+6dzXTaHUzE2 z0ktYWz0mGpVsJXvVhR#v0Nr-&+5C^M+|uxyPp@ie2@3_s#H-ACHOdm^M zf$JT|txTZv@xFmJ_&*1=hI^;RgIbf#kAC=QJ}OZ&{KhZOfY3h`R272se=2?D+3WVv z!}3rW+v^Z8b6*1#r19soa`u7~BP2dP^XK=7f?Sx!pMN2Z|M$f-{?`}NJoxLbdnkTS z^W?V@yTItF_`}okLn)^R_;BSzWv9W;^XXMxU(CYbp?R^y+oN|6*bu{S4L0TsC0rgL zcYG}U^xDva@tTk2w^CV;-n}4OUO2Ra(#jFT17P2k$h_obWPl8E`1GpA7qc*c#ra!7 zz6P~T^+Ch59iYQ}N-X)evGy(okK?~M+y*k{D5x*20p5A&)A{Yit^g*67r$GX7+ks) zJP$rm@N7QA^l~i&1A`-^x(1yD@DH@dYhoJ{=x}S0gFxjLXhXuk|NlWupU$nI1A=@y z*MbfR^6A_PTKVSFxwHXv7{E4GzQrsI`$3DXUpRq`0CmhjC9r4b5f8|br=U$u8Xn!? zV{}1m1&{7lkVzmm=+sJ(;UG3>`Wa-sM|Usih<^C~C-AmAWtWa@EN9vIbp8kXvIOMy zE0Az%0iD17`V9CSt$kpJZS{wCsGWLQ7+&o42cN423;lbblb<{9zu4Ep#PH&04if`p zegiW8*Ilpi;w>nhHCGEjDt(C;=B=PShh;oat`#{OAvCbTG$4-;er$o12JS5c@>la4 z4e)G4>m~jU3s5x#+H`!~qw{x(7XLQZ2@64y|KGFo%ZtNc6Q6l@f|GzSGzt8BvE7de z8r#0D{~h_K908xE@ax5VuzJw#_PHl~I%_X@_qy1Ey2t!e4!UqWEa5af;M@AGl=rn{ zgM~i-R1l|x4|EPjgQb2ctE=IY*QPB8O6)y)YY%{DY(Zo943L>%pU!_TmKT63(cBY0 z-KiIxkGZHa=z~tf>;_qvdcjxoxv%BFGTzs2zMX#{oN{Yl#;2f>rS8%lp!CSt!o&cc z3rf8JIy}PzwB@&x*`w2&!>2P@z@zzy2B`jm&x+L^K(YTB$OsiAC%j(h(QE76&BE|v zpD!f(n`;k%W3=|fi_~V2(ES&?n?Qv!r2Xg7{Kmth^Y;r$s2q1Q6GQ8P5}6n4nwS_s z>y8e3fX+K?KFQ<(S_;boPS*z>y{u0y{{4qc9j`uAp662Rx3uf@@@tUeoI! zrMVDj&i16>L1&qxlV_f6yJD0q$OdjtKMUb}s-==-dPKzCja&pd)a6FMx(* zJADs;j@A2q$K&7w7N1_5hS!RSwi&qm_h>#G;Msg+gJ<*c4g4*jaYv8dU;&SA*5*kp z44}n9_x@jS+zXnmSix8#;KTUHqxqL0fAco5T~~cAzm^Jv`n27p7eJ|CQzJOytUa6m zG8HXJYqXA!FP#fox7J{xS31SB*Y&@T<%iO4&*r25JbJ5{K)d05I$yo025aMQ%?B-{ z-{$Pl{6nZ*V<(dU3j_b+gT-=&m%tJAn$7UQYbHaCE=sp6Xo4@GyV$^^J6Hp>@mKS^ zN4JBA2jd6OVK$b(Jo()Zcvyb)x~o^hJ7F}fewr5JoJJi8I%+u^$|F{ zq2*CKXoC-EJX8qU>gnAL5_!>K{~y#516BF3=?T%t|Nnz#O&bqGMPD#KhSceo>zNo{ z@MVCb6sbKQ?9t8V(;dd)+5C&SOxL$J>W@#aCyP%fpHF8TM>nHm=K;rqFIXHKK^sx{ zi~O5^{4YraU%U2N=C~`!6%5B*pE0BzcYVUZ(Czxz1Cs28LDN15UvYSJvUh-Xy@F0R zwdio?_c-`cBdyb)w=EdvY0o`Op`i zUQZsdgTg@fwK{UU@NbXg;NKR>b?~u(3+L@_PZp1Zk2N6I>)!MMU9ao7fx{Pcc-`ru zmA<_qoIagmAm4ya-z!UWz$xt&+p>;Bx^-&ijttkxZTkpKEv=e9Ym)`P+wo z&1a6+acRe0Z!j<%cLgnBVc_5HdNU2=v$RfkrsM9QQzRICI{i70xod*1_i~JnJq#Y? z^yxhL5_DuO|Mp|Y*abm*DPG?JM~SUZZ{#nqMa>7<}MxnjA)0-hI70GO*eTqAO7dre257YR+A4Lc);w!aDauwz1M^j zwCD5NH~wu$95s%)8vbuN;K;x2m?OW-asF)wJT(s&vmRp?e!bn3@ep)hut#UOfJdjl zghyw*f=h?LhD(RLW?E;t*2|~={{JUwyxSVKUy8*8|8O@8f#L2GSpyud+Ubwk~ zMrh!}-Ps^9BX{J&7u3H4=U;ev+4zP*fPujS$9bj-3)vZVf(Sh4nKClsIL{Q+w12TL z6Lj4qmh(()N}=bOvfw(;)OjYTG4cFGC1@L1>wyw{=b46Qf-V!ma-OLKNOQ04`8XDa z7eQ`F=b7refofUsd8S{gLCpIvysAOtUmo3I93HKgN}N#xs+$jVGJct<59mmZUavnM zy^bs{-GZ*2mmLqjW^ru%4Jw(7A|0E5{Vyqk)CaFMJdU%2c7+^CWBkdGM)%XY*NMkIoaGoku(mzTt4`d*XxT%uOp8O|F%eOP=VyazpauBS}?eD z7qWl~2G4`fID9$(`SP#1$Kkk{!&mcT(Mqpg9s>_h@#@C`I%%{l%d^+-pHFWE6aRJz z@byDSK;_aOX8vZ-bf{zVuMhl9Z$Modu+^2I#$a#WH=o`BF8*yEoG$#^E7)B4wm9IiG{hy5V1Qp5x^q(1hx7*A<|W2{fF}z`s3oC8`%s zf>u&&zvRPt$m8H60q|K-93Gq(9r?Fi_24|^dGHAb|F#3*d$J*mAz;%Ro#7lFo$jDR z9zhELkXmSvpnKU0%FxZfS@~N*N1TH<=Yehj_iX;h%-;$+s>GxD05e3b;mMbQ;FkAK zX8tzNvOVA4v|k`Ec{Cqj1Fb9vEuR4G`*8%7fO{nw7#Kk72iifWBJ2PagZzs@=g))s zrvJ}){y*f~`P9|$iBIPRAI3vInx{NE!$8gi4UzeDx^wt+rh|?qknrdZz2MOquHez> zuHn;}Z{X8eZsF5e@6h4z;nETBnbsNa^-}deWO3`kHylu>UII-n{&;yD)Faw{*s=47 zBUlb}r6Xwd@1}qM|3g)t^y$3tx`uz72eTv0$>q6_V%rf^Y$v+#Z_8lv?XCL*iteHW z&*uN^{B0iKsIL3s(^-4Mg@0QGw@+v70T=%52^>D1xfeiMxLmp`SUj2!@HifPD&Wfb z)brp|XuQAo>CU|XIvB;5f6a4_*Ba1>HoWvw0yH<<{GXY>4b(vJ?XCL(HXmXThpXWe z$Ny&?oBy)%x9fuX@}0E@e0puWnLszU=&bkYwFTXk?92Gq@&8ls61(O<@s#-&>Wk}#ig_;P}D@9^bc^AzNn9UyP>H=P8> zeC-8x{x*J)-QBq-Je!YxaN*w;zz;fC!i9f(0FO&&C@3@%_*}XJI6RsU2!Kp?VNN{( zG5ZmRFXtmr_rsTe%_GpM6c=9hf{i`E@=_aes^SSq^)CAdbe`LxA3hKRGuT1Kv$%AI zg1x}z(k!Hz)M%~WoD@-Ji+0d1vdH!=#VL}BgqVBPy^GW863{b89*zOs1nYvy&d;DfmSLU zc)0~MF4kOofw{~J4qhCO=1&Tv(Y6YM22s%3wq(mLN63{)cKHZ>0MQRVcv;(b* zX+FU5GUFGhZ7}iQ|Nm`&|Nrm)_y7OofB*km|NH;{9jK6jZ&-x3ACT%_4e-{;e;(aV z4j$copb>MQZabguG|*;YkIt>2=}G<;@L4w9^FYm77yj*yptV@8od+EcK4kVde*8aZ z-XCrQ=>9it59^knpk|^Y|MWwS4YwFP{vY>jKKR42;TL4S@Q`ckfl`jwVxaw#oyS2_ zhDG`w-90}+3zc5a^*H#P#iN(SvcbmEztkMMHjt6OuR{?ODHR+qAG0$sfDRvzhm<3( zhEKqm{0)a=!(abW*_VewcYAef_;gorfXaDL@ze0vpTA`_I|GAjM;?c3XBsDHpW(q5 z296zW{GJEjD5SY|rt$iA)(LnXX9K0&m&ZgH7(5w|gT?|pj)RA5K&7L{@mA1C4`}+@ z<9I7*uNqVoeAyPLHagx4x?2k*>Zy4IbXfsE_^NfEUdVk2y{5nYSr`m&gUZMUpu@u) z`L|g+?ggy{1f4O=<{LrJf;=gZiHKPybY1ftmmBP&j z**!Xc_;&vGX#HQh`=ur5a3|18qU|CK3?87>gt{UO3_iUM|9pF^nH>4Ic{6sNZ+>3i zaoZ^KbvpdA;RirGYPK9P5rS_+DuK zwC118{O#FdprbwygG$*y{M%0+e8>k`-4i!d;NH7SuTI}^=;@=j;*m=GAdHuoX?EKpg zaQ&HYa%Bks8~T!0;)-y4>9|89`tNJ_!kyp2RKi7bY4sY1@9RqP;i~( zZ;1!n;`raAH;~bzGxP%gHp|Xm&5!CGTK-pX`gDRP`(GD;L)K0ZBq$w& z?&JCY|37F^lnP=9BW(gA348uO?8|ws`5-$e0V{ie4~d?}%)sEvzl|A`_OCiN|EMoH z@7j8x13j#vp55Ri0$MQ!x(le&gu|z^hJyn%Sl0>G{u0!d^61_RGQ*?!KP!JLs9Wya zd6j<~t57cs!~OsU28I`I1>h?X_*;(4Ffi-{o%Rh{(Am2cG(Z2@V<~7j`Tzg_ppqQA zU*6+*D`;^ESfqC^`1+e((?#Aa3@^@h|NZX)x$w-Vvmn5yvmyej7*u3{2Ee5~y1@qT z2kG7gDw%eIhE87WY5Du#<2X3wg9ffafNnc_D-AlT0CYYQi$^c`<~*NH6GorT8b%I}|HnN#!NCF2 z4B2(^auw(x+}4x)EtB~{2{iG)2l$R!*9-jH1ff~Vr?-{k@Bjamap2{BrF>v@|2%pF znILIC5F=-qfK__)fo}T&##qA1Etc03}`D&NHB_)fxdx z-H>}=eY)3zqRyv#FDNc{g6@I$=$-llG|vY*s(33XiGX6L`3Dn!ipFNiHZ|jW#?b+>}1FDF7BmVyX-`P6@Y(cBX zZ_tp_+5j-MH{$pI|6t=idZ#9US_Yua%;!M1bk1Gz8+5f9sE|zSY~Ao1G>7OqqoZ{b znAHk0y2B~FyA@<^r&F*;FXS$)-mM@TUi|6$`@aQrdf*CJvhFo)^<-h-Z><59Ev--Z zTR}H!zvec)4H~)5@?lK90Ix|6Z~JuDUU(e}B7Gp|oq`8qTn*oXwu*f7*bUEWoX0#l zuX;4^1!XdZ5;2cva5?}_yMlxFg;XvRgD>Y-AH~038g}?d9On+1mjMqi!$It;G!xCg_&p3J6odqq|7M zqjM{0PZsDJV^9!xuLZSU9Qn6x1x@+;^t%5wJPAH2U=sA;PY$2XUPxqsqsF5X94DYX zoM1z%Ji3EC8hmhN!KW7-uaNWe{=Epj4oZWqAQ}E`y+1+ex%r2G zsW9~3y>AB@OKd4gsZQYKEiO=-feF@jnNrCgwc(R{kABwma9bXReB zHvebiZ%*e2)moqxfY&@a!A|q&>;>88)47!cykcXo0GL_|>fHH)8YM=c)2w^f3V>bK z3JFKo8J_>odo~{v*a5j}0DR0CY|_D}cPXfx^5~rl%H^Po17NDa^{GF2%g(bGX%(O} z+zL?xF`%~>w44XDLmGS>U8@8rwmN$iej^{S09s#uOaQbJh`(8p1-x1CgJ-uN=tvc? z?oNo2-K`)Wz?UV0?lb_!gh%Ji7dd`lo579*UqCw>yywuD!LvIQbP1aRxNt%a570^T zApdtxtpHQtf*5kTv@e54=T?vo{_V$_e-zdQ*%~M^F!1+-xc$p+95M=mUl|eHr-K|h>fKvBMaEu=K3%c^NodqP_%WI3p7|C*w zm%zoUNAphs{wW8Ve+q!_1BV#a3%)k5+xG&;K~ORO5_Fm-eEpEEA;`ropy^>-BSi*= z5;I$4kVq@2Lj-l_jxx}4PLMmf`CCEzFFfGx`~W)m6f_A7RoV+zD$d^;07}VlrJ#KI z(iS8H+T!%=MN%2a*^meWT`+;305~8CKmfFBlntRx8(AB;b%$_G!{7h^U)}=8(tV%K zXD|Mj!W=Hd-wHZ}9HA6cSiD>hQrcWQgMq(w35W@C*h#o9-BNX!j^p6e4{jMiN|GHA zw(E?SnINsuO>{3om6_vyaL$14T!3Z(*A=@!qK@BOR5Tbtg?p)_M>jYZdTO(%cpe7{ zgD0U}R184P36PJwdqKS?(489~D>{3@39$K>9)Bz7)>y;a9^I}hJUZus%1WQkr6)io zCpb+n0}WMlwjTHe4qtBiqvj!S^4q_h}}8$2G}S_hV2GtSdY%u2Oz=D-WMPW zhczI5AZu>2f-aE-6~YYst;axScFzTs#Vu4-^zpfBydm z&1~_voCY0*aETSv{{lz{x9`G`oPiS z)4dcFepq6uH3}5Pi0Tbwphq{@rjlsGlfDR3i&l9u9`XP+-`b2pXTo;&f^wD%|F&LG zo$1kA^vAQ;iN&#-tMmN9_biQ|c0E6TQz0{GQ1d^3OFc6KgX4ZsBlo2&Xk#{{Rs`K{ z;<}>K6?C;AsNvMS7hGeOBzksU@o3)50IF9(s{;3O{Q3X?MSTir-NIf8xTpe1G!r6f z02j3Yi3USNJwT#9osU4Pxb_BsB&Tb;dr#Ht0kM7n4 z2r~k76*Pny06jFvqq7y{eeg1a&Q_5BLDz12bhbJ`3|RqI-UwRT>CxS~0Kx;!6oR}4 z;ek4Zpgs#IIdrxrK=gHh zg7N5VodDs1l8Z-Y>kJ6b0HS>XnCH5pqjd$S*Rsv^d`IibKmY%CyIx4^Yz6g6__w(( z?`T~EkzLZ!3c4tUf1B&Mj@ES$v85fY>mjUV9jzO{=7B5oj@FGJ;q9$^K%;4$ji5rK zyA^amMQ0;uN0w*ve?9(|?V!8^YGr^HyLvYN=izT%0xAJPJAS%LB|N%&LA8sA=D8wO z{%uo1N!P$+2oYcHsz_XHm#^4&x8Jb$w|BLf3?Qw9IFO9vlufaV!G!#F%U-2`5) zW&{m-fQuIf28Ls;&;I@Y&yaS!^$Cb(aOvLr7&Mf9@VSC#^Lb&{&YzA4Uvju~{yz9x z0Cdd%!M6(F3)(>Ikswh5YJ++}x(>TQs|i7i5+@`wF}ys?$iT3P1vFo8`L8(Gal0_6 z4{-bCKTwI*{EwZ#?Ix&LZa(zIv)75ok$+new0!twtDAIod}E!E%)>=<4O|NsAg2Rj33*aFg#d1>_d|9^1l2J+S=SHo|hF3(Gu z&;S3YLAuSLV15J+R0anA?Q%) z;6n~a{%yxSI8S(Xx^eh+hQS6Dk&5$D(A}}1Zo!NB$3XQtq@0StETnG5V=JVNKtw^g z6w=oMWzrYxAfn(x3S7*AOQ;zj(Z(a7LEdP@0ir0r_Ut_WQvDNbP6l-O+3lAhAU-HC z89X}=y#%+qx~GCOWb>haKD_}fO-pqCcj4dW!Q^WA)^R&{ z3ZB2+hlzn<7ih@Dg@5rW{$^W{dYD1KnE6{l(;1*iu9x#cNfELt&Zm1VsFC2)y%$^< zcK1Sx$6nAX0FUlo&~68h&fW{4G}GM+8iDob>;)}y1Dz52{U(1S=nQYrs4t|eiPEWL zc=iAP%Vi(`|K9=X_`fc9+zT2mF}(dUjH zP(1`;f@&X7r_;039aQ^(4DsxA2i0UCV?8_FK{Xl3B2UolXE)d-9^K%cwr8h5hi50d z<5quf=(i0$Ab0nSq%ogW>$jXVxM((pL=jKhQTnn&j+5B@cWI9^MG+9YK<5M%y; z4qEQ@0{jw9-Xb= z`tkpPW3J~I)0%59uoPK>r-!RR1#WjMNVi9?$3Kwa9^GKY{M#HDJ6zAT9Dv+=2^IBd zKEMRl=m^pXKJhiQ!=w2pBY!J+Jx}K)B=>{b@GsR-{RHx+2*_K#qS5Bi-B+MpJY}{_ z3@?AMGcfGn1+6{?T{?tF(l5E+{r`{PLWX}{GQ0!jF+Ok!{N&~Tx1g|-dAS{Q64EvS z&?PX<$NqVAmQLVr0ktjow-qotbd)aj=q+IO>;#?V*1^B6fXShwbQw4tLC$Zv#6Q(_ zIrymB787uHGVqT_XXpg}Z3WC8om)YTK!=uZB`F@g1uPzrb-NzjVB!Dg9Qe2W^XS|Q zZk1^MD)vDMhpC_rAUHTAeu11R`&tWWVO~Gq~ zJ-Q*rGk*8ztZneU_1oM4=qg3DLXAx<90Sq{pB z4XQcF;nSINP{0#(@BCJfYdt^*jCO}k03D#|(aR#{+xZvd;?@+<$nivQiUaquJbFtz zUa%N|%moiedqBEd9-Y0Ql~W#_U_W_$zu^HHefQ{eZ79_7=>{LG=nC482wIwg)P8Z? z#`tnBXuE4?ZNp1VG<*ISfm#dOz!p1#Hc_;Ku9fxZZUyzgJUU$ucz|YGKxb|6Z*x6= z@Bs(^Hs2M!EgYaxKFBd`usy#!Aj@bxj)VLC&@g|k2Wk%cb*5;1@awG6_~Fs*JHxA! zM*|Tmp3O%kd@M^3@V6{Rbz1jS@THO6U|;)myEeS|U<3-2UQkoc2Rf$!4}uvU%|G?{ zTU0?yHagGvKx3;35nH#dSQuWMI|4GH6;c7X?sypw+LPV86;${_mVARt^+SqG3@^RF zN94UY_=3Z;^N365>w~WaJP*EA*a7Zqc7l5ij@yNiQrK%t{%x(#|ADR-6L8_*z819p z%#rgbXx&hEE2uH#$$8YX^GN66gReO}555Hz`p__L0L`j3gOe4cg#vQl3!ZRrYX;O= zVc?%~z>(t^xFG~e11~;=F)@Jh59pSG?$Cyp3%>maO_KG=h`5q%Js8zJg*|BMW*Q4<3KKJjxPV&0;A7Ms_ewxYF3|o3Q0Tn` zU8e2Ry%pqC|IQS>7yg|!dLKNxTPOVe|KGdQMehfw2MccLfrn|mJ6ZI+JAL#(U0l$3 zEl3GyW(qWD3u1#(6lmNN#0Ir46#hYs0W~@_plncE!~n_$HBmrr0i9C}xz^RQ`Dg@a zvZS(c24} zF!Jbjv;cLRLAG=|8hCWJf?Vg(?Wp0=*$Q&BN4KMbM<;X)*HHr0BZsI34c3Ev4j&~3 zhYM^l8?rOR!y0@W27l{HkRJH#Adfi{2>>+`i)BHJ?GdS_+qJ`^({+aj463tKF|bGcj*C--V#1W(8_qrYo$+L zf;LZqR!o1t z{8x{^)u3$k7k6zOPQx=97v+w`=56vV#ozFb8k3{%(>ah8C*06Da0>!fvbQoZF zFL*$|dn(94kPzs4CN>3+?p~06AOQ`J?p}~HKx_k#?p~1VKx_+-?p~0iL2L()?p}~z zKx_|>?p}}=LF@pJ|HnK#zksI8K+UG^&<=3ibwaK?1I1430shud(3##`kbxlA9j_T& zI&oDVt_L7B2e@1UH}Jl}$}WCzu6ybJ`~QDDB{U>!fzl8>Epmc}$w3{09grg16}0bT z2dJ0<4PSw>0wcKL>(LEvIe{k9!7%}DU3qka8!#T7y^w*fR?svoNFUg{;5l#5@DWHu zXD@gRs~bE4@6p)`8sFmAXa$w-{F<%cLAK+q2mV45FR1Z`V1n*PhdKzv1epkOS9j|P zu)*MkKpx#-%^sbtklt)7Xx71_8!|W42^$Wt0A=Crt)O$A(>lR}Ox>+PpqXRv7!#;s z#2D}e4d6nDIQd&8U;qFAvh+2sFh&YOa2*74G)l&w^Yj1zmy3Ubwn!X@D|Xz*_}a*` z^Y~`Q*SemaFmZ500lAgn3c6&c8GOMmgCqZxV}`e3aStAb096Jr1pS#9969beemlkp z-g>b9J)f}wJOLD-Qe2R z15$q>Eu<(;{`>zmvuEdV&}s`Tv)m>iYalc5FXnCi|Npi6i~XjcEqKR0dckARFLoRN z&HMjLv#_UZioqI4@nDAX6U|J9U<;Wgy4BDib8Z6lD=UsP=Y-SQ1FDGzFr97tQF zDHFql`p5tOA0QE*dGi1NuBXK5YkvCwzv7es|4%;o|NqOA|Nmni(+)R1{r}(l&Hw+C z-~InD@cRG%HYg2ZgYd$4|Nn!R#e>f)Gyv}r=J)7j;RM~h0>3ZjgW>;OkkdH8_uV9W z>;mo4`Tzfay@%z6@(hpGlcg+%Ctpi=bO&>IWS#^m1)px%>3adv$?EhyfpTArN9Umz zM=c3#fjDBs!tlabjETXc8@>f%14v9?40#L01hD&&)?eb=|1jYKGs8|0foJ~%XnSd~ zo-1hA)jvnjMO_}fQ*XTg|9>ZF6#j)DXo|cM%l-#z*!~ArockX#p!*-Z!9zlL_dh)6 zV*p*O<$-R~O9_zXUfc7!p#2Y`$on5eL7N^9gT@lRc!8MrUwC;jF+kR*f-fj#{O8dd z4!P39v-9U`6X^NVo{T>|JAe6hGI@3ezVPS{{ovE>`ops`@Plus;}4JCKn3uPW1S~G zEI*ahdVnq%4%G0p4&*3)?Az_A;M*N&;M?tK;nV30THyS_v-1#Qu^M>4op0wwzwQw6 zAAX%7{1SfMA^aaeECCSffnRqF{|mp)7=8}FP7!|4jU)Umpi{7XdjlVMS%>hSC^`so z9^+kT@Od=LLFv%*|JfI-_kv1>=dYi6 zHrs(3MVX?}Zvj&axzTJ8Zki3YSj;KfsjD0q1%_$(XH^3E6MA);vK)9mzso=<}-tY&y% z4J+6&HS)WW7h^#D^ZzX9JfG#DEvBH$KCxv^kL8D9~`9u z9?i!%UW*)a=MePibQgGS=Gpv%r_|1)`54b}SI~YShGVYhK%1M+fP1Z{I^4O_j=OV$ zwqBk-=FTD9>CWTPSuSvloeOCi#HHJf#iiSi!=*cn2Xw+#h^yr({?E*!bAk*x2~sYbFT8g%Kq8 zgk$q>rebH0=0hJmnvV(i^~!(_aA5G!yj9HWaon9l7}SzU2OoMZBj?+B&GX|3Bu=;r-I_KPWT(;3(rf=FZ{u(&+#H|9-tX#;%~zEYM&m_}-yA zpbmNSFBbl$nV_Za-L(vkz5f3}8>HIzfY!wKR{inqO=SUH#@%_+r&qN;gc-E_R^^Oi z^AAS;R?s0Yu!T<^&`o0=$6Z0wnG8s$Duc#nL443;9D`5iFVGzY-M$BWJDF9B%Z5Px$MHqQ!$Ff%yvZ{s-wIw}t0;R&FgDv8cTc-W^qcg0Ka zy_%i1JA68GSNQenDEe4FDi(xAp-*SNz{~c(pmz0n5AcEA3=9naL1#sIC|=~>7P|gMa%a56)8_+@UKx zI>kX*40JWDM`yf*PiMJ;PiMV`PiMM;PiMY`PiMM=OQ*YsOQ*k&OJ{gMTBm!+OVC;& z&*uLups^?Zrb5sS8{M@m@T4*kw9cfr3bX-gg->Vh1>fFU4$vMe&;5*?3{1=%tZWA# zGW&GaZt%SP-lwy6hhy^(Cg?&M62lTS2?QE?@$G!-(_Opc<#$j}!5wx3d@@_^1=sFW z7HC|v^S3Vp9kRI}7T2KtT>QQ}Q#W{CegoQj1#u~8 z%O4rZ9OO_?l!IOB3+m!E*REgzIRFxlnk)SLA^Tr`1ub%D{?7sm0^iQ}{7tKvz#+hc2!R^#Iw91Ql7Zw3 zEFl1O0qFKbf-YbLU1I=Fte`E0wFmf{)S193KmhJ%chJ0eZ`B*m=A$Aw{5-?4`3GyM zEH2w#@*!WvPM^~cT^l~*c)4@YB z0kq(vJ5c};l188-?a=a^9FpI#ge1gYIq0^*s*{(8p|&FY^%7L^cGpV4{ly9K*Avg? zqcR5{GJ9Ok-2h(j0bVQ!GuWr|7bpSl0!>XLCx_V#;N&2J@I)>nW`>A_SOE??ES`V{ z8)(BV%syDH_L2$efg(fK?pg-d-u(ZL&41ZJfmj|4x=`Gs`OpK`&bL0jwvoQf4F26V zptAD9OTpiu_T+nFTP;4F^#U(lzk?eAelDH$JTEQ2gZ5Fv6MoTFm+nLfgfBsD0Y{Xm zDlYfwt$O0yn~EHWrNS@|fdla2OC^*5e9qqlx?|g=J5k29H%A5RwQ_xkxvu<+--Ena zED!P`$g8fMZ+v=fKly;Y`5d%m?&alQU?=;!bf)vXZ2bnh4h^)uk^y|44CuZA(D{Ds zpkbL(aGUz-?|=V2daF5J=&S)vpjK;mbQc?dPh&Z~22_254rJqa!N2A|cqrzWM>j;p zi?wS&y`}Fjcx{*%UU;ZL&MSnO-wrz4474g8aXtu0qkej2`@ZPy9c4@#PNi z+KtN(JTAZUU_9n=@tsF!DQK7436EY656t^GKOpYod;l621vMN&1wDhS;al)6W(R#b zU3d6&p7H^0@9M5y;j8(+ECY5QlTYUh(2W%y-K8fydTrQztZNSxXM?ZUECrwL`XACI z_u+T`;M4id)$o8TXe;C87a`yUBe%eNr;IeuVPs%<*~7xX06G88v-v+$v6gpli9O>>2T=2@+x37?=RZ%&;~xCZA3PYp zz0^W65!9Xb=q}gru)J5C2-3{ox(IZD%5KoGG2{d$k8bdhzaEUoK(`ege9r2@c-#ZB zwIyu=zku5ZegXdv{DOWT_yyfR@C&f<3-I#`^6?9@gKl*90N>GmqV)R9l^|omqoZJJ zTIPV3`*mLP=nn1h>1APZ1l#D@`O&BI7h+6d0_bRxZVq3|i$(UH{||dMA5ieIyal>% z(-*r|xcXv#Wc8k%mwl{bL7RFm_;!BcZ^{C-hArFL}WG zyl!}d!hZs2pDQSxl{SDc)U45EfZV8==h2P-M$KPL{7qj$Wqjv*4^WtaPVr&<;n4|R z5b43Z7c{Wz(Fq>+1s$>M)43LOWbA7@-_E~2o##9?zkpBHdi28864d7e-C`Gd!KXWP zhDUel0;Eec!MZ$~e=$OqlQ~&z5&a7q4R5LhooM?1lwbFrtDr^Qpu~6a9sCkaInX7V z$}XKhpqFS$z%J4J#aO=kr8#KPALM>5PtCueo8Wwyb5DS7r19wto#E4&x}ike@!Ju` zl2FjCl>E)JKz*@n&_3n~;3Lwp->3N=oZwHs$OMN9_>xPIXCR&my#SiM0U27l+OzWy z=(yQ~&slvK--GX8!7$LH^B(AGPH-g(xtbH*D6q?#u0g7QCjO=~psg&HpM5*O`0zVD z_OX1(-vrwD;L%yT0dzOO!RM^LjPE@y?}4t!;CKJ-)43Ehv+jBD2`luL%su9yId@9JjdU140H@Wys!Yf$D{L;XSY9x;Q_;M9@btQpd0%5Cm#c?&Ej|Z@6+ur z;oIp7zKY(r(-m=Y&N0L!h-n zpxaW-J-Wdqyhk%;w0GB@0Nrheey`?BQBcnvUGy-&Jfyxv8ZQQ2ZRFGGy90dl{ddpK zKajgN7l3k0^M5A(COdA>0n~pzFTRD`wV8T>ziAt2!+f`kiiT&mk4lECA?QRCPiq$y z4oCjU2SJ0zhW|aSeN;R^m(PRZ&%4)z#qr=nR_|UOp3Wbj5bF(LacKU|eDFD|XSWNB zhi`X?4TpcX4-W_O-J32f97vaMZt(17u>qwQ$OW9BI}%TL@H<`b=yu}pw07q3?Y!y3 z?{o@u1t;jJJ8*K5vGMG7vC#mvmBF>u6Hr6L!`j7?19V84N4JYbh9m#vqmKNOj({ex zx_vKzBt0DYCmnM<_=MH*;8RxrUKVYa&JPD4u=@3eNH{n)e`9t$_>9%FyM)=pyW2;O z!@Ju>mIHkE=22gMr&r$AB{CfTy(-GS{7!E`mvQ=n+-P~wquY0bM>lA;9(1WD=!!~b z0m${1&K#imde9A=U|9>u1(wbRkQ*(XHNZDqIxB#;E_OOccz|!dbPfRd8+7+3h z$YJqJ9-Yn!-n}fw9^GCXKK#xHJUYEId_XRM=qdnRpy_Gt$^kOXr}HKBdd>=8ey6KG z-MI%myIoWyK=)}%fLH<`mH>#w0b+4@cC)B}4*fv7t5X5uCl_lA$ON{FwE@IeF4h{* zOFJDvc7pVKz%K0!@C0qc>~yh?0N*m|Vx0iFb=1W=!xMZJoQrjVXSV~`Ar**gJm2|P zzT$711KPWi&&I&u+IiHo^PESoGw2w<6&{_w3p{&WR1`cqU1#`q{)UcUJA&`?dp~ddQ_`oCcBIpDPAI3+X&A%OdI%5yGbmm?twgn|>4v+2v4iC$ZpcL%W?Zn|@ zc@R`o82t3HJm$bZ>6nATKM#J_LoFvg`CX5^mP8bKpzBgPkG)vR0a`Kux;EgTk0$62 zQC9FthTR?#kVTdr0^o*LH-`u4c$Lo326!W@b_WCKTx(DT=gHw=?ZLs{YysNKZ+VZu zNepCa=R@DlJD_qHe8pFH=?0I^58x(NuL~oGtKol8m~@7ofchVFNg=r6`0io(v)FZl zPdB3vXaiyMp$ER5zdR2N5yJy7LBq(1zz5w=)%nq*`Qbm%9vZJ+5e*;a)D?#R5h^Bt60?Lyr_&D* zL%^fc=>sTH`*ypifG&G{;nC^zz@yil$;a?IsFmW|&0-0%*tgrqlEbHy$*0qc1HyN) z6!7VE67cM1UzODa36?1p#f)YDl%isJ>r&t*npl5h? z-ty`E2)fh~awRBe-{=c9@RgvyJ;7Ika{m4Q|D`=>A*{RugD)uE+ym+EyytoF8LL;X zh_O$1?FP_2-{6ASg@prr2<$_T&d?p8`@z9c>cjZV!}4RX>jY>_g5uA&^QGs(hpax9 z44$2reE6L&`*cT2_;!}22}z4%(5F98i+ zKk(^%hm>ZFC7@}>SO991Pp6A9C=8526I#s&6?{9-zXT0yL({WQ=POW9`gA_`=)4EI zl;eeoKBz%hdcd>uyRYVZ{+3?QzSmw?Mi0wlKHaf9O4$t$yleu^Gj;xiri>Gwy&)_L zKFp;nTn#UI?u4Z;M0k9zQ_6%;!F!umPnLNOS(hw2%M)zbpZ*GJ}-w&amR$Spby$Athd?H-|?jGql6z z+3mw30lGv~0>pw}C(7d49l`=ir|8#-g3IP+a2L4OiLvt{$ZMU~UmJTe-UeSGD&X7c z{KB)F(YM=?!?V-*gKwwv465F2bN&F@^oCU4&m0Z}RWu5%FZa<=ZXf*?dsIqw@qL zK06P4bh|KvuGEy4@alGv{@~T=A`QA+Q~H5lcZu{1zs?eAkTKGp&A%1+TYNyHaJ|kC z{H$H1!Sk=TL6?czfY!l6J1)J@dqhE#BR-7JeR{nZ9Xl_8nmMm6(G#63o5{D^3$)GZgKwwb4{+6qe2u6A_(Iajptf4)1)t7SpaR0D+lO5Obc?72hy}Y( zRD>PG1Kpp>$l=o|0=-XklTWV*y9eV5pKb}y=0gHLod-bo`+0P`FoOJR2=cEX$iIdl z{~A8<>rOFz;n$gB2=cF?XY)U}f88GVS^F4*{d>-X@x)7re?cd7u=@7;uz)Jq9Ui?Q z%nv+z9YMRZeLEk#P6UUqOIPd$$jLvxl205DK4$e{1l=oo!Vy%nFkT0DP`kq=%fh9 zrK05={F7Zz@K18xPz1SEv`oUcvp~Y9yG+2hvp~S7+YDkf=yp*9pYALUpY8+>U(2VC z`#_U_p3T=F_lrK|Z*m5Wpmbh)X$_*A4=8|ob0toOIPVvg@a>F{z<$ptsA=oNcplt6 z_z1qs-12y7hDY-u1<-|I@g9vwK&@<~nL>|V)-9T#Q;A`#pFk_!K>Y;h!eo$JJ&uEW z#?ZCHpcQ{$(O%P8+~7+`LA|jT^SD9hY72taPkQu<&fo?uv3UPt1vhM65U7mo4&CuW zKnt|9^#2PpP2|a0Z0iZ&_l-t@8cUv?=RFTTXNTT33TiFE?ixJ;3bszy3ut$Zt_2HY zyK8hZNCtM-s5^&8w>Jmuw$U;U4>GSCMZ2%lqxnsQ2k1KaJPz>kg8Sgr?%zEBpY`cY zVD#x!^Xbgv;NKg5&y{M*DprxJ024lUzv2W=Q|<==J*bl_(5<9g4_k9|4syS7{cpTq?oOFQP`@oqimipqVR;jDh#^0CUjg{YBd{w>K!MhL=#NKl9ShVIo}FPF{M*Wy(?Dldw#zbtHcEaw z_>7N#`$^7^oxWEjuzR9_To5P&6KM=WzExR_)PTyMm!K^mU9cB-l5wd34t< z@aayS@mj=}@r`e%nkV!`&JDsKnGSc3WA2;`C{6&Kc!_Cf38zPQ?S>b@YM@0AMZ3fdF=G(c&YdQ|9_BTpKjL^uUR*+b^Bfc z>43SR)1T|OI}gZPpk)o1F5qtk-M;j?aX%;oKqtw6e-Ap^{4m(hQ0Hw?g{+_AQe}dz zpXwI#>2~ArZ9P!p@72q~|u{iF`rI1j$`a>a5nfvXLkSC!-xhw z+R~CY8J(e5K#2)_pc!QE<}pwy*7?<=H?{$^C}IbA<9w&<0nlMZoSw}`SbQy?mDD@) zZ}(AW0v#d*uIt}*hhBN%4>|@9atM`2H#c~JALu;#&d?nm%|G-?^dNou&>bF~p))}J zx-{oz8+FFg)gIlx6Ffj;lbxUmb>9WxG5D$AtJ=Cj9lOvI9*DykK+bus2s#?wqxqj+ ziJC{Z>jLN{_6TL2Mjq(*m)Q!kvoO3^!+~6)L)N=FB8~5W7FBq3)^0fN3fe`;04>D8 zp|cj$t?UGaj$`K)&(5#NfinSdvnc3%d{Bz=IPMDCM8*K>U&E%|V43Q*sz;|QXg^v5 zI2hnVU}+OPnh&#hTHfPt*$tX5+*2;DoE&POj^vw(KD^nyg-E2^i02ZO;I zmhvImOyqm({x|<;DYy6OWd&XHd`Bz#l*nik<8QO z!FciiL;h_CKnvMkR)UHNSbwweAEN>T13!3EnFr+bJCDw(pe;b48~a}Rf@-PekBlD8 zp9DY)C0ao(p>B@u0Oron-JoHzZVz_QNCW>i2Y%4$JuhXz8X!*KZ|4K?dPPAOI-@z` z#LG{hA$F)^`P)G=+#bE6o}e-i)xihA)A5}kx1V^q_uv2jVCI3Bpo4C~%nL7<|NH-+ ze>+6QY>=_72lzWCff${xCth~KWe>cpg|jcb1fAgl+O5_B+KvcHk}u;yT0nZkK#We; z3om^^3~*uN$l=i$DBy9N5u84YK#NB_nt$n)RCT-V2BrH>NAGUe9i5II9-V?7;4K@V zvn5;`Jiy0IxXyr{KmqEWdUS%%rGNyoqXu~BtrK+UgzE{9PDcyKmHVshl4e5CoOy(9mW zqpmGaOHCa+uQ)b8srS76#Fz77%caV2@Wq#heLMg8THXNN7cB=X9ybWT7Vzo3yg~Ri zr$;B7PiGy+1~ynpk6PiygN|DUMHEt<0@VV#533S%$(-l$qoAIyujXCQ_|f+(zKqv= z8Si=aI)E<9*kO3v@Fb``=-q3g;on;$#pKMtEkueD)MR08_{CUm#lP)P=b`3D^&XcW zd2n8CIZzn{aknSv0O+$mma#iZ4})UXr_%s-b+=FFhmFFoc_E?}9-Rdo8`(NQXY!-f zmY`Y=(hl!-z2MUcRtdTiFUFVg2*@przKqX(HE(z}ANUIjT4wMCqaNL*7x?8F__qas zs~@l<_4&75a_qe92y0Q@YPnSD2(Eh$fa{)1KAm<*feQ&$Hyoh~o+<}jG7b(%Hx7^E z?x5{TNGl52LEH5_KoeP=pa!k$0gy*Lj=M{MV#24>-@v0Y-omFd+~FnYZgI$bB&fbR z_=vV)E#JNVC| z*NF*KcXs-5xbSah^3goV-^|Lw!0>VnCj)~mlRN`MNsO(cJg9I3-PY0Yi-W&a7+is| zd31vfW9M(<HuLy_d|C65o z4;UT*r5(t8yelk1pbg#Ypw(a~4P7738?G&v_&Yf{7#P4w=zwSQ!T&|aUTU*2FdTE| z;7RNB=khqt4hqEB!yesy;5%tSYr@(<>#{tW5B&A$4P*wp-i?EQJ0t(LKqkv(j-Jj~4B)(AQSuKC~}kKO`EXtUC(To?EhqUqK^NNkb{_O-{`H@~rIQVGex)O$ z59qqqFwhAooS=+&*s=L%eaT+e){`YWHn6`;MT%U|*0mJSPQ?EwJ;3|&!BN{_!N5QD zfFt-+$d^{2rAqKPED0qp{=`A6(6GkeV>Yz-%LdP59@-%Mayc6V133Ljf-84ebg=Wc z^+KY9)b#fm%<6C%D)YymcMBYD+7a9uLuJ)9Pl^; zG{o>S5M&ZGU_eJVw8=r7@y`QWSc0<0OVFhluv`Ul%rDT07059Gj8IGYw=p>G1a&H2 zUIxj*lNoI$NyAHq-(HG< z7M9@Xa>9x%aLNbW0|^Tg6{^XjX#U&W)6noX9f}e6t^NK>Y$WL9qrD z_M%Q2w77u>RG4(Kz1+?OF0+M?x$}d1z`USNIOs4O2GAO7cp(m6IuB9^Q}hzl89@XO zGk;qNiLQ_WxuT7U0klZ}1L(4iPoO>u|90PxX^`kabcG?cGPFLyaLQ}JG_V-R2glvH zL8gFK_=B&_fyF2e72vx#Al1}y*B@Y=uKz#|`UBcE;QOn?AGT}2_czGQPJbR0w;%;T zS-A%ZQ6LBMd?zTx5D{5sN{R`ffs&W*jG#E-z!rH(UfUpyt zUVdcw&9UL9g9pFUwPq0Id(DIM0-gd1bb`@KP_NPl)V}rOz@rqD2_e&c;P`#*>(O~= zgYav6pUy)b-!FMEUi9h>VSp7!c+3H{*TPuOLkeJMVj{0AR`?rq^b zaqy7<9*Z=>2zyAM! z{pbJxLO=iiH~Iblf5FfH|6PCn|G)Rw|Nkt%{{Nr&>;HfCAOHXF`~Ls`!5{zshyD2f z|NA%GnCA~dZW59@$8Z1tmwf;KfA?>MJhC~tzyJS_`}O}n$M^sLt-oXLmt^+JN>Fe1 z0(d!`N2e=d!z8Fqc`==Vz;2{>Uzr(Rh~8yH-Yt0xB*u7`kpWTNU|Ik20<_G#)3wJF zbZyZ^kIvcyM69=*fq%W_3c~9xSAaXcy&ei4oxTkoy`bd)kZt762R=ZDB^>cBvYY{0 zWSPt_&*0nn+VkLJ7LajXy*3g)*0n1@>nnR0_*+0*IYGt~p z?kW#y&%@VOf;Y57mRRlrEwQWw9q1v?09w1~)0ums*vz-{$ID=pwUnU#B&aiwy516W z&n6;DJi6sQy2}MX%Pn0&X7RUnf#zy^;~^)>fTyP4d3?X-dGINV$H8YTPW%Ea`o7&P z#=hM?#vFc~KKdMfohJP5u%vWOqt zvN#~%VR@+ZrAOxl(0wVdt*~ha-5S&R3(}v1>n}#v?_uq_0;0ndXESiE{=*gcy6G4i*@fwnprzV+<9{StJnwrA%7@X||=dwe<{fLHlK z7NA~y=iN)%0?Z@eCOT*VCOE}-ATPkI0QF_y3os{uBK5VkXXjm%)tATlL6hy^F-*|v z%MBjgr4v8{&_2B>_KYAGLY7{JF)=WJmR^EJRQQ|xK#R%#pYrOSa}{)ArDx|o&x`MT zJD+=AeDB?BV+UG#snK}@y7E#EY31dfm%N}F9zL81x*r_8{*uLqIrqS~!;C(ip&NWU zQx}v-fL2PDgn<`1HiOpcgOBF{-v{#n%ep!6ib~k}%b(yt0jCx=V|Nj5?wfyee z`2{qU173f*9VFRVyTGUOv&X^5EIy2{eJ%fi)=Tiae@3joWPz@~wBZ9SlmV~51l{+V zkA3~6N3Zt>(E7^{p!Js@!0RtPtcyQ@)-@s4UvhbL2XT0Gf`$Y*9KmTZhWW$6=PbUx zDK;N`EMN1td;zbpJn#Xu-g3=L$m&bX^_Ew#uDA5)=7ub{am;CK4%)6MSD zUC-g!30qys0$E)t0A5|$0lM|z#K8wF9*l>5EH9UCds+7P|NjYy^^~BbFjh-v&At@8xOm-P@rXJUc&nHiH%lfy-ULUJ*%O z#v4A&;1jc54d23REPjv9TZrOnf^T=!2hZ-PAHLlNzTKevfU17@b{2i`?UeBBjNky3 z#)hXs1+pW*0Fzg@jFew@i4=!#XAy^Ir<4=FAd8e|^HCuF@pD*wM4Rm*2_U!x)+F#!MPq8=yTr@YW<3d^s%J0*eyMe#W0W?<8 z`N#9%6Bf{dG{{1(7p=vAK=+`4N^H+w8#52adtSX$l)+~RWEKDU4?Y+Hv{OX$m5=31 z$ilXlb2&h3`i^*Zp7rQ;23_O}UDF3zhyz;E_Y34+$L2ST{7vgY<58U#K~vPAgAort zVDn-8;E{c?0JM^z`Im!FXXpW!&eRRX2A-htBLnzKJ|-VL3;B+{7MK8b|FIWW^MC*M z=?*>M*?GhlwDcUbgb#Fd;|YEN29ItAeofZ~k7m$PJzh|}c~~;?H!lKBr&wO)Z+gSY zz~FiC5ew+pNxxnmVOPWdKAj(YI!}U@X!>@Z1>Hj1{GJ&+faJmN{2dfXK8)W$bKuRO zy@ZI=23pV7TlvAKo582s1GEgn-cCuLe3ZD09KHvcIK@<2q z|ITX&PZirEJOr8#I{3k-^FPEyu(u!vp7P;$y6oHOV+AtM3bNb~R2P3JI_m@4vSE3s z#KgDrrBCNCkAsg`Jh}}$x+^$5I%@=fcyyKse((T|)HNRv@aa7Ca^}DP|KaENLp6Y- z6|`RL!V4BoP`Md;z^C(?kLEv5Sj&{3AAB3KCwOt;A<(L^*D0XzN316VWlaypLq6TE z6MUFmR~Y^WrAN<%2&wI-Nd1mJgl>`Ovr9#g@UdyT%r@cF?!;hDT== z2WVLcDDOiT3hoEx{Spn(x?~^5kDwkmc{=mKZY?ix`B&+ZUW(1I)Y!a$$y+yx~T zzLvlEn+!lh!`-zPd_ZSH-ty^u>C&CL!L{>&Pp@yo3nkF4Ii0^i3&i-FkAm(YFb9c% zQm7Ybh_dqn$PVyHTfIEY;D*)*(ESHK-LWSSF$P*m*8GkcyxG`;-}xFS^nDmVd01ZH zZw4Li?$KNL0kYhh1)5C#Em_n&_?U1#)HpTJi03ke7YShye)0iJUdVN@H?OO?Q}8Z@a(iP1X~SG znHTt*0znl;=PiT}icMkZ5ws+c#iQH8r`v%8k2wM!mKTc7`dVHtu>d7F$XZ0C#0Xjo zXd(<=xOhOoxAXGLeW0X>)O>MZ039WK@CgefJ^FM$dQr*-YEdDUFM5GSCZWq0J5x`T z@)#Zf#i~zt=!MrgK9Dtx-n}7e0-(i>KFqNzTn$fx6nKJ;4+r(a`9VtI>EE-P*|WQv z!=tkboH#vu9hrO$4|*Pa$l~2?!{y)Y!v$Ht=mkxfhaY%$9t5pcJm$k(3R$hF0AH;L zO_d^Co*;dmojz=!m5FRVmhbqRexR>P1f^5{W?Rsz#C<<&I) z@$BVcbL{*8YOZ+qhOj9#e`7xQjK#m(g-yY?o5#$vn}yBO@}OrYs9?F|Yt3Tk%kOmA zhu`TI=&CQzUK2CWRsv7UgC6|O=Ros#pfL-NZYED_CZEm=9{f&+K;2MJey2;&Wair| zV&>WHVkQ9IqxQtpngz@?0JWkZ`v-1&TDzDjIPyTkDAUbmwmH?^RLq<##&b%kT8i zhu`VE2YC0u1<1O3*lq!jZr>A75ee}A0nl~=Ck~HJXASRO7AcQzE)Ra^A0D0F3O@W! zCp^0uJ*^o)Th2jhK?}e;y)Aq?4|?)Do%HB*Ht+?jaZ%y$04=`-tqJw$1Z^T<1o5H! z6kLoYz*`Yqj1?eT6I_fnJi6IIDhxcj-N9NdK%=w_@S45I;pK7Aiq38}PtX(;@(NQH z&u$(z&+ZU5P~iqyO$wexf$QaO?g0%Y_f|4?f}4Sz=U&UFP4Miz<=JWF+X-5d{|>ac zz41U~q6rwIJ;>r4^g@avQj z@NE7Cc3;#7KWi6(2fm%RK<+yXa^FvngAZAJdtLZICHW4Y-W2W+KD`+qJU~n9zrXh9 zmuGP43Oxb7-OfYuq$8-2!g#@@%lCrg!G|oMB`7|O=RmIY=>~TRJUat8d^6UN>?ftpr(Rs+nT8@9R>jnNvt|ve%Ge3d$l=^f_K{^)U z96p`$KAq2eyH!*?yL}lvEzdZDMt~VSn=k+O>3rpBdB(Hz3x5;%xY^D}FT+8LaGQ?` zfY)T|frj2eMa<-b{F4rOb{_EIcRJ?V%>jx=AMowM{Gb*A<28^M7*F_EzAMe}Y(663 zWBH&IwDuCT0tV42KJ3xUYQX?rdkI=}30-9gnizrJg9jSMg&yGwU3&>yi~<(zHI;kB z%;3=r-lX_K=@BEti`o~A4E)?4y`nOYm>GOJ-@nj##0WZh4N|^)fYx44VSuc?-1VOk zx}FNu-VTPHg#;e`=)C{paUp0QwjF5mM7Q1=$vXp^#4fnbn{{c!Yh{ghFK3>56 z1HXXB2Yx}&9$1GD9^L#N-2&iKML0Z~k4tzoA9&&M|6J*H(3mK=YYy6$-2Bm=@nrMI zI>Sq`FQs%DkcR9){sD!TW2j?Dut)P73D8mokIvsd-SrYa-Q@}%-R2(M;T*o5UpzIh zc=9`4^XdHmLi#@=gU5{413vt&4?yR4__jXr=!`w#(Hm_6xk$Jo0yh5R)A`S*^OH|^ z^$(wJW8coNo|;!-rd<68H|2$I>)VO|&{5dUhb2HO4}2`2l^%KRo;Cqhti%+Ld97&X z9RV5SY5Aoz*rU1j0BGp)M5QHq;PMk2o*vCdB#@JNCr03Ubl09Ryo9RmC65$xXrrxn z@o0Xd0Xh{A6yd!r$02P+cZr?ggL*rCFL-wTd9Cl+dB?ZYM8&tWMg_F1?to{fi;4ia zcItJM@Mw0uAmQ10#>3im2Y<_IP=Vg*dcwE6L`A~4yGBL9v)e?)vpYnE1GEna9Q4*SUS@_JAi{&+>6nM*2@ig^gC5O440nT$ zrt;u-zTwgQBfVG$^@P0#pcD4e7#J8{tN~5NHXZ>P46+)W=XYv=!|jI0_lE^F9y@=6 zT6y5(2Tv5|f{r6F=HJHqM1qCEv-2P5;K|P8o}FJ_2>xMY@ag;xIuo|@7-+y<6nx|^ zXwfIrF-8VQ@IktUw~^f813DM~<_j(G)ZTXwR7bp=`>qo(>MnN&v z1`_VP|KiRsP!R-KkL}URvdsgOUV6(lz;5^K{PEfuQr~(q{_yPl>C+twx~=$yZ)YTj zZ>OMVXTb}P?$QrF-M&9OI}1Mec6$8q=q=LlXg;Xn*?Gak@)&;$=pYL4=G_7dPwN7K z;`=_`Tt3}F96p`C4}3a9U-))=IQVuuc=&Yse(>o8o#6*w&*a;A!mrzh|A$|v54(b2 zw-5UV5K98YdI4eyfLJ&Dx_;rS`bNF@guzNQD(BN+g0&RQm^|;|>UBZ5#XgkQ^ zj6YvG{r~?TWBn=8`D}RRL##?uKtT-J2(}Y(gd~>f5YS%11E8ggu%qJ<(;=WL=|#tL zMuz6v8;m6i9^I}FJUX2@d^(LinvZ}FxH#<5E4uwIGs6qEBcMD4KRj|VNbJ*L)aejN ze1XolXntb=%IV$M&%XpEK+sP1?~e*nJW$TR%!HkPDG6R43p!V&m#35P0dq&55%2>y z$h}U7QT*`z4l~0GJrqBH@?&rS$mz}Y44@Uv)f_KA{a|G9Xs(uEDB<_$u2uk_cNqKf z4p{UEM>KNuO#n4uTMv{ddwjo9pa(S!+?M_A(RuvE3XmRXbsX!_TrUB(qF&);{|`n6 z%<~wro!@bRi20-y_~(-j5S~v0o!@c6qt_z=wEe&XJg?LX8U*#|^#EP|2p-#SK5zr= z{Elxvod-b$K&S5x@SX$T6F%L(D||ISmMOe8gXY)&2YfnDcwBtp$#~4S^9)qJT*B}h zWYH7&0@P+#CI*I=;j9b{6TCrrzq|B;N3YE>A8X$O#eU$!J|aM;TkZf2Nc!+QpYZ8C z=hJ!E)$o9C=Xubuc{`}j+j+yIJM@G{H|VgBA3nW442&<=qfIBdfG!FD$yBW6-|NH4 z_!4~FTDR)~Ps@WKQ+*jvzN|&l!Qj&!F5uH0Z{cfs*|+mNfAcg@cLaR6-D&W78;8M@ zLJ+@r^_qNzH1tfid35gug`ZEah`C3vO@l|T$v+R~t)Os3ZtgfRIPwcPJ>VDcd*H|~ z==FeK(Cq<8vh=WT=M9KQ zLAUzt1Gm&bPHO>O^W_8DxK`TX(Q9%H>_p$r8=&zf-_C;|vn)>(1%l3{Z$75sX?YxU z{s$rT#fr%4eLH`GCNivD4|sOo;BNxm#RR&~>!$}y#0@+>ak*G>AGmD{ny(V{?L6$+ zdC>FX%a@nHGZn{tdVS0oQNsf=>h9Bd4tzX_jhTn$aW7Ewfxzh?KbZKN#F;@Wg;=0b zVAB9vH;OVt1!{;xj|lPU^j+Z7xzzzwOLvwwyms>KeCN}7417$;chInk+*eS^0iLKj z;nVFp!>8MKfk$^~184^Zbg~Mp+Y@wBVcRq&@O%|3{6_j^RB&V{r2#sVRrHyW0komTm+^?F z<^8f+=u8&4z2({Kdjr%!d|~(vG$IuG0yLR*12hx_8ozPipM2b-+e^Wt(?bFOF(Mwl zJo|lnbI#uI=;hhw+naLchOgyU{+6SVd9541mPbmbyxb1v!REEVdlGy)kMXxG{R=v9 z3N(Ac;MwgfV0ggrl83dQ0RN81cVL&ObwhKL*D43Rnx z-%d7g_xLwcUQt^uCCr4^pm zz6n0PEc?BCYmPGb^zuyd=mgDfT=VYrIm_U|@ALt5+=Wkfn1Fw`&shPK(?O03`0zX3 z_uzN><ZJk|KvlUqd5|t!qvT`1Z=2_T_ha?91}=x=K;RV19ZHHql8B%p0hy|K!<&JLQW8IfSwToIx7S;_mXWfh*oz4}IA;e}-+6Eoq;mhxQ%%igz6c8YP zK@Rt52A$9fI=cgO#tUdLP{F73A>`zcW>EU@<#)Q`+3l?0(Fqf;LBiCL?cvH-L8fuJG;rgIwLYFfceazhmTY@&(UFf+h)>J$hL>A^W&N z^D;j?GA~Mi3RT8mp3Q$8Tslh+6x(=q`wMt<7YM*-C7lF(@Jve{e=Ulz6f}3%dF;hh z&>UmuF<;Gpo(JDCd+a=W1_FHe*MIcr1}`g`;Q^Xp>GYl8(G6N&2?(GXg+kq$C|;j^P&&G(`o-+k^Mg1kqZ8uF&7n} zM@n3j@b7fFC;&dJB$5Lp13HD|B4k?dpa$p^l5IYg&q^GtA;!KH84?r?EKvsi#l|G%%UV`R>CV2I-fKK#e@aPo*wGA$Kbl!XM=nbgr z{LDx5AAbu2BRCNDgC^)adTkDR_WCn=TAuOftX)tl>}q)6|L%~p4F26QXE}U2!!>+5;|)L}0zRGL79fTMXnzT)Y*g^+^!M;+J|5uFd}srx zm1udebP{-S(ZRcyr@{041@B&w2G8!AtDs)v0*}s`6AB<6D8x>H4kNh=I*jB5=tPsN zph?H8pmF&Vpp!UG__qEB9aLN5>}&alzp0B2HAEm&IxgL%2V6Tp`1JOI+Q=`q{sbLM z`3Exj2)YjX<@ukWI+VfJ@+p7QOo$Pnly$PEX#w zCfhuFO}>LpBk=>x$97kO(~pOxg=gmlAAaY<{=GhH8GO0}75qCxPAK?v2TFh#65gFP zCm?5v1ag1`K*y4tfXoMi641w@t)Ok6pwI%HY`2yHnuqH;>2z}N?5_E(;L%y-;n933z@z!#29M6~9+n?UCxgf1 z?ZLx4->-ohQl8x*rxidATaV6=V+tUi1c)U8VhMm)pmR)4dv^Pr=J4qBImY49DRRu$ z@(q8}Or+Vu6QEQAN?`oWb3n6&OTUAT?5w@u)As}|uULJ7!)dM`tas9OqaslVS06FgjygCT9BmjgB zd^^1%M}atDKl(%ieDujOP=oB?b7oL$$OCkjOy~vgZWks6uTB?c1rQ5#5(=|~f42{l zfM2H%Gw37~CV!BV{W?QHb6hOUe%3zBFNzoYFuPvx?G2Fd=sYpO6V#1_o`0eMKmP=D zGKlmG_(>=?KqsL*Kt2gYgTDoIB)n&@^9?_1A8GLT7x*L;CD33hJb{Bc#?a$X*g?U= zTzkQz*NM>u+Q#?qWodZrjT}G>kn>S|yA?sRTt1y196p^2zMWw&JiD2FyS?E@rFd9= z^60$a(R_iyv-yCAhvg^!W=QxvVum&ny}DiaL1DuQ@)bWQY&gO9YY6ythHwgab+hn; zuGir3>x|(9jRkZ1S%+}GC|>T#_|vyHQo^V60Caq#^BDZR6p%@f^HMlc3JYJ!OCbOHbjK<9c19}rbjL~fc1BA0bjJz! zc18;Lbjx{k!%j|-^zJoT;?XPe$-CENl1DGg2hZk%H$bgQ&;Msj3ZZAG2!NZjw>>(~ z`B;|=K+jIu0h*!v=hL00;M^F6ih@sfk$_Kk zhJdf-JI6hsR5|6&IkqH?kGHGsrdJ@ zbb9sL9QWuo+2+y9^U(u(l!^xKqf|1VFf({`l5>=b|1oCJbRMW%`XcxkBg2bzXQ0!0 zzQ@4Rc|M)bUPK)O&FCHRJoud1yH{ktM|Ul_k+Am(s7QbQ;_+kTAyw4=Q-CLE#lvTh z=ARrzafrogtUkRq4bU@HK#fD#nJNcB!H9XL%1W>>wlh`wK{D_&RRlb`y#?TBstABq zV@*0>aKXd!Bmbm>1{Xc~UGKFV@Zfj74;}(Qx$p&%ML|QLi21h0H=u^IM`!2)(D?wJ z|3Q7fVi})K@Y=q{BOpo8EJZAMZt|rA=-PzNbFT#v#!c{S{;ODY4$?~g#m2zk(pfqM zbg9F^m&`t$uY7uSJVD2)`ZE6Y$h--kiLucF@_T;FAVBT~~nS%LF{Tc_0&Po}K)jo#7miJG5PQKxgSdOVoWgcy{`z zaCmmIsCf3qOL%s>OL%m;E^y=*a97~h1m70v+3gOxvJf;P3f}AA%LDB}vv~S|){wiN z@a%Or@a(qt1&y8e`gnpbg6ss<;jRY|sR2Cy{N1w?yv7r>gb%d5;Fzc8HBayfm9-aM zBt8TW1swp#5o8AUgLf}Wy+^mtVF8bBk;5L{C5JgYJ3~}ncy^Ykd;q!p2i%Vyojiv< zIztX~cy{}!fa>+b0-m6)1Mlp88UKL}hm`Q_^?l)C?RtX0srdi@|NJez%*d^G(E9RT z@Bx^vCqV0^LG2h71>er&o|@O;PTcnZ!-+q@R~w@_QA7pgLeTkbDgq$)Knz21qJ~GO z56FqvJrBMIIT3V94rpC`^U)XltwsO;|9|-bWjGPMp8bKx!57RPJ6ErQ01y842R%SH zCtH5tZ{EVpzIlMbr)DfuxoCshE z0+b4%2_gbi0QK@fhiuUjgE>-S_z#-4_vv&!0GfUU#s4`EP&yCb@a(+qt9jg$-}wTl z8TlgdJ~VJ4asR`+*Cm$$SLB0q5KaK#pnVUGeLraImx5z|K1$yWoIk4ZzdZHm z2A>4x3F_`2^Wk?o;L{Dh3e>0bxliYz7kc;LWf#`063XD&S<2zr=_}yVsdAWK1H6%k zU(n?+hvz|X#_;LR?eMjH$KPBA>Fbtxbl1-C=#@F{0UDkE;-KO(FwjV2E5-Bbhu3?SkiZe2WT5bX9!3E&vBpLn&T4S3B(Ja1^J!d zJi0>{xOA6Jaq0Z{q7!t-d1vf`mlwdD#^=7BM?AX0N3uYcbX)+fB=qTg_re9V!3&Zw z9$?FH;+~!59G;#20zREOAdi9$O@ny!`xVcFZ|r>;zd-!@hQArK%gDQzB?VMsgU4e! zeRp_v^Lut9{OJkur)RgwaZivxJv-sXgNE%qk^Sij@~3Af&v8%Cu^LZ(LB53@kpuGO z%WvRg8L`)zTC5BV{M@kh4CwXm3DEZB=6_85O?yELB|E=)UVP`(3!5Ziaq#I?VfX3X z!wS0E)T8+y2WTkNr!#dzk%~v>XOG^kAn_O1*+E0H&p~tlXFyAaJMV#)>AkiCuN3NR zRRG=64BCz1&*9P8D*@tlwt{p*s*p~1#3_-Uo&Fr2o$TPHdLEsn3w$)cd32U;@aY5{ zQSE!c$MPe8a}cO20KQ!pxwqu$0Uk!a_}bE=7i_psr-n~w0_c|78IWVY!DpVY@UT1u zZTfpOyPl8$b1(8YgGOmQK-cf|ns|cd#zB$b)$L*^;MLh;3i5zow~isGozCIcxx^IY z2fxlWrXWvvb#F2K@e8yDWQr-nZ_rBA#~zxOJ^7ukfEH}MzXh5o3cUbYY67cRJ3+^b zdG)q{wR?b1BnHhOm7YkO;MLt?%JKXEf0(TxKE&`Iu;E^vJz&d0@*vASoBv7hw{U@a zP2F2eA3&Y)0=x+Y*%=uiXUu2?wa;#NHXnQ8(R#aLGRz4tFM?)OKp|=!x`Dr01Qfm~ zJyB5DUU+Q;Izy&Y&$BZQv=HZnXQ!J0=>B3*nGOy`aDNKCBFDq>6o2zsP=^z=iQ*x% z4Ba|=CqR`QXg(Emh!beFf@inAXSX|tXSbPWcNm9f=TT41 ztKdf0`5W-!^@ZU9(7|;x5BPNc^yK$E=g}?b(Ot;l(JAcFSa0;Jigz6Sf_Htr!)10=fS`B;OY}(KgheFhArssUytTPFFgLA;BSTO zF1!I+CDfgI!lOI(0>p{A3qW&}C03wwT|6xB^G`m&Kj|Q7BL%35-CZQ$*;ydq(QO1; z+X-4B>e*@F(aYoM0r#%WaZnEf6bY8cOQk)Vk4X4hJ}(vWXgmTc%@7@i!!PXigZf*b z=Jmm+%-+2rxeU-E%cJxDi|Fg19@hUCjn^1Kmw7<;qk49J@B}SJ_`u(CfDwBA3w*k& z`Avjp=U-4$1GJI%wFs!a)a`o0xAg$1CgyKx0Ie|pf5Nx(h^OY4;!y8i76;IL9_ZvR z@UqnIP*5c|0knp~v)kvWfM<8eQPBR4qa2={plY-hT#b5k`_Ay}^f?MzeJl_2H(<$+>% zk6xZ=Q1p3r*PLbW?Djd!;n`gRvK?&pc~8yjpc$?g4%gtx@dv2Y=*f8hH6Oyr37(zz z{d-MPd^&3vc=yWGdw17-W$^D#`3f@aE`x7(&0P-P&ZoYbe|zn&e|2A0{q4E zt8jxstKdKb37{M7`8z=;aC`T&+L7L4Cr`Oxj zl6dDc(0+~1n=d*+8oEKJULFCR6bM=Y)aiP`r@NfPv)deW51ns!J%>-{XCKYqkU+Wy z+N;x9y5q&+E08b()f1qVi{Q1T-6^2ei(h>;pZfAUe*t$~I!iD3bbfj<1*9A_w*o1! zUU(Ybtboqj78U5DI?PyuRNLT^RrW$^^h zS~C9d$h-*3UOtS!Jeq$y6x)GY$0W>F`e^>}ZGGa>nY*V{^0hkHNylC+a;R2rB z{u&;=CU-%joIbrgWxk-H;&20>-k7o-hTq`p<1hGh-uLYe67aOVnyp!;n94J!LL)~254;Fr#JqE zujQ$t_53ZJpfzi-_P=khhlFo;fCk+36p-mUpb>hI=^DPBpkdhr&?q;^@Bj(lZU+Uw zPL~^?tt&S`Hu!d)@Mu2%qIBy^WAMcD4{!v6R%^q%H{g&1r`2xIL^OJ^L0aR!;826^ z3is>E?U}5HZz_Yttz_UAC!=sl47G@5JFmu>pcmUjgg@u=onG#_}u zqtoYt04V4^Ebs9*TZ3EYEM>mED#vjIJyOrI<~StuLHpoAs{%la>LER7aPY%>mcG3u z$0a~FeSE(F+7RFQA5;;$bm#7I>3sR(IrtL5?=R&+8$3bV_pW*LnmBlL+kEutcKHbM zz(*V&_z3dAM~}{sj{=~u@UXm5T<6it;{eJJpczTeZgJ1x5@#2}ps+c@MCIeS2%pOSl?70lD~P1-KLT zA9Of5N-_5`o(XQgJMf~>FQ9?xi*KP_-wU8qEBKp&z~iX?8Xn!@ z5w4K42mA#<8}&fz#(aAH4Lo|?Jv^+#9ejF4%Dj3_uKV}0IDpRI=Xbi`)ys3-li%r} zPq&wVPj`fXU$@I~0bk3*o}K4>_?JnX^m3|emlx`{}@ z)7nh{G|<8CbO^kUq#ML-4FLHVbXHRthezjD4-l`j)dAGc1{JKGTQk5+1JJ-P=yE?0 zuXAezi0Up9@aWvy0AYeOg96X1*91DlY*Oaa%VX}*?P9Ls)9qp&0iEu#b}H84GDa}{02-Ki<^#5dzxh9i+v|li#q8&);k}3!0g{E;sDtTAuiz8*&_;amsdB7 zxL798r+FygFM%L5BNT_lR2j{{O#tohRcj&(3p*967_ zVyXce66u~}3OZuLt8)%G)qjZE;0P!*I6P7QYiVtue(O_gI{NjB!^$8iKJ)qZwdYuP=62NSs%#< zMW;cIVm$XU4ZHz8^?-M;NQzId&-E7`y;H7&Le8hR@P$wBT998sTV>)9UYg+8@!PwX zCEBBx=b&rHFYjKKAn-a-@PYLnlAvR4aonrn-D{EpnjHqU%|Rmop3Mhec>MQ00ot}% ziI^7lwXOw~6vshpGK-jfI^SWR2KMNV67U2K=2#v^OauFLe)F(A%-;l_1?ar?611Vx zv-uckkmCSo-EoNxsO$x;vz-ju#(m7Q^Q14o(^=o{Kmp&*00G}_K~N_5?eyUA?G*6n zH7WD$&H2gzaxlodpz#P_%de%<9?gd&K(oJ~0fZSW3=GHv2!GeX2N0fsre{+;dbfgt z5j1`veH=1=;CKva`~XyiUVP!f_<_HL6*_+KS_CP)Jz5X&x4Z(Swf`qTOHnmH6$gMS zM33%p0gukmA0FM}KHcsdKAo-~JUT;Pcyx!H7x3w3Iq%c$bDqPelO1$KJ^1{%?$93| z&*=0i-LmZyrv z{CjyE{JMS43;6en1c7TE&*lpZpoLw%B0>JWG7etdA=g2*(|rLS%R~Ilp#0_6D{|eh zSLQzG@EJ#b0d7ZrK`uvrL1tgjO;w%2Afq__dtEpgeL7EhTAnDH^pY2CJJSS|{so@- zYfw+z-Y4@RsH-sO=C8woJv+aF_5^i)e{maB0D>1yeDKsfz~8hG%(wIC6=C=3^|AYp zd>kpLz1n)9MA5hNkmvssjt!RjzP+CROJDmiUhuH|P+kGj1R43(JXYf9(fp68M8RX!p^^~iqS;Rq2arp1IKYk{wW6> z8$NKpW_H}b5O zcWl}O{%zqFp!2zn`M0@Stzay5Z+Wh<&q=+)Pt@Z z2TQnJ4G+8)1C8&!mUi3$+IRx;1Ap6nP~cd8C>1h1*>b7G#}IU`BLB7n9*idrdoZ5! z>-Aw|^aNdsH|3ZU|27`RhM#(lpaX_EK9mSH{M6&0deEWa8>0iqxzhcw*+9Wpx*Z%` zEtg8S@NWwj@aQ$20?Jy+BGWt=KmC8?0qTJ@A7b%fya=-eB+^}O;L%&h=+XMkgWu(! zM{kG~6G0Um2o<|Pr%EDm!QO-007~E z{r~^}xBmbCFN(6j4{3iac>gD82NG!P0+QMqf7XLe&TB7ZVqoy;T^az|9Ra$h?3z#K zuNS?C7#W<7wb+708NjE^H2>!-*EYO_D11QqzhRFkSOtFzXolV6{~-^{3#G4KJ_Q9^ zZ*Mqw0PW!qAI*m)YKE8ix*d~{B_qo6u+l=@>_{rVDwb{;c5Ayl+y!rua@P- zveO=#CrZLRdbfZzBKsh1LW7MMLkSny9T!SJy*BV*Jmq8gt5n00f14Y-BmXu(4#$Q% zHinXrmIEc(pv~!wJGdAa7+X%3guHA6dy@6!T2RV&y|Dw-I$FU9+QjF21GLK#EbyA6 z+x1GPGmGJYPG*nBBcK=nRS4jXvMmQnY(Q-)#vPnsGfT}uHga}ecBzq= zj2mo5iD=8EQc?bGCz~HK9(>5vcoK9w#cPq4OC{_(8Mqi2UUS0bI1jpZ+c`Er|KrGc z-j#oQ84EO#AW{HmztD4+&VMeQp9~MYs5`*O(EQ_nsnm;U`xzO!89bVQu$0{MIQT%p zqxl5W%NwA(AzZH*9(W15@W7)Nv@Qvh)Oh6k)hl5!b{Lz9fa{%&c6gLfQJ^A z-R#F07(Ke(IT|b&_*?YBEna5ydTs4(!iP69<- zH)z<46STqn2P6N~Lms`RQu#~_9{esJpuNWnKA^GYUe->XKcE5I4IaI&CqQeR;DgyfLo*(|t}CGX7@dB^{yQFHQDN{r4mzHd!K2$5v~~Oh?3Q|uZf6Uh?h1}?42-4RKArzx{MZXx<#pVn zcQ2?lgt{~vUQeJkiHLXYm!8K4<(@NG(6ZGZlMb6vs6-`Wc*FhF-RcBk%m zT?jHCdcIv`4S+3eXhq&sBf_ zdvtpnbTESNCiw3lfq4U&PxoHXI&I(9|NJdtU~5wwJbG=7a+w(RfsR%NEsW}X_@WLp zg4CTY&~l)J-SL3fYL(eFQ3kbFZ>x9KvH0(46i%E zNSFx&Tp$A8d%m*X!M??JB6;O!64Sq~K+oxhvym`k~Qy306x zy6Xg93;J}IY52B&<8J{S_36`HXW-Eb*{9k04OGb5wf_144ZOgC5u9U7<2|~;n>IbV zLmQwAS(BlNo-kPba7n_XaJaIRM$n14`bYjXkaxK&SF}bc0JV1L&S@3!lzn z2cOPr50B;}70|;yJdQJi68q~nh%5`Lu8`N`ae$_4c3}AtOAYLYn_ZP&Q4bsza02F&3y?X_~ zy%f-?7#^KBeLEk#SULUA|7Nf-BY!I+Xs1i}T+ot956Dg19^K%e^y&5Z54{84ryG2M zk7x7m|B#dRJ-YW=K>Bw4pw)=Yphb@!-E$2!KAo-$d^N!nru^G@ zxIFk5pDuRywfqg5Hvyf4*~?<()A=zCHeL!kk;LA!^Q;HsPq5KqpfhcIeHq>Px2b5k zTd8Q2-1X7?@6}tQ&-j|zgYmel;cc&89)8fSXUmU8b6=hX`KR-zr{!t>=6#@(@&BLn zusq1$`~)Q4%VOyXT2}QEa*2frmz$LdQ^{oyX3%|Lu^V1*Ee-VOyboF<-udXovR#Y} ztp`BOt(O&`p{d>ym4BYSH7fj`mj8+!VD1Hvhu;HD`ILdx^S5|_6#qZ&`Tv}+<(IPi zpgkoPAfet8mA^3kMJM>T9Z2KPyO75J{y-YP1;YjYZ3ol%^KO9HH$46y_UQyikWc4U zP-uE|&Q$QP1C0EwN=yt4pPl*hS@b`<^XI$h3xGH!`U)T_Mc*KezvR$oC;ogN z?F*mXK-2>euSWX=h|mU4uOc%X|5n)NB-*<(^@a_ zw}MYUb>-U1@ztdd!>s=3k z$leS8{{Mf$vx<=+&4oW7%mXo852W$uLzMF8L$&6E%*js$Zx~AB&yU@Z#-AU$z_IfH zc>k?uZ>fMQ|F#35sh zu!b0p)y2q$&HeZPzh`f$0j6Oueyjkk?x?+h@G7!K&=phQGtu69_PW?I_*i}@uJM3e zn&#On!|v0&6jY+X(myCYmBxV54U0Y~b?|^v$04*7BH-2QqA%dtd{n^4@ec z_5tK~XpH%Eesk$O=+k-l#joX{mcb2Jpw9ta$go%I|Ns9C{H^|fKwDL&g7aKAxaHu{ z-3wZF=EA=XT%f&XcWn5;=)w5OqxDjWn1|)5V(tbT@dLHoFG1~8{?;v^c7o+8{uWn8 z1_u6ZEaEK(Dx*4&H9wPg;os&X&gj_u(B9^LnW1mzAs7B_F5*m%2OqLIHb1a;$e4Mk>qcR{Qv(y`1Ao=1`Y-W{wB}=|Nq-EFfcOg1Kp9~(R`2LC1_0! zdMO?R8efH#;&VY^;M2VoRMPnNR{wv^@4j}&Y8c0TZA z{Nri)g})he4X$r*0;uHHVREz5VFZ=j_q}>+*co4M^Xa_l(fpFJNY2ypc(Jz+q+l)% zapm8p!sTYA!c_9mhw=OCe#Z@rpq$~-dBCIb2&k$Abqye^(+~SFe)q8a=fUs(&!bmV zbUQP{i=R`$UF)0MKrPPm9-YTtJco$f-v(O!fBeObsf-LhjQ0`R?rmdccyS0M+RLi7 z9i;8ti>x`w4H$5J4L%P>0W{Rq`5U|%rML73c%rz9!voa5=mee00vfyaeZjviSi+~< z7<66ecaQJ)J@_3@gAN`^1$D$PKJ#t;R>JGiUCq&X_=U_CMuyHC9?eG-z(WL{pp7fc z9v2^gP5|Nrsd@2hGb8B8pBDj}L1*zm%5RW+__w)B7@qX7=J()tJP+E-)p@P+Q0z-k z_X^(Las;2R)%a!xBLl-XHb&6;nG(_6psPH5dQA(HnHX9wl_(P3ILnaJk|=j*p01Y>MKyWeY_QvyBU~# zdR-?vY-iiVh_3U(z@aP7c z=-KHY;L!~>z@r;%oM)#8hi4~)W9K)Aj?!Jeo#$OTe0OwS?g-uK(Yx0H6x6+?4KKj= z5%un^0Q0wkV&FwAGpJZ<1u5a*)(e(t{=voH!o|qI(EJn1=neSg)9dlO;TH?|Ok*F= ziJHBEzZ-tCfezL5w7kgQdJI&~b?@~68Qnbfpnj$qY$K}VJNbZ<@g_y4~O|F+&ru;|hWV1wt* z0P%W7`$2~jB(pU6Fkblo0MV8Pjjw>lN?So!5WM)d0n}dOZ+Z3i|Nob2ppK5^A^uj- zowF})L!?>-K=p#H?3> zR#58~5$m9KuSYjz!%{aa)AKRRyA^C+r|UwGZm@Qb?p}ytzB4^KT_<+CUg&h)&A-j{N{8!ykM34b z>&K(hwZW$|x5K3)b%IMr?T(Jvoh}`vyF7Yp8(w&A`1>DJrig)fphEFw`JeyIwGE8? zt&6~3tUch-4asbfzLifWIIVbe&IRRGpKf!H?syKL-r5GA-ld>Kgd713kWhRvHSf=V z(2*seYyLDnxAJOo6KrQMD4}`uvPNuS1Z@-pxyhrKRj3R! z4ABa5vqvvfCAg&V=w)4&1sacNJpoEpy{s2zgGzeW29M*e9iSe$$8pyMkdX*zG37eJ zGm-2?Db^yZ9VD9?{dE285lM*`E;K2?R?byi<8Sq6VqgFt zY3keg2<%2sZgb_|#-id0y5Fn?G1>eq3KAj)Iw=()(cnKQ5aRjy97<@r9Me?9dD`mLSC-Djhs=fb}&b`SXCJr{o0o3CXZ`M2??IPz~RQDJQOsOQk|P0xYjoFo5~!;TG~ zIei%qfrIE8e-o&%^JRSG(LG`|Nj4f3GycR@Q&7#rT)hpEEqR4LVWVl z1yu5Z0|)HsRuecM;&l~JQf~dm-;xh1iT|JT{11zUW9;lN_x}C=A7q?IFE3~a8`Qk_ zOJ-^GWIW;c|ABAkh1a>q94we$P6mzLfD94GG=%MC#ozz`!9F?Zqj{0P33RO!%qJck zj+;4MI^=yi(>Z)P-34Cy{QdvmaR+Fi{B)G~@&7~6`PQ#R9Kpl|Btb= zy<7=iOmo4bSM%!S3Jx|C|5*{|~yg7sR&u2Vpz^`~ScH-~a#b|3U7i1+hV=QhojR|3Bz( zC-(pU|AP`Oh%Ngc|NKO^J4ffi=7-Fn+{+1S34;g8J$fVl`*aul_vubx2dzg0mG!Kk za`%E~bBziILkWjZ=l2)uR)WgP`!HQE=dA?wf{r6A>4qqI(YzAW?EL@Y<_bmzwDx54 z8wv0h*xD05-Ri#GIiPixF)9L{mUlfm?}JWF^3?q6$!w$IX?fMN^SUp;6KGlTizJX; zjQ4#yKZ6bx74WtES}g0+U3$W^+YR9%1&>}<{<+Kyo}F$So|bp{Cmrx;Mm+%)G$7UK zd%>qW;lEFJ1v_Nyx`frI^ZEY^p3U_TpFV$aWCf&m`(pPBMuyibeL(AHk0Hq~fyvJX zF?v~N%>mU0pfeZ0zj(GBx$O^XU!a%g631Pyfa;3If1tirF~37c=@rP$PA@?np3V;k zU$8a)1NFO#C3y~XxL$E+cxLa=aIV3jqx34|u-ezTD)yZp55DAS{0Hik70WlAe9+;0 zg{Psz^(vC;YmR%E85kH|+c!M4Pvg%!md0OnL5P3b$uxc|g@cTpAHgF?Rtg808Xnq% zM~|!&4lt+jzhjKHU|dq{;5t%?3@C z|3Ohz2Z^fxFC>?Pqw2&9f#sm60&OI1hD-ig29^8_V)U|pngxre=P&G*fxIa10@*O& z(fJ8_rz~R$vq$Rzk6zQa-OLQJFXQ$9{7*wVJq@ZJR6hp?fO_z8o}Je`JHLDM%1)WZ z%tQe(v>>_pCv%aCN3ZQhh{$mUP=n=z z$A8ZcrM#f)3_Lo|$G)@)`TIZiut%@$>>11qFYa}LmRGze>H%2>O28QF@xVhTpg{TS z0Y1^2fq?-u&@KTQOPjS6JmqN$;xKsj`Y{?Fn9#6?70d-*hWJa3d_Gtd`zP!kz`3GZ34yZ6=Doq8gooGJE=)?H&g9rac-dP8k8A@-w z1PxL8^xF1JWM**b{L%b?8Fa8;>l;u#-dq0Pw>$m6Z+AV;LSX+AP#O09#r7qP3@^8X z=5IT5XYhv~;Ma8Bz_01LAnmy80S55)j&5DegUk#*y|oKoD}nZxbiVX}IL4>*1=u}Y z2bmf8TgpLQht8vp-;OfzPdVV({Fjk`>Os%uzaRJ~9a1>ea;Wr}XY(I+{z(TQDh{?B z3rGzkn@=90S3^nY|yL_s66rLE#X}R(pxGE+Ohz( z+_d)qGec=RxRnCgZ{=~^bpu#^^D!2fqKpH~4E(Ke|3Oy^9c3wv@aVM-J;2QHVnWd0 z|F6A}Gk^nwPj~7L-_|GmEuh;OJ~;mKQ86e{_wCJ5F@Tuw*8;)I;!qDG!%J|B zz1wvMD2MTX@c7SB$_d(L@gg+)sy!Tqf5zL45ct8bZ7s%Xh)|dO38D6VE*OPnn2E6y^ya-A|9<7%=_+3tc6VcIq z%nT(`E0{{|uiz+g0H^qe-@r1f_c1e+KJ(~xX7mIdi!1Q0nsC?mmaifC~T)ud8*K>Guv%i)Bo%e(_eE3Vm zRxp)3Ucpf!fD|_TrLRDz${&k;X*Ld+nFE?#f#h8GzY-~WU5+;v{z-^RLn4>QAl3((Qy_vb^B zy)$TN@i^$~;Iqc}fsLJ#$;j}!*rS(KaS|vAx^D0|?z#h11ifQn;Q>2^@I4&AU%ALN(T z7Er|nO1RCn8yHF~KneD>u1DwL51#+|N;N?(+#pa8b>4rWG!L9KeOv#R2s$2PXYg%( z0%`*Pf3X@YasLJ5JaA${jHiC^_|IR;7W-1b7gSfGmY1ORpGWim|0TR1JpTK0fRrWA zV`PXu49)Kld3(?rneKXy*q3f#NrV|7d2o6S1*O==H=q!Ktk3Z2R`co3y{=dGFvE=}Nt3GJFwKMjFV|Sdu_rnYf4Gg9J9=)bL_Dl@mE-OSW zNF`{V8Ki=N2{Nh!X0!0OT7eR7ckBs|?rI58Gvh7SpZ_5Bmq7sa zbRmq}0=fp;^*Wc3)t>z36I{=4KGTd7Ds_BUIvx{xyo5$ALv?BkIqBT@C2Q=ckmTU z=P`$dXZ*c&1}?pB;SLS|xE%PmeRt&F_JiN)pF_tUUNO*cTyNojkLDi&9-SA8PrL+O z5(%=`qZ`>w2C~fqol`@cdFJ@t$M4a3q5OnL^TB^Uy)C?;X%)wvAU}I*e)Q-S)v{+| z0AI8Y8XN=Vq8CxnkTL+>$_nun!bjlq2+_iaBKud_fihj~2Cy5z!}u?5azfGw_&Tue zTIjilru(izlga-82~ZLO9cbtH%}uDG)ZDY%4%C z3rRLImL~XitNC{4alGtiU|=}rI)f3)_vuXC;M1AA!=u-A24v?VXrnoJ%FUw}R8q8p z0?wnCRiy}&+#utkz0k?=`!B-4=9h?VWb8ZvsxBFjCm{}dK-MI{hEsb@KVL=m%0|Z0 zlODaMJ1dcMRj5Sg!PlG8F#5k99-W^-TT6Yq>jgZz?ZEwxUQt?L1p#o|{OK^B7tX+gCF-w%RHG5*$Bpb^Av>|ibnf9rV$28NxW&Uoh` z8?GW9{%t3EMLK&$E*^ZvlYJ_s^SFb?c?bS&=RJCDmt1CK;NQ;UpVsNy2wf`cz(3`9 z%K`qW2OL^{luGh%JLJ(TS_@LP&BckaqlLE~VFQ7w;aO-@!T3 z@Z<|uwm<(p8jpaS8GD$28~c7x3DS8eZGuPV&jYVmI*&UvJm+`mbvJP7^$&Mw_|F9@ zD_!}w9dzM$I_}cZ!dnNjrML3G4S(l_@>8$vpax*mZ%$Z0q%?EkZGaVJKRtRyvYgH z5wQNa60|>Fh}<7P@LC?W+|sl2C@3d@x+>rSHBq0=Y>o|#;L9Z(V;rH`z^4~9um&2% zKp9*E4`aXv*Z#iP2CWt8xN+>i7d+mI7DVGuLq1YQV0^pMj&P%0;V$cb$>s7VDHasn?g@ae6+ z;?t|^(8?Cs+*x(5q9 z_vFCg(d{7d{{bj%yvXiA9-;)B54!KATOL#^c|cdkw%#t$^TkOn?j?dDieWJ2$Y_W)hP!tK#3+S~{Vf**&$ zj(FA%RstPQGduul4!1CX07%+nM}_0E@IvYCogTY#V^3ex33#0xdl-@bK_@hHTg1N1 znFQ+}vU`B|F=%`}G`<{^4=O)E>q#wOXCojkx!DgM@#J^@VfcR+=vFJA&L1A%9~Gpb zUUHKTzvKpVI1K0%BJhDHknYF{@MXr(&ea8vUf&(ytCc)D552fw54z;$0%M6BXz?d# zq}sRB&ZGH=1#G=%=dBmhZyJIiBW`d1fCP322lL_E#3m?FAec{pT3Oa!2fJZOubVtz00jN#FdIp?K zAQPyd<}7@?s@D~C1=0f0co$f_*K{LDX>aX>7b}Vw8D1y8XvAlQ3Bn3TkiJf$tWX0f z1zF)*1nWQc*1mX={+5B^H4msa3F1Y*Wnch}d%WytWO$)cfgD|s_Mb=d8;8!H9=*2W zt3ji(KHZ>kSY4DDkhL>Lap8$%CBMz~LUu@w7 zjoz|`f-45s1E9V!I2aJY{{y^!6C5ibrKJa6JOC|h25n4hJy62w(OdiB#kw~P3@>I@ zGBUhaU&hGrVpSQkV^Piz0p$+~NJrD7*S2pJ$l;*Er1?KnncxSH|L!07`#|kPkLKea zUgz+)3W2(0y|#N-!W6dtFSYjQX8p4odGr=^-o%gp{4Jn;MIOB#jNlQho2!`_N}qf5 z+BQHmcb@m?2GD^dprri5@g3-3m*b$Jh0fXw$6OyUK++)ciH#FhFqS5I zbRO(H)clCaqxs<<@cOD=AJGdStGG)aytY8CPe5lbeQ^BeXHX*V+goQa!Lz##K76^T z4_xS#z6K3nvh#0qeb92C_a z2XM|ob}neWQs)OyE9QYmH-}Gm0mlcA|BQ9v9^Deh90V9QFt#}x*!(Zy{^0SSyY%X7 zA<%ePceTcg%e{;YV6lUsbed`N_y6ndpivi4_|uyJ#6Jce-R%6^IF2zeH2h@bZ(YX->Y%-6 z@aSgUv630ISB06sbt!1EWbJzf{#J8Hdg)ybNiPzhF^ke`ryi)>{4p%TUcvv1T zS>e%b`vSxU?R3y)WMFs^*9$4zmV(EBWG}B^W&p=bC+h`}I@lo8an}pb40i(*Y~bYA zY`bLzXhBpfGsynqtczDLgBEN32Q}nZFf+UW&#i;6WMf?eG7G%opLHgL3A)JsXdKCu0#M=sm5&h5uV2p00IKmkI$7sHm@v<`g1DWmWgzN5Yx;6#c)5Dwg&_k2!+&1y z<~lN4h`*_{Ti|pM%U4XRw0~;{i<|z^008 zA@h&VU(D=+wDTr*fy!xX2GE&qpvgLT^WPJ6J1bmy7F2ms7bC-KX3*7)AU8nPYWjiY z{=Zn#iCiQ@^H;cI7}!7H_I!7_fCp&JMGkmI>Ay!e=#WO=1FnYOz!gAe>511;phYbL z%AhM0N+Mo@6sEz}vp61emtgel{OYUu6|}U)t5?R*vk|nQ06sx^6+BA;8f;hs_STR8 zC0d@HM?e}uW1;*lqTo}8pMh>5@U;9^wD#q5Q0oKQ-h1)8gOS0dTfy_-69v!aGfZG@ zknM7y#iO23q=qfZ3y$*Bg{|l35x(7*G6v0CG3KGJh-R09nu87?uB^ zH3I*AyHiw6fSpWb3|8)>{h035j2U92nQmE-__~f;ptKnNm(0mSqr{%XY8;@R5kvh=K$oCgV zvKSeBdR4C$Ff)M4XI0PR?x4mngO6qDi4t+p8a42x%dcL{?tr+`w=-V=WH;&}1(3@? zQ@;mZKL)2AgfDeFAih)u(cPevl)t@@2XD3d|02H~RGH_4`hRTy{{P?i@Be?$N>aoB z|Nn#YLs&4VL!b{i-Se=A=I0^}52*V}Btbj;JQ$CG4haJ__inxT*$&D8aRT7#$D>!& zuol#E{|;LEcP|q;b%Oe1AoD#ymqm7qgI6>=ad;dD%_B2_*7qhIV`PN1X)eIpDaTzu zfU>E_an~1My4&{w^mw5Q9^IigpvMb&9CrY*Vbtvq{;(TiA?-$kP%uB z4)83~6;NgXEj9)nHsA}|4S4}+SEPayXcZ!8oQ^fN3=}7>9UjMBCxC(*)GP*vXRqrH zk51Pape2dm>Z-T)zDI9q$BQdQpa#le*gW_u(9nJ74Uhk)(U$)3x0HityTAXx@G=*q zsGHYu5i_X4`e;7b-JliSU^iQG@Iy9w_OddAi(*&sZc1<#L+s?tafG{hWhsenRzP+0 zH&EbUck^NJ^f<`Ppg}W_Ue@y_OrYs;u!}(D4pJ!Ia)7%?oKzR}8KQ(@3T_t}!d;{e zQq;}+djY5cf(Wzo;8F`~m{}lPSXx42n7uJTaUp026=)3!mUssB8NtiEUbcb?yI$7W z#yG=lnLRwr9v747q9|k+m6~964QLhCcjQw2`wQDKTrt=T8Z3UF94d{GxX_A=6EfE z*1iUvP6`^G76zI9gQ4U-=tRI58=4pyUO(|{wqpQIFe6sM&1hl-A3p+i7)t*MG+WpF zM!}==vt#E!Xc@?_>3YDYw-)ID{Im(XK;@eY=w9Z3Fa9-wnh`(#m&(7`+6eA0gEn~I zMT|He12;@xTX{79P%L)w=;rq6t`_jo{OMtN!n5;Oxf`f8yMhLs8PblswrpcO?h0D84&roy zdIoKb9=&@XfE$ga4KKFt`~SZg%w;T<^XLZe>G14yMd}86cDgdW>}O_R=->H2e-4 z?Er70;MZ&g?a|=ZYz5u^$*&1s{?4xnp5o!x1n-65*K7r~1Nb$;i{1G(TOsyt1yP-~ zS6*CiUu=jN;Ol zcbOO%UVN@+1Ze_yV?EFeaooe8z`*cY1=J#E0o{S|LG`~pXwk9)0|P|PQ}YPubaH;D zhaSD9JGyJHyZ|j0_w2mBg0X~sGviAp&^jhi7v>hYoJn2a(HT1fbk4C)ckKyR!*9o3 zw}7hy#5Q@K&fFC~o#0z8JT32+YWi4yE>-ESy#g}-h1=i%|G|crE(T9OZ3UP7u{%Jf zfmADc_PQ&0_sZCNSl;m9cZb|Ikp^l9w*D{WGXz~o+znEld*HPXc*3L;9K=4IdqKMk zz^T@w6CA1Fac9U*f?FQFy`UI)aUusaEY`rt-{JwPd%ANwK)2O+bjI!g-6gO2$CvRh z=;jO`%LhLEP7gdA|NQ&^zupnl%BlC|clv@zjr{Tq-L+RhhZc2yda(f%0ifd&gkPlB zf!d@Np3VPRO76V`UHJ>$0SLa2-5t~(JmJZB+^4hl1o$|EdEi8R%0u&zN9R<~zWDCa zE&q@CSUvz>%ERycsk3xTL+uO(&?Qwq-QY0gZ&?P~(O2y%0A^|`M`tU6?6vbf|u!_T`vcpD|j}aX95*ZlA9S{ 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

    G?&OB^kM(+@q^& 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�mebnhLQ%djP!%U&#FC4|Gkz_9S`nRC*IF2nc> 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 z z{|pTJ{}~wk|1&Tc{AXYY_|L##_@99x@IM2C(SHVpp#KaE#{U@@g8wrxfNneh-CAJ! zpMfFtKLdl=e+GuI{|pS~{}~v<|1&UH{AXZ@_|L##`JaIy@;?KE)qe(tDA0ll7DfgQ z7Dfg}7Dfh57Dfgp7Dfgw7Dfip5$)P6j0`L+j0`#~j0~XbX>?f_8Q54D8T42f8Q57E z8T45g88}!N84Oq$88}%O84Ot%89NU|_8II=J@NU<<7II%D? zNV709II}P^$gnUnxUeuX$g(gpxUw)Z$gwaoxUn!Y$g?mqxU(=aD6lXxc(5=sD6%jz zc(O1uD6udyc(E`tD6=p!c(X7vsIV|H_^>cCsIo9J__8oEsIf3I_^~iDsIxFK__HuF zY+zwzxWU56u#tt4;U)_s!zLC+hFdI*44YXP8E&&MGHhXCWVpk^$gq`#k>M^2Bf~Zp zMuvMVj11dZ7#Z%fFf#05VPtr~!pN|bg^}SQ3nRlW7Dk3gEQ}1hSr{1}voJF3VPRx= z!otX~mxYnxDGMXRJ{CrXXDo~i`&k$np0hAA9AIH&c)`NRaFB(O;UxM>1Bf~KkMuvARj10$F7#ZHPFfyEAVPyEg!pLxv zg^}SS3nRlR7Dk3oEQ}1NSr{2UvoJE8VPRzW!otXKmW7ewD+?pTITl8SZ!C-q=UEsT zzOyhgTwq~j_`$-+aFKg<43}9L8Gf@cG8C{fGE8S>WGG~1WSGIq z$WX+}$S{+Yk)fECkzp1qBSQ%*Bg1S~Mut*WMus`8j0|P0j0|&G85zo185!oWGBQ-K zGBV6(Wn`#iWn@^u%E(Z~%E+*gm64%_m62gFDsT2XI#?MQ*0VA)bh0usY+z+%=wfAL*vQJr(8J2eu$h&Sp_i4B zVGAoGLmw+6!&X*ChJIE?hHb2j3=>!x8Md=BGE8J;WZ1#V$S{eOkzpq*Bf}I{Muy$2 zj0{s*85#C~+{eSnaDa!Ap^=A?;UEtqLlX}p!yz6l$k4&V$Z&#(k)e}^k>Ml{BSRMtBf}{kMuu)4MuyWo zj0`=3!*G%)`hqg@=*h3J)X0R31i#t2~Sh(|8yeuJJH3Oy^-_xX#1KFoTDY z;RX*Q!%QAVhMPQ$46}F`8E)|~GR)>-WVp@4$S{Y8k>L&xBg0%CMuxjQj12R57#Z&I zFfz>NVPv?^!^p6Jhmqj{4L#wBg0xAMuxXMj122|7#ZI2Ffy#?VPtsE!^p6Khmqj}4$K@G>$4@-i|M@-i|6@iH%#@G>%l z@-i}%@-i}n@iH=$@iH=m^D;7&^D;6-@G>$~@G>$)@-i}1@-i|+@iH=0@iH<*^D;72 z^D;8T@G>&g@G>&Q@-i~i@-i~S@iH>h@iH>R^D;8j^D;6d@G>$q@G>$a@-i|s@-i|c z@iH&A@G>%_@-i~C@-i|kkYHpuAi>D6P=b-+padhsA_+!@ zLlTS(izOHt4ofgHERkSjI3mHwuvCJP;iv>7!!ij*hGPR8SBf}~QMut-oj0~$K7#U7WFfy!>U}QKW!N{;yf|22@1S7*b2}Xu<5{wM% zB^VjbOE5BQkYHrEAi>D6QG$`-q68zuCJ9D{OA?F>nnY>{AOxFW&GuvLPQ z;i?29!!`*REUBf~BUMuuAwj10Ra7#VI$ zFf#0sU}U%>!N{;zf|22_1S7*f2}Xu{5{wM{B^Vj*OE5Ax@OJ^G+#0@aJ*z>(0a+p z!1{gH%(4G)tpoLrW8rR8tdELko)(BV$ubeb`CQi8(p> z$@;}b$@8lo0F+)pl58TX9x~K(ETv_iAhOCsbzXa#YieM5{omE zit@|zbPeogUFDTZBT|xu7M}mO?Y$zm@!0AFaB{erOFFhv}6aacgCSaH6 zq^2h(gVL6jm9vh9osF8718AW-ahl5v^ooiT(sE0ztiZSPBor$YXXKaWq$nh%DrDvs z7v+Ms-xMVjl;%}}ZmOu(tu@HZD@iTNOUzM7Eh@?{ zvQkI|@l#V2O7n_R6O%I_QInsNYNb$IQe`R!g`CXX%n}8#gHls!?bYoRbQGdvt*ok( zbMlKzi&B*plJoP5OBAdX6tope^NJJGQWcaG((*JEY!%WHbBa@|6{-~!Y7Hv%a&pRY z^@xk3%p|>{;%bm1(d>r=CnW3@KmnqV5S&?+>H~6QYKmKKiDOD)K}l*+0yv0LGK-TF zi&9ck;A&heG7?LROHxz7?jR!p;w!oG63a5v6H7p)2iSN>QpBENb29Tl>R<(8aY+g| zb%HNX%FfJ7*DES6N(B{zpsNlu^D;}oCX(S`c#cU<&B@Wv&r1ebOR-_aIho0+`k+gF z^ozk|h<;&dW^y)209FW5;#SbX{-9f}3Lqh&my()PnjQ}=focpF+Vl$Tr@ z4=J4>>N4}v@}V-|Fo%eOnDNCWFcWg}lVNJ|lXEhQOJIhfm|T>Xjx2}JUR+WXpP!ai zoC?=hg6fEp%7RqnqB%b)D>b$bH5t4}TYk*Xs zB4mRY?XYp;=iamlhSJ=9Oq@f&vh9cN@IwN=z$BEdm<{ zQLA38kdvRBn4g zwA7;16cD4lD6=FLbPpWD^U|f|5pNo38rX130 z%gIm92Aigkk_o!#F2ATUvseMN0~*SM3xfkY6?AWiz|yuQgapZ zixi4W@{1DFK}BdvW@#>xTuweHj~0RUPJ=B0*$-(}X{73<>nRjh=H_JPWh;QxX%?3x z=AL*Yft<);^= z78gTq>jAf*OY@QwOH$MGi!!THQy>Mce{pVVadBdLD!9q$Tv}X`p9@y2ppjn;QKEn) zl^2(!Wafj*uFRa&6b10Da-ia;Bp+0a=9iXW(F45_R3Q!0)(0yuO3W+)^$T)RLBRki z>WflKN{jMRQxp>OpbjX{NX-LhzS6vu)S{e9kXF=O4ldcVL3&CQtT2kCg2a*xy`o}0 zbVn6e<|XSFGqABxdFm zDYWMJ4fYJE1b*s}9lSiWR^BbQdDb2?`2np!}tvr>7UoWmB4$otIypXZL>p z-rUsO{Gv)&MFPsn`DqFePbxslQ!cC(UQlT0h|3NRD&U^F<@?TeQ-4zb1v7A78 z&o95k(FI(SH8mTQr~*M%iYv560T~vOpYH=| zmIp!W6Q|PRN>_Mw0#X4IaRyZ;sl_hf+N3xnKi@YouhJ(oFB{f10VxEfN=ImA;*(ii z0x~*3FWnO~U;?Q>LgAH$Ykr!4X^DTDFF2=q<^@123jdOf)S^)2Dgr%G6jv7O7bNEB zm*!~1)WT0swZf>rP<#Pa9uKnyRA?uHYqXMl zSP_;As)sOaumbxwH3gKT3X1YeQj;NfK_gp?a3{2Zo~n>Yg3Caa6S%fPaTO?iD}aST zPQcP}!W=&>&P>ls%+binOiH%03ifml@C3IP?TDhnO|C*Qwix~L8@-dE*8j0aNQ0X&CxF`O)UkPiY;A&RTY(k0u`Y}TtPgf!Q8Bc_1XnSzOUFSSKzLEF0P88DI|o~p0M`YYxq#G3Sloq8C!#U|kN@fC z6qSOSXpqq-*g#e}Y-9>DNCoGD`c`@=2rg)J3YiD;YcY}n#K06>6?kw8nTHslD$ggz z>u4T;jj=oD=ar=9l@#m4O7&uWmvF}*HwFeW3PxhFxRTFLo2`9->hda#KVh&QQWc0~zj+!YjruyL{S{Gycj;#5$Fvn0O= zRKL;Eo`M`u7l2CEK<|&$Eze9T$pHCD&k~#}!2MZ}Pe6y%gD@i#1H%#q2nMm2GcYi$ zU|?VXu~#uLFn}kJGGH=t@jXC*y^vO#A(fiOtT3+UNSAU5bsC(wCLAU5dyCJ+X(L1#CC&TRs* zzeCSt0s?+AoJY!B!;M<6!noFfnhv0>*Mf!MIK zi9l@7nM5EAVuQ}T0bvjuboLDhgV><+Z$KEt2AzQe!XP&292^h^u|a3yfG~)C3wkCF zhz&Xu2ZTZFKqdwT5C*XmnHU&A7{orp#J~W;Aog)41_lrYu}?BFFr0>-Uvrj;fdPa; z;un}07(f`rzQM%6a2u-TE)xR-2!q7$GchoLFo?~-%)pQjItzpWa{dbF3>1(!=nNFl zxh5d?MrH;E(77leHt1Xw&{-!SHY4=x5D*)5mIvre3lJN0mIw%g*v%{q44o{Hw#GzO z28O+?5cYmn1_lrY@eZ;wFdTx4A7N!+I0|JSXJudjVUQXIHUAQ14BGiynu~?0fa%~*Vq^s?n1@yvoSC{fwG^oF)+M2n>!XWk;9tMW9P`!_M7#JQy z*$li442--W6$}j4ybKIBP_`p41A_~c?aRx+0Ky<9q{hDazoPJn?S0m?2EU|=YLvYP}L7(f_geyacjLmO1QSAc;5ghAqS zL3{I|;tYZe4Do`HGtCkN85sTxf>eOc0%c%`7lyEf#2FYw#35`WaRvrsC_6};fgu>m zE)r*8D2B4Ph%+#3g|fGcGcfFhvX6-~Fn}<~q?6(d440wecf}bPKo}&>Ey2LRF9BjP zFtkcAFtkD0DBne_MFsMs1Fla#8Mv@E+APkbRlw@GAhKh?zF)&C- zfkYV?q@^Gj#0!&RU;tqdJ3@+qAr7i0L5hI^ghAp-QVa|r3}UBBF)-vn)f7uHFw{WV z^->HBAPiEoL5hLl98{b^nt_2?8YIfVU?L5{AfA~t1A`S*+(w##0fa%~PSOkvAPiz> zOEWMOL)A1$GcYtl*^8tZ7?wiWE2J41RzlgUr5PC3LfOxx85lqqWX=m|28NeV@wd_p z3?K{=e=p6z@DVDmBg4R8ECUi{U@((mU@(WWFUT-3T!ON%$S^QGfU+Nf&R>AC--6Cq zfU>`U&P{-_e}K+LfU*zBGBEs?g_zH+z`($-0AUL#Fff2HsNNS=U|OzU;tqdTTzjL zK}iu3CTfZd3?K{=2kox~VGui5k%1uuswP^Ifgujc2JN}chO$9>Z$bNJLFzjd85lqq z#GatYz%Uc4W``mJ!%issk|F~G2!qsIQDk5MVG#SdA_D^mgV+p83=AL)Vlyc*Ffc1Y z+{v!Qz`y}zb1N}0fG|i6zY+rj2!q&yN(>Aj3}TBaF))BIh%KeWzyQJ^wyY8Zg96kX zWhDj%5C(~>DlssCFo>TcEtKt{#J~W;AT>Tp z3=AL)Vh1TPFn}lekBG55C*Y@lo=R67{r!RW?+z3hLp*g$_xxz zQ1(k@28K6K_6KDK1`r0FMfq8ofdPa;>;&lEV30YlRTvn&RUz>bpvu5d3}rW{GBET* z*^^Wm7^Xtm3se~x7DCyJRT&soLfM;C85lN0+1peZ7`8*%dsG=1_CnbQR2di!LfOYu z85mAL*{4+*7(f{0o^z@U3>Tr|k5m~Loq2BghAq~)EF36L+#zG#=x)-%HFTWzyQJ^Gmoe-FdT)7A5&vs zI14rBoEifI2!qTyug1V|87h87je!A#LE=}{7#JQx#UHCNFn}<~%ol153@@SLuhbYA zKp3R{wHgD%N2vHGH3o*yPIkhTl*&g8>7BrU67;%YcDF z*8pOUo&f`c9aP-WfPrBels(gcfngR@&1?e(hW}7;Rzn5`eM5*jhK39beNgrULk5P& zQ1%By1_n7Jh#CbW1_pO1+rx-~!QTkt#{eS+hW)+}H4J_X3_VLA>+^N`O6p> zik3m#S+b0Qp%%)Xw~T>dK9v1o83V&dD4S^o0|N+ybg->pU;tqdn|lQV0|-YzyQJ^z1vnYFzkei?^(&fuoudHx{`qbgh6UFS1~Z?fd0|P&30FHryLEs+) zgXBMm8ijuh4Ej)UgMSPRK~V9Ke+&$1Plv7oCp6I z7@k4dKmRi@{08ka1)anGpMl};e~8=uGBPrNFi7tMCPoGj2C>VS85ycT2eX65AGsJA zm_Y}zGcYi)axpTnLD@W9j12rxwgnd>gB6r*&&9|9!XR^=axpSIgNm#0GBT)x4smB- zU|7w^$gl=n^ z1Y?kRu>d1O2~@mZfRUjQ%HAx%$gmB{-YLMyunWrGE5OJA!XUke1Q;1W7{op+z{mi? zAU5b=c_z@|@(c_N>Vk|6+EBKxAR~hblwB;y$N<71^$miI42@9nZGwyp`=IQTf{YAj zpzQO4j0_+QQm-$>$Y2OMxSoN5!9<7=j6vdNLW~UNP;omUMg|ZDiQgAuWB_3h`>_xs z!z-v7(82eNput%N1_oL)B7{uNz!pN`#s%Do6BLfJ7#P^FZG8}-49};0?I1FWP z5@lrA3ObOVfq`Li%(oX~ zWN?6ruNGru0AY~$S}{fj5C*X~h%qv3g{s*u#>fD|AaPl7Mh1D%WdNXNv^XP!DwM4* z&d8t*W$Q~YGMIxd3t(Viu#{kAu!6F!B^eoPKouPD87!b|YbizsJ1E;-ije_?LFxmg z7#YH#;*nB}4AD?_mlPwzG$?z96eGh-DBD1Wk--dfxd8(MgM|zugDsS8FT= zSw;p|DBE3@kpYB3c1@6FWS9sQpDN490Ky>g>9ULrAPi!EmSbf20=kHSfq}umgpt7l zba?{k{)sF`hA*It2SDQ4j0|R=fqn)C2LF6UFb3)E&u3(q1iFX-bYDh3Bg5l-M(|nj zPx2WVKp3RvLp~$JN2oaH!h-MZ5H$=Pj0{(MpzK~o2990``@?!hhR^FE?62z?8Gb|A zf7dfI{DZPVhx#jRgs2JF$jA@`Wv6UpWJrgy3pO$`{N4mnGj|Uo!@NC^GHt;gMux>u zHt3*!UC?3uAU~dGWN?GB(=ReIWP%RwXJB9`zR1YX1ZB5gWMt@uvR7SXWLOJjJ6&RA zZ~;wnfINJQk--c!i2*7HZZR_CK-s0Y7#Tnqq@?8*BLfJ7*t2gjGR%jnS$K<)VLg-$ zx&(j?bP)gp0|UncMg|EeTlxVbgCdlz{D6@`1bq3jDU7#Xg;fP{(5OGXB-mk>6~2Sx@E2DJmve_&()VGx_)BO?RX zM@H~mo4`j#23;uI;3FeLCX`+9k&&Sn%I^Ef$N<71z5O2<874r*Cw^pPnDr47FS9=~ zGOU4$gDxvz`UDXd_{7Ly4P`rgVr1}uvI9ObG6X`|37;4lQlV_nWd{nMA$oN`GcuS$ z+2)@a8ET;Hy3dRZlcDSdpBWiWLD}a&GcsI;vLAeAWcUDOfBDSF0Ky>m|M<+vVD|-L zzQY$r1`q~`JAGkf_yZOH|Ampk_A5k<_g6*+KPWrkDL*0p<|iYAE0pd2lab*+ zlwI(Pk)h-lL`}snMur+FyWtljLmQMm;TI#r8Yp|iFGhx4Q1*dej0{Ji>%2Jc{q3gAai&KlhTX53A zE90ybK>Os1A$xTd>}(ZGLo!koAp4IL^3xOw@{3D!i<1*U%fb^&Q!?}6t9e1&ATx_W zi(|?Y%XCvR)6&3JC1)h&<)!Atg9NZ{D=tb+i-#%#*&1I^lp0@BoU5U!mzY!x)~1nM zT7q4Prh*L@XqjqqeqLH;x*mAVa(r=SRVwBxIq*)l`23>GbdW6?nhG|y3OV_uDS4^I z#qq_d={U407NjN?#%HEf#6t{7E-lf^ERF|ngwW8$v<6*$QED1~^+XyAQAVU`U|%9! z=~$wWlbTpuqL81L3SHL?TY_CslvbgZEjarIsszc8_MJ7GwA=D=9Y~Y>Hk{F?d@9XsI8X z(TOFX9UQ3&R>;fwi%JSYimeo^6u=Cy-NhwEnaL$Nl?s`8pe2FDnR)3}3Mr)pIhmlX z7%5Pd;2p?T3ZV7oA(^?Upv0$8lw1a3BgAo=2-B%xgDtorVU(E%2}(q$W#)l)0$>I$ zL_Iw06>N}V1GM8L8&eA?_kv0T1I27aNP*UD!nWT)N(em#SLm*6D+L{e#N?9F#2hOH zlo%*V1?_UtD=OyYVqid#DNif|XLr4#V$hbPOi=0qE!$5`!E_I_z$n%OZRG+L6*)N? zHOV#L!T?+#=;16@6l`tb%D_q?1yr$KT4qskiH4?LX&z_^n1&{9Be3LqG8~j&TmmWR zC~y(n46>Yrvs@+KML3P1j)UO-qrgRQGc=1*(;($Xa%l;K0Xmc*x1a>H%sVR|vimS! z0dXb=WD`zKYMz3f0{DyoD=S~e2y&N9BI;kz>eQm*+{_dOJ5b$rtEp(S^142A3rYHjvXmz#P;97gT*>)tHo+mjcrVVi!}^NE+2W8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OLn8z*2e~p8Y(VE2f`-pgJJschprbA|G*LR~ zpkdpRV#LrUieza?W=^plXvm>B7qnqHH3eoe|3ZT&u(AZvXCR{Oi ztQT|~1?Y??B*oA{;9^Y$(8)I7gR>Izpn?h@wV*iz&@oe>5k2?`H{=eWfF^{%lM2Np zMfwGxv74eiy`th$_}K>vu;Iv1&^b^F$@wX%3I+L@c_oxjP$lOi7U&x58R!}4CZ(1n z>OoG^EJ!RW1|8X2lAMuR3_3F*9umBuuqM}+D8odM0|23?P(Y?=K!a7_{pE;}SkM7M z`UORqWr-!J`elhl`X#vq`bnjkIVr{ZiMgrz;F%iTlKlLfVtshJfHOen(1KDA=D;{; zyD%u$A;aX5(|=Nnif9#@U>Adq3DAd3PH_c*PB>DCcJ&K!4T@FJSBMUB4G9hMivh!AMdx@4wj zmIUM%`$JEF^-E2MF)8;g_&mnk!~$JIJ#*;BY|tscpyUkOSI)oy-FgVyn#BO!(gmlX zTNxQV^TDwJIZ_qO49U+A&rC@TMI0do^;9r;h6bXN+|&qiBskB3M%wjDKqopC>x0;! za|QK42M*+b<^mIfQj1G-O02A4qvr{rQ@0ZH6cSvEiV_sc5_3vZ$#nuELnP%_q^9T< z6$gN_oo8MN*!$3p=iscU02(b#Pc2dapN$Hhr2`!vo0*pmD;ZKi5v>58kpa#7fF^MA z((_8er#^yD=mH(t0!m+LnI#|vB_Mu@0_X%wBvr+^i8(oVRY4C}O9h`o20FS3J{k)) z3+w~8ocu&AenMPY6_s4Ur>Pn8lFxC&%#k&>>yWK zI4ptHAOxoRD02&>RxM8~1N#eH7b2a?28zPm#1bn7HE44Jx($f|#z&_!|Nj5q|M&m@ zHGlvAzx4P2{~v$<|ED~*GC+qJ>l*8sK+6$O)aVrzyMZU~K?xEhppcrEUz(l)IhIQ= zH?tTtFR750TcQA-B*@EG2zLx~RVYbJhh&7je1#xSH#dX;s6IhVsDh>tV9kYM&}ohe z1*t`_N5B$Sut&$plRj=cg%HWac532p}8-%a?H1fu}HVm}mhyXVB0L zslrFqh9m*Ac!aQi70YzO6O0DDseT6q?wLn}|pOIz?E$FL(|DOzt*lvo~*SaSkRI^eUziy)`1 zCgv${!G)2Vl0~U$$)zPF#kr9AI?xPWd14u;g<76i2I9e`Kw@wXNCwnENX*PDRsf$> z2~Nb2Xed_X0x8BPpO>Gf;N%LK7caI_P|#4-zxWTX7}lWP(>$fUL>P12w~S6hIXZ$ceD5 zTV4b@f;>%=3p6vWn^*)Y1d6Q`e0^LLauX}SbM0m6APTgiCAG*(K~+bgurw2NKBkp| zDi>58=yW&!l6p{b26;KPC^fIdiVMU|$}G`Mtw>BR(c^+l z45lc6&c@HrE4JdYQqa}qvQo$atu}%B7G#?$s2h+{S_D1;lFLd#0Wu@0qfn9$I^rrn zO#!p_Q=|u3X<}omP^6cdmjWx8i@{NlTEwLQ3M(rG1yFy*N&(W!0UweJKXwy(W+vD& zSZfy^Nw6*vtlTD4p3nUL|Ns8)|Nr0l{{KJIkN^KQe*6dTG$6L!HtFmC|I@$z|G)U_ z|Nra0{{LUX0?`j!e+Fyc!D#5w7@%V^z%>qx59T1PbA#Crqhaze^)MP{E{w*tYz?L! zM#J0(RJuq=x>R{%9 zj=un5&=DOV4AY0S?Hz1{5SRq5ii9v=^3bjC3@~}<@dL2cl?SoZ(_#by8hGqC>uFTnc$|Knx<|9@Nd|3Am_ z|Nljo|NpPN{QrN8<^TV?EdT%CfBFCaDa-%=uU!8Bf5-Cw|EDeg|9|E3|NqY~|NozT z#sB{bEB^mCU-AFH^NRofeL#HH|Np~Q{QsZ3;{X5Z761QtuK53d+KT`G3mE?YU$x@@ z|D!AZ|2JU$|Nrue|NmdE`2YXkivRyPSN{Jmzw-Zoot6Lpo3H%;-)ZIl|BMWp%9@%= znwpw&YK)qiGCcB{eEcGsnwqlG5|V-n+?txa>Y50^!O6v{$;Pay$*!p>#w4Vu$s(Ys zDJ2Y5pu)fa3s+b;LXS;gfWu=@Z1g{%MnU%UGMe+lOQ|F^CF|Nr3X|Nl>~{{R2R z>i_>At^WW2{p$b!f3N=kpJ&bg|8i^o|JPab|G(3k|Ns5g{Qqz9@8AE(HUIx7t@-~y zcg_F*6>I+gZ(Z~M|HL)_|Ic0X|G&e(fB)C7`Tu|Kn*aY#uKEA}#+v{CU$6Q9|L2GQ z|G%yI|DSp7|Nnez|Noa*`~Sb}+W-Gy;Q)&_SUkYO0Y<~Z4JHo@FIf1)^rMFhEIeT0 z1mnZ>!_0%34~vhX8t)c={{0tV{Qp0E<^TT~EC2s5U-|!k+sgm{XRQ4Hf91;m|F^FE z|6hUe|Nng}|NlR^^8f$KEC2t0u=4-^S1bSj|Gx77f96&H|BI~p|6gs@|Nkbd{{Qz{ z_5Xj^s{j8T{`~u&xa$A^tX2R2m#+H%zhTw?|GlgJ|DU<)|No_{{{Q#*^Y8!mRsa7V zUG@L}#Z~|RKUnqu|L0Zz|1-Y-|Nq~r|NpsH|Nk$x`u~50)&Kvit{#ndSo(vdZ%XsK z!>@n;CI0{WAHV$n|AOWJ|JN`7|G#hf|Nje?|Np;n`TzfWm;e8-@&DidW6S^lzqtJW z|J%#||9`Rk|Nl?R|NsBL{QrOM761RstoZ+5Z^i%rHY@)B4_fj6f82`y|2=;F`=7bu z|No*D|Nqyn`2W9S#sB|PSN#9KaK-=sYghdLAMoqn|NSfe|3ACp|NomS{{Mfm;{X5O zpgQC2|Nm?&|Nj?W`TxJ%%K!g0R{sC5yK*$%sU7bgKmPq!`1|jF`qKaZE0+HM-@f$! z|7lDA|6j56|Nos!|NlR_^#6Z@zyJQ9Tl)Y1&87eUKVJI(|A(dj|NmP0|3CY(|Nn)T z{r|7B?EinWW&i)XEc^dIYT5t)Y0Liq5BTx#f8nzK|Ere$|KGao|NjZg{{Nr5?En9j z%l`l0y6pe|h#&v{A7A$W|J7yx|36&z|Nn<&|Nk>D|Nmd`_5c5T%m4qEUjF~T+VcPZ z4VM4^Z@PRm-l-k$0pI@p*ZBSKfBxeC{~H$n|KGp(|NnW5|Nq~x`2YWdi~s*WyZHZq zi{JnLUt9eD|HH-q|G!@R|NoE0|Nk>B`Tw7P$^ZY-OaA}YS@QqC-ID+ReU|+HpS0xv z|GXvt|3`fL_rG$<|Nl)({{Qb?^8f#gCI9~~UGo3`#wGv%?_Ki$f5Nwa|IaV^|Nrii z|Nmbu`TzgNlK=m?m;V1R`SSmNv8Dh2D=+>3UvKIE{}xOC|F>NVY9~S(#ISlA!b9v& zg7qU{?EqLi2GqxZsYTj&M6xlk{tJ>>U?!~p1M4@z?1$M4Jx&m;7Ri1XA4U^22Ie1F z{KMKOFdAlm)wBQq>!1Dq-~H_W|7p+u|Bw0l58SVYSY+ZLq3aUzTagoTOC5v2fmt&_ z;N|`_FyK013{#_Ne#-;WU`38u1!E>lR5eGKSeZTye_jQQ4!+Hm>b!iL-Ao^kSOsM)7`$eob z3R`d73{lS@;~*yVO5Ap%HAFt4141*{LunNVeIN%yGgvr4{B;uQ4*>^;r#I5>Ic3{R ztIt&2`{X^u+>XNzg|=MR7Z;qhcZ*?=TI&4*A`W#Fh=PU}#GP>efMme90ZcM5Fzn?k z>DDfqXdilZUYv;9M*GYTM&rZ#4%tH@_JaM68Z-HW+pmL;GzINKwqJ5Er9fWg36%fB z{@Gl6WB-8H_F-x|CAPQU+IPh-S)5k_btl6II3FetQx8d(FVM_;0JjfjKg>Na_rcuj z5!=;zuH4x2<65O{=PqrrhtUiSpg06!m=8hp2WWVLXb-0itPBhw`#ln0IL%{a0QZI8 zOf+|{V`X3f9kur6VvTbUD+A~l6^1u~i=1Ux85q177#QTA9(TUO!oZNtz`)?q_}+O2 z3j;$h0|NusaUPcd76yim3=9k+cFHbnEDQ|S7#J9K#96!SVP;_X&A`AgD>KxkgqeXs zo{@pUV_~6-9y0@j6UY%79WHN}7#Na4XURUE;j)g2fuV_!f#I<3YL_A=28IQU3=A>m z`(3P<7#Kj?s;f<|x^OTtFkEG1U`UI5>jFC0^(`XWvRSD!4CI*JId$F!;j0_B6ObiS`7fM~< zF)%PBfz&8grlLktWI6-*2aT+f%eg3kSI1nE7r!*w15149=R1A}eD z8P^F63=9)N=7cb2PGwt8M8GR zl{YZ5PG)8Jz{Gloh2bfSKp!jP99DtpY>d}fS?91Z-ez?}RxisaF@cG7BQwKRCe}5~ z3|E+0zp*ghWJWUd|7=l4J_FWNhEjorjEt>}iU{o>KXIRDV*Sa)aD@ppzVm9HFkwA6g^A&;2`DixGE)MH9Weu)nA>e5wR9#E z!&Y0?jZ>HycG-f$^OqezNUYNyq~L;!)ZCd&48L4ir%YjD`0sk0k%8fuJ3mP5syoP8 zlYFJ_PiJD-)y#s3^VelFflN!DU>=por&RC;WH4Ygm?c8CWf6QJxmM?N6Un_%wl5bs^p(H zjfvrHmDJjqObq*Kgbz(+Vz^lI4Rp$Jz5MSPObpNJ6_3wgVtCiY$U18>6GKxoqZEig zr4A15>3OCJSV)#9uQFuGZ z_X`+>LF$?pGYU_g%*3!|38V0?8B7dEmNN3so595JZ5gA)>$yw}9V-}lK;biG1!Er* z1H+-Rs$XX^F>LwDc$blZVd8hj z70e+1{ojl{AO*+&F!F$WeD^OSD~P}JAB6w$AH<|h4NSt{7cnuMYGC4Dwup)0UIP>R z@<~h#{~MT;Cr@HxSl-CQ|8*e~!|p~V{v!*S7|u5`@lRgJ#PGP0iT}|8CWh~gO#B-c zFfnvDG4VGnU}Bix#KeDoJ`=;{CMN!Q^O+d#G%+dvpUA|}+swoc3W){HOoE_j-qp+` z0gB+;%}f%Y2%6f$#QuFE6T`|DCLWOdp%x|{ko@l!CTWoT>{ceB&vTg=cC|9`fRe)H zRwf=$8u;AG!~=@`o;D^PP`s~hV^TXcmx11LB@n>~1@psQ=Vp!M7#Q$p+6T|sVCN+?IeswbOfb>u4 zViLYHhlycL7ZVpKs|){p<&?W!OrWagZ#PrGuW3vSXM32$K#BW(4-*e4 zc{TJh@qiq4x|d0K$7Cjk$GuEqpk&tE$HWgxRx|sU_(7ID?_<&i>A%#^#PeYi6T^#s zCjRS_m>AYfVB!Y_;(-ZF;K+SAfl2N5L?(vLiA<~@b+aZisU4cg#Bg*X6VJ7YOpNy? zGVy$x$i&z*iAiC~5+=q~lb9qfO=4m^G>NI3nStT#R3@JHQ<)fVPG#bGHI<3+`&1^L znbVjUo2N1HOqs^S_;(tU;^OH{jPs^5fzI&wH-m|1#!M#0t23E+*3V*Md^nS-im9p3lVi3d-+az{Im=0Tbiw1rYfI3z&HBE?{Cj1Lc2Qz{JzHkcsgxl)q>p z6VLvIOpNOmLeyVg$i(wuArs?$D8FS96VJj$OpFs2LFBhCV&b{9h>7talz(;+(^Dn} zhD}SEc#bV)V%)csi5F3M{GXl57{+jvnStRbbIfZl_Ju4b85tN}vGVNZV4uRq`kIUV zJ)6K)F4n8;0>`;nCvhmf=3?K%Vaveqk%RRNC+l)fh1Xo{$2qeY7&dYVzUE>-#U=iX zi}4|sIm8OaT2LDXRxkc%6gxq z*2ocjfdxeG{Ytyo)#H~ z>AXBgr5RT9^30N9n8e3(N}A!Oq{u~ChX0Zxvt$|ONr`-tVb~%i^Hhf6k`&KMX@&<< zJPV~6-bwMTlxFxR#k)|Np)kg<%tm5h!f%v=e5FG71>5 z-eqL?$|$gwiLsRlbf6$8lnLnj&nUyt&d$Kl&Mwl!!LW*5kJ_Nea^(d@SI6t9W%psrf_h3jb|03#0n;cLyV1#AUhT?G0tLQ_{GY= z@QYP>y+6ZqzIBX~f*4K;vNi`YycGN@ye5$0xW-z>3xNy=w0NclFtli!UH50$rTvAG zfnl94Ym-02IbGpP{tRt;T&w&UCg?3^Vqn;Auz~S)Aj1S>))N5?n~jB61Ty?E)?5|H zu)vh(s6WGGbK%8-42vv;=LRr5wd7wH$neGTBIwk27vV4d469w8KKn5=xCvhLW4PtU zv(%sAncHPX28Nv;!ixeJ&Ui#JFf8?yT@=7@*Gu?l5W@^_{+U4xYrF-f1Th@+PM#OU zu+m55Wgx>LAK5d04EKFx85sWfNW2PQSnVrz)}P^)ukf(|hTpzQM*K|BY77}o}!LNrqU|7Ya6%P9SViQxxhHz?fxGjiW$l)eSx z*D^rD6-}P!AtO&WGs9cPZ4mV`3_lnd7=AGFPh)0S%%sr5%_A)Xstd`)}sK~Hgq7&3ElHysf$Z$q#2{Qx32U-4uiVXi{`xzM+`sAgr zC^D>+2i;lm|39M=!&GJlhN;Z_k60P@GAmqQW%$o5zlW8fhgEC|E5jnz5RgOv|7YY= zWM9b05X3*7ks&b>Dki`Hb?a-8Ta&-%o!d_hx|5YXjPE7WXiBe zS-RPr;kUBS0&|8QHIVpH9r+2S4D)q688@3ToY3RwF=KeCC$QO!;k5p9#)oDM4~*E? znlh{~=J;*Su+>=jl{v#%W3`>;3_pwoUYRp=m^d&noG=kvZN~7xL~OAc!w(a&d1efK zrn^96Pyc6sU}SvBxQ$6{wIRbxCXVTb3=f%KGyO4OIKwuRm4V?Yx9~#)hJymB*9{n! z3BCsfnrQtU1BNMLCzu!*?nnsVF<^KvF^!#pp;bZPx&gyfg)Phs3@4QpuNp9PsmNV0 zV0fh>^v{T4o3{8rLxvkV?9E0DCv;hN8#3I}G+|BM*! z>G5AQV)&#dao&ibUti+AA;TJdi7kcwpWA>#|f2nL1&MjWS%7@LiGrW-T%8>5u-511KmGaqH* zxs%GUj#=?X3d2`c5WAIw=Ta)eM2<6z3=DTUg|DYFeBj*3$iT3WoBwq>!v^lPTnr3% zMR{hVGyE3iX-a3fFUIpRjp36R|ARD!d*U1m(ionJPZnfg_^H9OGnHY3ChM6jhOL^Q zD;U0-u>MM6_+_%5iGg9IInS>YhOOow*ccd=+NDlTW$3bRV`gBO;KVaIm0^z4dJYDL z(_SF;7ra_o7#Obl$=pg|Xz`c5k;1UWU-or6LvJAe%1nlnfdUO#3_pYTH>NV24dI`W z!O$Bjyfl-cAxvmSCd2+Pf#sPD=OPk2G8n!^zGVEH%FrJZeI$$FKqVG3-l`n32lxH-)D!m2q||&ze-mJ*hlrQyHJ9^883;e4Hk`Ad|5*oo8}7 zM14Ba&M^_Hxq#T|FIgBfEc(&y*?#to1p2K)Q zho?W6@plf-lw8L4Tp>iAB+AGi!P?Ep5St9@({r3+gjcZt|1)~rV-!Bm!_dMaaE6;< zBa7@VZiaI#j~E#kHn7R8=3)57CjX3wVJ3(0E*^$G9KsWL7;bRNpXXudJ2x;4sOuBJji7jQn^sX0Fpe%xQ20-FvEOit|vkaXPM<@2{Wu>;e8^+u${$#f#Ep| z{|+IB2dsygW(hN#<9x+5N0{Lr4{xh5!z$i+jB|t;PViYUFf{P^vccgMvm344(z1n}r!B2=P1-VwfW|4_u()9ZMk4M!*wRG7R6C85q7X3vFj(=wNxw z$iVQLRdg#GLjzk7xD33;%+Sfmz|hI~h4FzthFz!8IH5DzqVv}zz$-6 z=U{(k$#8~Ke5EzRcTR`DmJHjud>I&8xcS#wG5qBgUvAB?f`?;`CBs{ummqWA@bSF1 zWa#AQziYwpji2Y4CBtL^{@WG|ZGwN8UsyA&5@nxZ#c)+jsKc7!xmXDU!)kwlv|{L$U(Ce7 zut8DyyA{J>ML!0HiAww*EE$d~i7&Ng_^-s?WXUjHnP-L-!xm-!pB4;TRK#CcGrUn1 zKWokKQVnF{EOqu-mJGYq#s69|^l6B_ux6O3$urB6VVx%bb_<3HS|Ig1w0N2<8Ln#a zFS1~Gq6Hd<_@Lvs%97!zF8>(|hKKqBE!GUP3G1Uio%2au!HNyfk<>l547tNHHS~L7K zbDv%m2}5=#&+nqtCEL zR(PWx!+zNoHU@^n$^ttK7#69>|I%Z4s4DzekD*&lc#0mwJhhvQ3=H4Zoh0@ZUCIc&GCXG&cciN8Ms%J;=;(pYa>0T{L?RC*xF3@SPb;rTP2LF*0nC zUd+kBa9u_C$q`0|O0b~!w*ZzpNAP4CR#n@VPN>=CUEp5Bf}d{;pZnA8D4vdO*qQP@XA{O#Czt; zv)}|HL!-a^lv9ih9l`SZ&oDCV3l@8Pl#!t=Oa!E4R@fgV28L;oVkeI>GOUT*z`?+9 zCsFF-Nk)bxNwRNFGBO-Y0)@@m6se~3j10?CWtuK9GF(a(zIu+4VN$yA*>j8x*VE-+ zpJ!zFmOhn{fuTE-=hrz#hUuB0QNA}>iZ3rPGAzoLdwPwL;XpR1M81=wJN+CZ!>nA% z;};kiHs_uP4Ph6EeLlm;aIS#=&KX9Ay9L7cPBSumC;*LYe=3yUc$$&nO%YGeX-0tF<1f3pMJ@wnB= zD7F0)ct`MqZu8Foxy)cSOs zk>S)NMxGfb7#X%rX5@Q)jFI8jG)DRMvy2Q~GZ=+09%lrN-$^by&d4x#CZp=C9AjiyGM^DNGjM4kqwwJqj0{f~GJ*zj?=OaMKP_fF z!pOj|Z7JhACXjURYDVGSV~h+d*D#82Jjuv#e+{GT&!dbC$Ja9Q^c`hnxV)BeDl-Ga ziw%rCAnzaF%qTqdC?iAr7Dj$h6u#KPXmsH$Bg5`(jKZK;IkycobpCl8qr}c@j11k| z8ATWv=5J?|=>sK!?TixNuQD<`+0Mwn=PDz^kL`>SpeUKMgOUH!6-I^)I~YZ_Utwgp zu!E7m^)e&F{T+o{?eWK1Okn?1_Dh<_rw~_c2<5co+9GN;REjWcaY3Q5eLZc7TxwWXJLYjG`d^ z!2^suAhRzWU=#zz^V6d z|0zbs11A|*@G>yGxW>pc@j4^pmurkluoe%vF6KGNDmj&d;TG#P(45HZ6O4?97#XHB zGB8YMwgcI(Fuyf28VK~6f(Imoff_)dLT;=57CBm?uQ@BBx;S%R;CI*JL+_Jxg71%%fKGkg*#V_Z3@YcMdp2Qz;Qt1~bx5{a83%Ge+(3-{oE(0IjtRtARqtYS|&8NRZfW@cbG zz^VL{li@L^0@(jT_nA4nSsC6kb9b^b{9s-VX%~UU9`3R-Fx+M3{ld=hjZL7RgQ1Om z6-e^`f5rrc7fcKcFPQ9ai85?w6F4TyFpZt3OO)XXyYMYhhKXGK-$WP=aVb9(VR*{* zj|nvOqP01Zt!0wE#>{XEJa7w| z2V?jSst*|@{(~mBq8S)oF$p&^GyDZzufyhIfp-mqi$UGfrk^VCZAz z-zUZ}mlae_&u8ObCdROiEt`R1COdzh7{dzoLre?|uQ)l6i!wBF@oW)gxX;D2Sd`%t z7tbV7hKt-hw?!E4a&PBgV3;Dpb6JF8k%-Iz5r(OvvfsrRw~KNf5@kFfx(6{r0UFC8 zc3f!=6XR4ShE-Y&46C$wUdA$P(>f}^!0_9O=U)s%yY(4%28PQ{JXc~E?mK~8{?M6o zcMQWT=M^jr43j*BcPBBN^WfZ>#4y2E_)sjvVqehRm2doo7bP+L_t#-ySREj|B9`G@ zpz!u2hS?!JR}vYPhdg6sVCW6wxs=2(FN}S148yK4@ilP_ufoKy#4$_@7vB=ca41~- zP8`F#aPcE?46`D{m&7p~ix58($M7jae0m(iqDb*QaSZ1o#plN{JdHGCV3-{x-W11h zJW9Mjj^T5Zcvl=lS9BBu!;@(7Q?U%cqoo-bro~8Xi(%Xs!*f1{@p=r;<wXF+5*l z7#m}G7RNI7#`4UIWqcFM^EZ}pQM~Q>M8^B^ADQ74-ZZb+=V`KQj#QTbk z;UP1BCp*Ij=Ie|M498g=RXfkG;ner5N#HCq!#}2EP<#n8 z@<>V;GnjF@Guk5tRQ~^GG+=OJU~uDk#l#Tg$-pq1k^Kl0!y-^yVH+b)3p2wJMxG-~ z3@;g37ceorV+4oGf5x~NvBkU$vlvB>@G`7t^kZQ7%P7#u%dmi1;3W@3E33c)9)=lg z0`qtnR@|FVJnkC_>^Gx9%VW;n(O>Y|=!;=jtwaF1yQsN@H$5ew(6|=xUR)(j{%1_uBx>%Kuu`#S?m3+d+aEg_G9UH@KR>dc541ZWxK0G6@`JVYtd9_Kt<&BePT=D?={}e*-JSJQn3XR)&Kt;Mo5E zpE1@~^e;Clg*@fz7?f&TxsT zo`K;av%nR0hAvitOY95>S;ZPT7=ExxE#+XC!p=XPgJCJV_EHXp3+&*qn;pv-;U~J1 zlVKI3z!?sPU5p^(_c96W;9ywBEU=A(;V6snN=}B)ECO3N7?!hguHl-wKT(3=t;lQ^u)FN-B-gVug!_Th z&__0gKTHBI*%%sGgx9k(Y+w<%&BpMLMXZ^fVJ4fwqjleifUaUF)HzfYh! zN(P2&jI!^|8P+h1Ewx}c#GK8*u$D#Yp&7$*mZSU(3_T)3zs(ueh)8vrGkg#Ov7bl^ ztT$))BP;ydoMD~<|3h9;wPbWPY17+&7Wx zGh=8n1(|uzRDyxwo2gijIYW;bsK#UXKYIcbV+Yd)#)>a44BgDar(79svhX~0VtC5V zvD<~=7>Cp<7ltDu!aJQAdPIfWoEa8~H!^Q@VK^@({MDJEQC4`KE5l}4wH8-~S#t7! zof-NSgzq{t+))(%>BR6}v6q>F;g1T>K{tjEYN7vK7>=r66k=eQW+lAMh2fEv@I)7e zbJif)$+rB@-5HkK^89jR*kQ}_(2e1wt=cg+h8{b$#cmAS?fARh7*5+sbhti@oeYH-;DXJgsgFKkY&0G&u6Vb7h#~DDl{pVWXq;BNv8qjy(IE86G$a ztZ`xZc;rltqh*S!D&c<;j}md!)bBW zhvEzu#I;EjdoRYo@Lr6yRh;3A80h{qP#XiAdH2K^819L&ei37MEC#Y;wj-m}HpZmi z91M$CL}qa^Y-5RLV3^3-&Nz#c;T)Ud98QLQ_J{^fhUcK7^dX1D98QMyoIHm)8FzES zGaaamot?@Uf0dDAAs@p7M!vIr3~QNL-}5p|VBwg;$8e98^)fHRW7e~*3=A8%SvT`C z+~a1Q!ppFqhkrId!v!AxCVqy8Jp4cT7{2oGzu{wOHE?6BB&R z#Ic%{;TDVQDOQI0tdJ$I;I`WZE(Xxp;|y+wM_j@!+zem2KwTnG`$ddl0~Z6s1}=dg zTnvY}gr9IRT!qUCE@R|)#mI1zk-wFR;RPdnZZdcoqwsohhKJIF0eB! zWe2T?1joZGb_Rx5?CR?|7$$O9g2P;c0W_Sri%FoDo#8u^%r7>EE3CW=*%=nIi9KRt z*v-a$nvLNYTQ!PW@#*Xg|CnT2*cqO%@~&oQ*uWX4_{$XQaSRx{F3shB!9T8=? zB*Hyal;Nics8c*y6nZBPxDO8+Ihf7F-Oa+Vj0rU9e21B*mxbX4vn{Bdd4rqrGB?9@ zb_Ry)>{}TRv@2*=Zqk3r^t3t_g|c&t(%GAsW=Bn?5#Lx z+~crRDTuQ}`k*EQ!%r9a?rtWAgC64VdzctDdrDsHU}Bi-C4Q!ZiD8S^R2Bw?lYZP6 z+n5+W`^isfU}Bi$U&_GH9>9O2g^6KFkno#+CWc4Ba_3r@7#c#3^DrCK+ zDE^|AiQ!bD@c(8ehD%Ap@0*zzZYB$yZ)IZWN)f)%%)~G=RrqZ)6T{9l{!^_?3@_7! zuQf9|~{#1#tX<=eGQZ3xr#Kdr`MtD*a6T`z=f#oes z43p}FKQuBitf&`$-N?jnqJfcrQ415pj|N8J=Z#DZ6Pp-CK_)zIVibPa$i#4@nNbnM zztqAgGPeb^M2%5oMhg?e>NZB+$t_F_JK7jKxEL4?Okm{s+{(o8ZUQ6w?^Y&;#>tE< z?^~G|S|>B!U}IpoJCjj*aXS;kkC}|1s%+_OM%m4+ObmDCFbc13Wnx$}pHcWvI}^jd z`Hb@2?Mw`F7cjozWMJrA#V9TH#0G8SjQ;7 ztd)u3<2puxUrkI59UB-0J~S~g%-F#AnTdg+do!cJ`!*(qmMx3|4ed+}Q?@YXFfcT1 zWfZvF$i(n%E2F^cCMJgK+ZhG+H8L@D?qn4H+Qh`LeJ7*9`bH*(MY|XU_B1jvOxew- z46^*^ZbqrEEldoP_b~2fWdM!ViS;)yF)Ta8$n*F=Bg3XcjG%?rT}K$jZ~kXwxO;?A zZe{}$!^WeGa`*l-GMqok*vrVkaOW7KYC{7PL*sGA`Fsov3$8MXooi%bcyN_b{8$SU z!^3NgQmfjS80K7O6hG3$#PHxcqwwrTCWiMn7=^zzFfshQ$tbWI6z8`Xh1(jL7*5@0 z6#mk{#PI13qrkEzCWbY48HGPJFfp9E$0!Om=RTwG(*`DnpZ6IRLHr318O0YiF)>Vd z#K<$JiHYIuBSwMgO-u}bA2EV1(>nizk^evg6T{+XjKUilm>6C^V^jn=>&gp8`3?UW z8IHYVTb`MgdTe+<48X_IMf-!@t*z(hLkU-!Q5*PGe#?@`h3E9cTga8%DJ^ zQ<)fMy=CNYpUT9r?k%I*^C?UWH{UX=%pwZ)T|7*@S!RBM>T#BlXJquPgwObiVl7}efQWMWwTfsy~^L?(v)9~jm4O=Mzt z^?^}s=0qljsUI2DeokOwIPsB@XT>xohAST#Wf>Sgeq>ZTJb{T}&L>8-zkN&$$3HQu zJ?~>;`1FZU?P(tq!`#n|YM`8a;xi-v`93CwJD(ZVmh>?(w0~h#YwBZS*z|=_?L{vW z!`&~8ya#)j7+!q=ncDu9QSDbZ6T_yjjQm?cWyn`XO$LTnUm4ZDcQP@|`NqhuF7*_mc z6xj$WRsJ#xg4}uQFQeF!W+sNOe;GwKG%+#s{bLjYi7)uasL#M~{vV_9wiYIarvHpH zxfmE`cQWx@>tbSD-pQ27z%Zc;!kg2@)CO<1fXn}{ObiTPnRvQn7#f+MOE56JkQVtT z&G1JWv<48h-xXo0^gw`N7pv4`A%;C{vKIvyRC3qpp0l+xuH$&7H}Vkp^Sl{jK7_cp^K4wE+gYq)IJb$-;;%%RS@I`4tZ7=1{F{nYc?+<+e1d? zH{3ASgX+ySjEqYd8KA>JXGIv=S$N(EGt6M&nIp>3z{=4e%Fw}T!@zKawY));VK-=a zcNsftgDAslcF;&x8>d)_u72`k#@*m^FgI z0_=B5M&U?-Y=%q&28Nl8tos-l=P}x%v{MBcZnH2j+-Bi<$jb1X1vG~UZYx_d8pkVd zV`7-Vs63U4;UJ@O6BEM|(0+?nCe8^=j5C-xH!?9UWa3=I#JH1*^Ar=~Av8A#G5q9b zVED;@hG~Wv!%?Bvpeb@tyeKy@N}pi^ZSk1R#;~7xDbsc~h9|5V3=BPNyqnkjuv``;B z90l?}KSLb@LmlgLCWdClcF_K<|FaVrqZ^pB7RWMuVB=pT%W$59uUnR3E~m^(X@-qF zJ6PUHGdvPL!2C&uVZXT60$GN4;@sDz8U9OfT$Ev$CoR52mSMXz?|d1CW77OfWEmF8 z@SKrhTrJCUQ%rRE@qv_tW&cYR&hRKU75u&fqyG#&9e~y`wWIF zLZJ20J4JaGWHDTo62Fwm@J0%>hiIuRh`mP^R2%lm^Xy1xm?a;?!0<+%^=mrA7e&t3 zX$@$AO_&SZ@nT_z%G=?MgQZLgOIvs^SrZJp#W<8q0@W6$2T{govSDt?v z3{7sJiK&O)7g)Y!GRzL+URu%hRUHI8RqI>W)Za0Z4M@jP$R89v0buE}J$p1^x4ouMO%^?nA!vLue{84T-^ zOc)p*C5iRrGBl)!UCd!vkjC*KjbUvX@4O6#J!!n((-}^uaopp7*(oJ9G8EXf$o6IKvfg{zh?zOS~ZA z^L#O1L>X4|gBB707GPT|%J5i_^}Hy<4?&Ot$AmzZoEGw9V3;Y)dPS6Bp72LT28Mo7 z))k@*Gevp7iZd(|?Ol|91; zeZ>Rz40{a(*4Z;GHR5fsXE<)g+hosh#q1$F1H)E3xpVdmH|%&{+A%z}YXId~aKBK1 z0aT2?VB}lD#PE&L6D+38u$7mAVJk1sav_Ghygajn7&`fR_6adO=I2={#PFUU)H?)~ z0Rjv+#2FZFh_k*EXSfRMsBW5jns&KIT(9m185O&P9;@Vqr; z=oI5^FlX2#A^6aY;jqLz5e9~hy5h^t7!KGcquoX5{N;Vz|lZ3Kjb< z%E0hll(kok;g6^eRP3xM1H)NS)@Py&mqd+0X$3TYHJOotVKO7@V$e)2=XOTM^++{5 zSReNeHl9mt3`bz&oE&_th77`xwgvYJcAi7*3|rX2W1zD|8TlPpOBh-i`BySBPGOWn zN~554eO|1$L2Li{XEHIiFd-yCY0rbZlp)v(zLu#A;~VHqp`VOEB%tbz;-$5?r8u`*slj3$G_hJ%mQ zkwG{P%$8;Vbtl&{$~770lOf2C*`TnV#mK-gi&0<=Bg1+|&I62$ zJJ7?LgO62*LD&Qm4g$)o)(jf53=FOup^Uz8Z$Z*AdYsShU}S7!?BEjZ@?%)QEj-PS z;R?TSs~^L2eo%G2L4Aq8Lrz2toLDPbvna%$%kR7i_#81hEHz7^L!Yt`SCpQV|)Z!=iltlG1;H7 z+h2`=VWU6mDSyUo{*v$r2ao??xDnr6swks?7wbbthE^v24NQzvm=HTm!R=6C4pRns zR%Zqk2?hom@o2_ir0@WxEqq~hNt}VEF^0q7vCsdE9HJZ!46-&*c~Il?HY0Z@6T>q`Sibu!$-wYel69ICLz5IJ zKmBLqe#^=Fg_GeECwQ;!e@55(%J7^Sv^g50hQpa7lffG_#`K?&!<;pS!3NZx1dZ8(_Tw&MWIf2pu$<8w>aMe( zP1H=RZ}BD(%))Sug|m&7@ji|*4<1qxMjkkdf% zpxnaDI)jU$lX(dX1H)Q&sSYlN69dB(Ce}?%3^SP^V%+Z;h5DHo8bKvI!~fZ;j0z0) z3=HzNp~F|tl#VrXCzSkJ_;lZoRI6XQW9HFzw5)(8x+n<5J`(1Nl_|bpJd<2cy6& zMur)nZGfQiszPKs6GIyl|8gdVsZ3g6|8eDsY-40-0?p)s`2q}}IPGO*UBk#Q3Dlqa z|DTb|f^#t=LmDfnV-GSLoiF%`hqFV1;jFk&n*_ssaTiGZ%~oYpWS9+_`(tMP&CGC{ zS!f;$!&_!c&~7@8Z7huI5KacwkMMrwe@3BatjieZb1?LA@GsFznB!c{hg1#HHDKm+ZzAnhRk@$6;F+`_`Jj2S$~{h!frFY{*R-Ruk> z*g9Dm7=CiMG4+WuH1G>d5M|gWB+w_ya83xcf@i5n`UFvi9#OD!LFR}tEN5n5Sk5f; zoS9)av(Qt}0vhm!%>T2OFfq<&I>{t9vxZ>>GyjwthF{FW*Q*(NS%i9O80NDaV`gA@ z$}Y9Pn&BId!1`*2iSoh=su`Npg{M_BeAeY(Rn72CkAG=3!w&tR+0_i2?cOn-u4d@S z)aDt$OU2jKFw89D|5wAXwv7K~4a0*n){V6c4dnv! zY8WPzCo(X6EEm{V%g|O4{=Sx>rBZHP4a24?rFV4AUtB&z- zoj)wofYujL{j7)oj2yPCNeoVqdPjz#g`I(+h28QS55r#ejVufd@3>>zco|Offcgrv zZ5b`V^^MyjE`~XbprskJna!SaG0frQn9I$uol~uyn_(H3@MSKB-CSaixEQ*)d1i1k zPUA+Z-~Tgmn6XAPScB}F4Ow$HhnZmmBkN{nhULtx>p+FOzzP<|tIQE_fB$DhU0(=l z1K`|S3d(EO7(w&tJZqU49)SiUH!$&BWMbUI#Bqj+@dy*rew;gujMo?$esVA{{N&)@ zp~A3=lXapB!#`fD9u{4NvFQqn3 zjp3b?+7nfVUD9gjR2iO1Yiw6#I4h&}OO@e-tnemPhGsdnnW_wz<Kr3Re8RuGPbL6OjKi>sm60sjd7`(CM@Vc zc@Gi4pm38Ch-J_+V_-<&C}7M$RK%cqSWtndh(X7bfuWS6m65R)E~%fATBe^@nv(-P zd`X6ZfdRB`0(AZdXbpo60|NtS;OSxnD~QV|$j!p2$iT$N$iT>$$jA&i3FZM0!vS^% zhBq7x3?Oq9{&O;X5M^Lc;b35BP=TDl1d;<6JWv9p{(&VA!w1lTP*yw)23!mb3RXM} z0sIUME>=7Y4MGeIC00BP6T}%9W?1nsI7l!sY_Z~DD3E7hIAX=akg%D7;e!JY1H(QB zh8Ra427&zy3?+^{3PMC3RfP6gh)mP7B?P- zgcwE!9XB2Zg;+)g2R9xDgE&Tp5H}u%hAKt|0e2n-g(-{-93DIj0%sT*K6vslJUGM1 zz~jZkP;i-%A;OD?q2UT6Lyi{@gTp;Wh9_P;3?D#g&6|f|!b3&|8E+njghz}F7T!Dz z2c9xAM0oQsTwrEmF!AMKXpm)MF!1AHI3Ul&;NZu@0Gil*62!xxFoB8TOArr3z-%Ul zBf&fj4NI69o&@tSC@g1U_!G>-uwXS4gG2}qgTrOTU8AKv@7z9k284MzL7(O^LGdO_gU}lCj5j+eHA; zgflaIh~Qxeh+$^XiR58eP|VEW5Xr;vp_rK=B9e#UKq)gr4~SpG%ciQ-|H(9X;-A&Q5gpo^K|1W0@WGlNbv55tEk%nSw5JPa46GBdP9 z^DqR=VrDoK&BIVIpP9iRhKJ$8d}aol7#@ZM(6K!rdIK|qL@W=3!&YX7m{=YLh6l_H zIk7wp2Ocvs)Wq^I6g+2U=m5#TU}jhWlK;faup^d-A>cDJ!-ZHLhJ>HY3@mXx3(pFo-1ZFc|oz#KN#6k%!^I3Kj;1Bp!wZ+gKPXl6V*tcCj$bN#bERu%CtD1c-lx zh2c&T55tG!EDRjUJPZd;voM$>^DrpfV`1n3(N9?zRwVN(>WLzig_3sW^gdb6!S0` z%;aFu0r6*ZF!+GzIUEcnAn~~z3@ybx3<3){7}gZ?Ficp$!LS3wU&O(1qnL-`!6FWZ zKgB!@3zl#&aFp;cB&^_IP$}VIc(9&>!2-m;$-!`ohJtEN2A(n=h7Z-8 z3=$x^hLb_3jE5nio|D0$jECVuJtsp*84p843n#;ZG9CtpK2C-MAo+eyh6^D6WKM<$ zAbnFf8Cc4B7%oibWbi2GVMv(8$xu^lrK0LHh|<8b25A>=V5SI$;lv4 z!Nb6?hLb^~f`?(kCQgQc3Lb_BTR0giDtH(YwsSJfso-G{*ulxLrh7ID0g$@g zoD3I0`u1`%{Hfq!*szzAfv1v(!C^lqgF+<_gTNt9h7=Hegp;A7l7}JSC?`WtB@cta z2~LJNl{^d#Cpj55RPr!vILXOyppu7S!YNLM3za+!3D-FpUVy}Jax#ci@qqTCGbmK? zFf4e%$zV~%!yxdRlOdsshhf8CPKJsq9tKd|-T|W7xEK~x@h}u{a4~EE$xCuEya3VC zTnsGLJPZygTnq*^JPZZtTnq^{JPZ@ExENY$co-h!axqK*iPvy3Y^dR3Flgdp_)^2e zAkfUkz);J>Frk%;L8F$3p`eY6!J?LjVM99?Lr5(T!-sAzh6a%Md@hERIv$3G&0Gux zbvz6VTeui{K>VLv3>Ol47!3Y%F}wklODxkZa2?=c;CA3; zV0ggCz+%A9z;J<|fu})$fmc9?foXye1LFoE2G$3{3`_wc3=A8@8TcAjF)%Jz&A{)l zk%6sYBZGjzCI*JC%?ykeHZ!mmY++zLu!VuaVH<;Bz;*_q4Lccx8+J1=J=o2_DzKM< z(P1wGQ^Gz5k%qGjq8lDFh$%c{5Wnz@K|2^e`GQ9A;$LaF~%b;0U9k!cj(}4W}8651e5%X}HE{ zYH*#=tN^rrd%3GdWIJ%*66wF_Y7WbxceR z>zSMr_AoIW*u&%!u$PJH!d@oV4f~kf793z=DmcjGE^wF$)E4nzxWMEYaEXcG!A&Nw z4cyG$27JuS2K>w{4gAbZ5BQmVCI~b8I*2j*ZIESVG>~IvNRVgtU!cSs5TMK)7@*1= zWT3&!lAysHETF|4QlP`k`ap-7DL|Jwv_X%V@qr#Q^8$Tl)&v7)<^@K~tO_Q~%n!_& znJ!o`Gc;H-vre#LW-+j44lD3tW-#z!4nN?-98uuQ%(B3jIkLf@Ich=(b96%(GebZG zGgCqoGiyUOGs}Z)<`{z<=GX_h%!~|q%y9;#%<%^0%n1b*%!vtA%uE-mm{}ESn3FEl zGbc}IVrJaX#GIng$((A?%bX_A$DBT45_86c>CB89rZZ;>%wW!1u!K2#!3yS_1*@4E z8`d)CE?CdZ;BbREkKqn;{)YR^1q~0F3okroW(s)1%u?`_nK9rwGlRn`=AsF&nTr>E zWG-RgV=4V$$ik{%%)(G$$x>F}%2Hks$iiR{#8P1p$5QzqlZC+{o2AO3oTd6g6AME_ zGfPcC2TN_i1QwP96Id7pCb85xOlDzPFqx(Pz!a7ShG{H~2Gd!Z5@xV8PngNV@L?89 z%Z0ft3=8J5FgnaA1<>nFlKOtPB&nSeYMmu`+(>Vr6FNVP%-m z$IA3!GAkp)R8}T~>8vaUGg%ovEM#RgSj5U8u#A;4U>Pez!U|T#gq5tU1*=$@KdfP8 z2w2a``e8FGgTpOW#s{}qSwGxiWe#}A%J|?hE7ON3tc)L?va)`7&dTuNH7irV8&<}K zx2y~dA6Xe6d|_p3_{z$>;0G(?hM%kq8~(F0HZZU;G_bKT32?G88gQ~P2=KBoJ>X|! zH4tWFcp%Nj8X(8UP@v4l@<4@+!9k0S<$^XFqmD5fL$L|l#7Aaq4A0HkCM~dJV|i-H z#&pn%jgi@!Z8D!D8)Jh98}kJZwkZvsY*Qb2vQ1;~Vw+yz#l~RZ%f|A+myJ=ukB#X- zC>!&Ha5lz<2sV}pQEUtX32cl732dwfQrVai(%4ugq_Z(y$YWzzP{_tyP|U_6P{PL4 zP|C)TP|3!$p^A;cp_YwRpqY(%K`R^MfmSw#3%zX26Z+X0117LBeVD+;U@(o1>B0;) zh6&r)7!!7|%?Q}fHdEjf8{>piY_m3;W}9trhK;4+3>))@vutx1F0#!HxWvY|;4<61 z2lv<*H#}gQukeIzLBbO@#)2no3pa4FGkoA?;$h*%=r**;hHNW@lws z!_HE$hJE#hwd^bf>)4q;tY>FR*ucK#!)|t_ggxx68}_g>CG2HqEZEP^@Zc!>+7B1m z875q3UpL_n`+9}D>>Ct*vTt~@Zn(H5WvB}5W}(AAf1EdK?VoIhfEIUf@}`P3%MK&2_+m`3hFr+3>rALerVv> z7SO`6eM1WevqCG!jt?Ci%o{p67(VoH>|D^xvFkt|$L@fBjy(YrI9Lu$;MiL*kz?P1 z$sGF+OyM|CFqPwAzzmK<7iMrA4w%JpL}50^(Sq3=3=igT9CMhjyG986RYFGHfX3ykStndDEbj^VWqjPDX}uPSyqG zoVNukIPVnHa55gK;k+wQ&&gO&&&eRr%E{Q!#(8f+JLmm^PEN)Pot(@DU7U;!U7QaN zbaApabaOs5=;eI$pqG=?p`VlaLq8{@zy!|63np;ED?`W(R)&xZtPCL>Yz!d|Yz!e4Yz!eQ*cd_{ zurY*4urq`Nurq{ourq`lU}p&V!Ojq3z`+ob!NCx+fP*3A1_wik04GC;2PZ>F11Ces z4o-%U51b4k8e9w^30w>zGq@N+E^sk~aBwq(IB+wBRB$tdY~W@HdBM#PqQJut62QX{ z(!s+Ja)5^+Oegb466 zgm~~Xgf#FogzVsF2>HOz5TYT#5RxFk5Hdr6A>@JpLkNc;Lx_bSLr8%jL&yq2hL8t> z3?TwS3?U9e3?UUl3?UnY7(!kMF@z`xGlWD4GlWbKW(YYU%n-sL!VqF1!Vr=n!Vt1R zgdyaH2tx>mC_{*YC__kvC_~5wQHGEQq6{GtVhkYxVhkZ2VhkY%#27+;h%tm1h%ARQI;0sw4oEYE{E%h{F_2*h$&g_PSs=p@azln8L_n4y#6y-Lq(PP; zWQQz6$O~D9kN`P`kPbP9kOOiIAwT38LNw$VLL%fDLOSFbLJr6?g#3_a2r*D#2+2@j z2)Uua5F((+5aOW75K^JY5VAp$A>@G~Lx_YDLr8!ULr8}bL&yOohL9gh3?UlI3?T{1 z3?UPg8A47dGlVdxFoamBFoYDSFodj7VF-Dk!Vn^$$`InA$`I0^$`GCj;aIiSN3@A;iFxAtb|;A!LCmLkMVGmxm#ap@k8A zix>L@W(MW}76#S{Yz&MH>b-ZQWqc+bFg>^%b`!v_ZDln)Gy7d|jBmwjYlboj);tnitE zA>kK;lmG*x^a55!nFDN$j0boaWun7#SZ(G0I<%VPu>j%gEXw$H;I@o{@2b z0wc44A|sQ55+k#KG9zPy3M2CcQ-%-@GlmccGlq}~Glq~2W(*-O%osux%o#!g%o##D z%o#!sm@|a@FlPucuwV$uuwV#TV8IY_!-641z>*=v!;&GS!IB|lgC#@A3n_*W1!;zm z2x*281v!S01quuy75b2L1{&7~b#xi40(^oP7%K$WXUBRnFbD|nuyagcWMI%?U|>*T zU|`@V{QMsTKxejt)N_b0266xYcjOajV{+zY^K54AW9erGsRt!sjv|P96VU7cNIyGv z^`LXpTNoG^B#I&Gi$RGGr2hLNRP()>*?ZV}!FGV`2kF}bVpKr%DZ=zU#;*_LzAp?6 z3_q$M`l=zz&KNjuqv>;QX6a*QU;x>p!pOjIqaLDu8OTnM`_7}O$Kt*iMh1p}28ce; zf!!dj9GlVf`C|GDwA0+99is2_B#h+98OR|01srZOpch|o)UN>Z zvBlFLMh1ogRfu{4SUl~Xtp^^vkZ_;F#K7=D2cl0NdQlTcGMYZLZ~(azbUyhFYluD_F=+TLC#;W$nStR# zAVeR?%^;l|ztQwz@t+Gb0|Q40L?1kTTu0M~C4JO@jwFWaGr|@ASoDF`)^~)W**BlC zeQ!W{Bn+a@8g#fAC?1;8^kE546&41DjwpyeZ&)T7x0(x=19z@U%;(f3^wWD5fW$NS}|`3p<Oor$yh3VT)Sl<;^28J6`A^PC<^%K^|!^Xg{U^+zKVwipDX!@}D&xMVFVZ|(n zKF|@sppfCPN7IKEo}hTEVPjx$m;=!VJ_HGr9@WwGA;sGoHU@?Rb0O+MYpucdfG$o0 z#Q>}xLbC@HZ%^157-lSj=-Ug@1hVHhTD-YqmOnD=3=9p+A^N}AC_?JVP{})*bUM54VKT)?Zcw)2s;A<$3BR@T`>P0Mzar# zJO8jVFie2z1I^fjv~tWRtj~mlfgxi*#6Ayb1A?O(O&=C_=78q?Z$tFW0XHof7&t(e z-~IoOXh&etw*<7i`3pqfOf!%x85lS~m);@ki$|>wLGf^hgMndz&R3AC|K}mv7&!7* zf%)+I5KDRy;bdU2(1qx011-`6#a}R*J}mC^;bdT#U<}a*56^ToeOTh5g_D6H!vvyl z7xacL4rerdSnS)w$-wZz7NQSc?=++7!(!hTkbQO#eeimR2zTm$wz)e%^ufc^8_hl} z;hDn4z>whx(H8`&gFxY_Pgvg^E(V4PE)ad3a*zn*5J%I8C0wq6>`Q{^dm;hVw|g~e zykpVF!_C0(pcH?TU;@@8u<~OwntCkZafgqA;Xx@x-v(HHx|py&5q<`S zhzf|lG|-|qkiVMI^r86+6plXp3=9s95PkMAcjlw%!xD}y{0s~pEf9V1ay*@|zCHX5 z3dA^MiV(xpC{J}mK&BEZ0K zqYI)h2xcEQVSRH17#JS(Li8!Z?0b)vzOdMLMSy{!V>U#eIjrA!98DiuxPa0nk01j> zz#51?czN&}O&?Oav=L-r*Z@_(4ixyHbaEX{Jz9AHN+%_P3=9!#A@+b)t%9_25K->0 z0bQ289-5j zcWMTwaFBmogcukkPD9kg+wu8m>anEz8X*RT4QC+w)M5E78%-ZtJb~=nBE-P3;xa@Z z=uB*o6EVs;v~U3Fdn3faP;eEZ&kPn0&1m*vai@wf14G3%h(37volaO^j4%Vkhno<6 zp!-0;?({~}hsB*!gc%qz?n3k(faRBP!urk#GcX7|fapttwOi5kVF^zb5e5d2XApgy zuyzP~xS;6+r7If|1_p&!5Pf$+o&%+mel&L?wF`1Y7#J=<)mOmmDMnL|B|MghFfd5G zhuHHErjH2s-4S76VE6*j7Y%bCdOhlf7#H9XWneIXs_%fcBf8PthZYW?bmbz-z|g_) z9o#>cs>#GrEUDM1d`%Fuiw$^0qK(wV_*<4hv-`j^Ve=Pe__!VBF4bbVFl554rCd&bb)2O zuSbl5LBSfL4>YC*(#lbeW*?UPc|?qXVTB7spC>F|`=jZ@;?6%}3=9WcA^KQh`9&X1 zAC~ww0j)jvhUklj^?$Vq>&p>mU^tKt(f3jrU!3=9fyA^PC% zJdGAESnP9=VqlPXho)~iVSP1H3=9JA(e$C0>sajDBE`Uv@e!i$9<2O}N3#!0es}}g z3F+|B3SWr6ewaRG{Q5xUbc{3u!-XJ-K1o4?RtKLy<43axOS$<+nt@>gJ47Gk6d48v zj^BjsGm&9n_`w6w=LO3@M7T3YhJisr5TfrOtUftS*uEt)3=BI&Ao|Y1^zA3C?~V)u zLx2QCUp1_J*i2ZTh%5s`g%(7gDlC1SN7IKTo%qNyFgO@O^eyU!mhaoq^kIp=7Fh;{ z6MG=~RQaI#;qw+GbT=#gV!XzzmP+X>4r+=TTV zkz-)U?uF>%hSlTGw}9=(7k+<0V~VpO`W8Xm&+!^fA2j?x;h-bWz|a6y4-bdixYftV zGcas`s)tTPGH`51QxCNl?0Wwel+!1`jbcG85mA*{sQ~=BdmN%M$?BS zz5W6DM*yO41#BGA9!(!wdH}i8M1g^!LmHw_3Kmbh(ez2^xgzy{6&V;hlp*R3VC76WntC*QK;c-U$iR?b0MVBVE7#2N>jSxOiy{NV3}c8s z&^}DCztqw6fdm;K^XN|$85njz)qemTTmfp2bEB!pGJhhY#J~`c3bCgP77o+V^kHd7 zgeWmEyeNR^y9X=JiqZ6836CBn28Ids5Pd&k^E`_Q>pP;vz%XMQM4ut7-elee&JXbX zkLEv6IQ~&$U|4VnqAw2`UL2Fr^dXJ+=qNKVJbFMHQ1yRd>B1Y$9xUN^Mwx*j;TpuAR0W7d9NL8Sv8XUG%=ih>XAg_# zax{Hd+F>>-3=9E(A^IX=^+_aqCC2b8y5A^PNC z;fNmoSn^AYDg#5sbTobF_F)PCJ*o^017a+Z~&?vyr>YQz8_6JTDufv&lEKVh8O1`_5?zL zpMfJCO&?Nx9Z_Ro=r|8ie*~6aoYB;y*#jzn|EMuAC|rl=gOA@z6V_*<&cHAOst+E| z|Ix}RH2XmQ%28)vXt)8fuNUU8$7uSX?G2Fo=BP6;JbRY0#y(8zXk&X!##-l`!IJoqnVGE4?*@^(O_U$@c^Q)5auppG<`_+uxK(c z{D7*5-6zk%kER|=INNA4FgQGd*mD$S&-cyXat&VZK-&o*cjag@Fg$>&cZaEejiw&z zE|B^;nhXpDPax(;!PFnet{xO#XFzL`UO?2t)8%F~^;pUg7A*z_kM|IL=hYz=arC3< zLknk+eKuMQ3=<^&fc*=%?>U-2EcTUv?304%gU@#zM$?Cue?a!F(PCgY0M%y-D_@u6 z*9Qv6Ct3^)FVrCRB|yB#z|oDS4=LP5v>6yA)FJBO;a-hhJ;+@y+6)XkbRp`QVD(u# zntCkZSEJ3q5MTw-7Y8r5(Db3DJCJ=_v>6y8!Xf(dVCA>Xby&{!AGCB+l3TY7aGO+NzjHVt-eHNm_z;L1fqR*5UViCuBG<|691No~* zhk-$$4WdsR){p5XtnY{p1H+3c5Pj~j{$Da-eSbiKvIwFNbdngzr5x7y^?|gQ=rS;9 ztcB=p_-)>Op1<^_c5lbr~2AK=pls>AQ|LzKJD$T+wA< z=-2_V4|F0SNGk_{`EroIc=Q+;4(x*HgWRUZz`)UuW*^8fXurZnkAZ<CdeJ*>@764_@97F~9yqkAdL@R3ChOs5_c{XyFJ7 z4;g(1h6!gO_JJ?l0>zs=nm%YegVg)zGcX98hp30wZ|LK-Na0tb&%p2@7}Bo=odFDz z;`om?ZimHPTl5(iIxa%&>4t?L`uHx?U7&pRM4y4-#bt>4BOpsa{yEwT7Z#}G7s3DM^Z3y*pIba@!b99~S#~j2IX$e1zC1 z4bw-&II@co1H+C_5Pj(|eb3RxJ+Z`hjS&OGhtCjwaR1##(}yKIw-_-nWPE|>gZmGC zTo;S~-WV}39QX>+X9SDCel+{g^nucsiZKI2#1Du*csp-4nm%Yd51jsu85lHvLDa+h z)AeZT(ZT~{PmeJJgTZfzzKgJY8jYq8$(}vN3=9wcLe#_8V_KuB$70VHV+Mwbe-M2u zVD^Zk=|fr%qGH0p@B>}__jRc42B>Hmcf^E&A%YL04>7KYrVp+E0@C-#gn^+z5TefpR^HU3>BC~5i75la z4H<~OTv+=i9!(z>|K*r6FbL>F^ugCr`lIPXvk#OmmY6ayR2V|^!P{Z_X!?-C|B5LC z!ww^e`U$Y|mmf_%nmwTK;4x!hXt0Fn^Mi%|`?cWsfVU5^=yNe+V3=VI(YG2lj(i+V zAC~Z_F=Jp@-~-VIx=tLF6gklKVX<$E83RLxA4K0Idyp;$29DK)?R#U!z;Iw1L?3*= z*nG5bK`U=S;jd!Oz_4Q`L?68To{pvu+J69rV~9Be!-_c&^{o)+F>rLFsmGEYd(0Ua z4$Oz>n+I#h5|OTsm@_a)tb^#=4y(`H(d@$#{(sCF7#3`X=!2ZN&A`ABjHVBZJ54MY z7(BK>^y$LVi8`7-v~UE4OO6Et!-Q=Reem_O;%NG?*tf)jfx%)sL?3*;I(oXqV&5GL z1_p;65Pe~=dg(IZbRuHOz~He5qR&ql5`h@$5=(gcSTZn7I1kYWkN0By{sX127E1<( zjK>gtr4Z{FIMCw{+I|74-(tzYumh^T3#Q%}%^s+FsPrf`8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*Oqai@Y5CEMo4jqcXYAfj41CRua-oVQMp3(r_hk&dPCJ&=8 zK-GcnPXMV)(B}s03kDg+z`!t@fq}t@fq}t*A7UPKTRa0y{{m@xXKG zQy{ZJoB*gg5F3PH;xJl324W6K4uoOiF#3QjM1C{WozU$R3=5Qb!1lt_?Sjf1gA8F{ zV0a7VgSK{ogcuDV{`3NI7#J98p?sMCKaK*R7VMzEdfYKL^K=|9C?t(5KWB|?kfwrAqhl)d&!-2181ck>Z zs5sb-3=9mQ^}rxDy0{o40|O{b(8WU;85lrI7z9iq{sV3=A4jaZp%- z#P=~WFvv16FgQWQVd_Cwr-6oo(ZxaM*nrXwx_A>40|RJi58Yq2tPBhwKSn{#2c;j7 zy#;9e=`4_Vf`xY*R34PpK}LV*+%df58&lZ*@upsi}S z+^f*U40hiH7)_*mTg@Q$z|#K~1_lPukud1up!0e_@q{k!#mK+_x@l^O7Q`Ir^bJP& z0ShOn_rY$3u|c$?Ekr*|ovt~A4;_BTQ15dBA`erK?k*Q5Ncu5`ngg8iUsG6du=LiW`_^|?ds69Q>sU?AwuX;61YLiK_2E=VgO`#@K%gYp$D z+(3Fj_{3$1yP?wv4E@I;e3&{nN7?c~54 zy7**hJfe#~XJTLg#VNXY4l^X5!@?I72Ot~)4QH4*y1vWI3=E*Mb^%&`I}1$*&}l}7 z3N&*jpv5P;xeHks7(jI~y7*ZZNPa*U=VoPK0ByNN7uSQ9+vwsJtPBhWSiOuFCf&9Cc0g?_0 zl@p}abD(=nKw%4OXTxaFy+I)Hw)+rw#DE;Zz`(Gam4QK%N`jR z7(newQ2s#|w}ScyT^wXKNPUqd#9mlA^^_5k4$#Fx=Y@dEBXn~>c?z33QqXco$_ipW ztR0JPuUD-a)DeLhq@_+m>21_r*95cQzDSwZSS>i|LL=c0>?F)=Xc zKO2c0tnN@wWeZP0cpx_CDur2STtwX?k*K;juzE*t~(!5A1A(8W(M zK-v-L;wKp(=^9=9GE_Z!zas?NpX-43Uts=n6N1#Y== z|NEff1FQEUSs?yG7f)w_GEspytE+m!PdgpnQ%l?#BRWr=g33 z`lFz=Ht6D@b^@qwMHdI{9|MKI7+QN*6pfGG9%N>M)Zys$A3C2{YVSXhJac)M)xH-BxJUGNb?MKk~IJ){GM##Mq z=;Gy!koE(*cnc2qr!ivpuMZ<+90lDRejM)PgNjdxgTxnTs}U%^YN7ooba7Dn2l)$K zd=)g@(8X8d5MPf&oC{iRpqt|WEq~C(LFp5ldqDXSoA@0Z{=JJs+?WYcj-lI|!vsl( z=;Du|?QL}N_c+AQK*bH>A>j`ia|VU~ZfN+Ri-Y!`gZzaq4hlDH;;?j!t{yb51{xpS z2DKN|hXmRC8OjHZ(Si84pzc5ye*_g5NPxH-ROW)z8$kIm^FU%C469FJw892Ry$X^8 zVVF3KcK8C3H$WTTod7i-HoveO$_JG>Aal^wJD};`fTkW@KgbLa29=#48s;8@j}Z54 z_za;1pzb@L2=PCxAAN%h(%(WCe+V6~c?DGu3opSFNF4kv5AA)jOgMiY>;sxbn!_z z#3w_=rJ&;opgI<0?+UbW)Bv>jK_7R}gzATlx0^uup!Pb*ydRnnf5GA@3Mvnq#~@^0 z0#rSyjSMmmUHxrnIKtw04)kuITBtr)I(g3m8K;A_D?w&~@D8XtP@50LN7rY<%D@1c zyLt$n4}j@MR|gu;1=aIspyt8qXHZ)T)CWfw2ldH8ZID@6knjP;Dad|bXulR+9JDVQ z6b6T&>OpA|q<%FcWPbF;WQaMSxhjx2;}i%V)bK22lS1B<^muRv^M3`_s8ei^bHhz*zD2fCCEBm%)O zaTxs}1XUiQ3X+yV?1Wxad00M%l_LdE^EW{01^XfDA3^;AN@E~%Y@y>&=;ENhGN{Z# z7YF54Y~rB)Iw&sC)#pR|59s0r43PAXE?&d{nNPtc?!myofG%DPTDyQn9JDVA)CNFT z4;mi;mA&ZVTXEP6+Na}*#eC2)fuOnw-5iiOsBS|Sp9>xHM;C8`_K(rULG>!Aj6@f2 zhxVi7q2m*^8T+z?t0po@doj}##7P7+oA1lbnz?93=E*L5_IuXED-TF=r|=Pen9>K zjTeCGZgg?bIS-)tLl+N*&Ijc}=QBWY1~Lb9ZV{->wh<~0ier%YekdPQuY&j*j0_A; z3=9lBry%7cY(50kF9xM|ba7Cg1hr)a+Bw1FPar!$=D^A!1E@Hm`81Fo5Qgc4(E+z0 zfXPGY4eY#Ne>FhOOMub_P3}gU(x_i-YE&Ky5yB@d9SZI2EkE1=$J0|Do*ym^eDWpc;~{ zKx6PA^{{lxuooh}0ZI$(hwv9XgQQ>dbQo|9B7Xo%Gn|0%ahad69U?EV140+*smCNYjpmRh) z=@VT%5;`A(E)F_30-HO3L)H61{RJ!ElcDSSu!)1(u;}8TbNNB*BGJV`*NtLR-@^du z*P*Kig%3Diptl+M>c`Jg@=h~EYsuRI79w*Uz;Ffjat^5dZV@Nlt0kL{h1;809_o^e*yK~ z(ZxaIF4)A)S+JYqh(p{3DsBRGC&+Cef1t0|K)1h_1=4@?h3W%^F-RY1J_b~0q(Q|& z^A8|#bafrj_7%D~Cp)CSiY{Kq0hzBm1vM9z4o)#a`WxutpnE+)eM5Bd&(QHsba4h| z$b2NaIOuL#P zhShg4dc$-`J#hg_KZ1rYs4N1hhwWbgu|XK74@NJ5wwn}IL(D9zl9R7^V+K z7i@&+W7q_xL5e`-%Zs_HU z8&o~4JdB3&VeS7+C?D4DnhoWH`u`yJUWM{O?SBw|Dzv`1y3YR_5`~*FYi2+@G2Gl%QJv|%Bht*#v zpnTAJLr@T+hx=csJm{PkkUZ!dFi^dQo{rGVb98xe==wa^`Wp0hv(QaQ`T(t|1K9&g zkDxS;E)J^yLE{N#Q1!6!SPv*4R=K=;^QzDi5naS3>!qaS@O^Zb134 z@i5RjD3G~qw;=8X)!Eq8fzA^HsYBNfTE7a4TYad0n0wLHJ%i4F213=r+9&AhK?*Wz<@3e3O`VuLl*~?JD{TSH65j;X9}Wr> zBn&IhV6+1?p9G-MFfA~3FggLMt^keZybXy5SU74!`JgcxkXf0~{26#bNEk7tnSzx;SWE3aG5)xC3#gHONs63=GatKB!*= z5^99+uvSj;-I_>G6P*4 zWF}~gjQbwM9MCu}$Q;o4JE$*`02Oxx2{JG+fZ`XFM$pAU#6jbzp#JJc=sHeNID_m3wJShnIr{obP`U$!_fF_KS5Vx7)Wa~We1_4meVe%C z6MjL$0e#(U7#E~H{eu~@ZWffbKxTl(XF+zNi-Yd90Ht|!anL*iDD9$)gVcibpo@d{ z1cLOSi#ve!5Hc_@po@djEol7Mf)V0QQ2qkBGYiTGg*k{1N=Kk_3|-tAIxc`N4jKyu z#S6MPXwDNJ+iEo3>U!a?F6FLu% zF8-DQvMvN&9JJmDTllauGBAMh5W0HMIw4TJqKkv}D}v$*T^!U82E`S+cn~9`ABHXt zS`P&3KcS0*?hC{w?gFh>(bYTS5C@%02O6(ISFedfJ!pI!Tlj$X&tek?-5-Wc95fDs zP23t}sMR!_=6^DC^~0~%idr5kgoI4*Udb|y$2x_*$| z*u+8RgZzQ69+cm(iNoq4boHQe4rB+qcmy>4qKkv(`?0wPRPSID2hFd8+=OlpXuUN^ zoY4HlVQ9F)+R^Cc52)P)vR4$kzZm2nPA50p+o`nsWfP5SyP*0&Wh}^kbbX+90!Saa_;k?T94z(2P6o(42DjQN4H*ly2 ztz!knF}nIc&~_TS_&*%tpvfj|{sNU3*u(`G85lryCA#^b`BzZ+h%OE~hZdVT!qD;# zT|KBA2jydQaS3QSg)R;%-$8vl4^~Kd1WIe5_$z?&LF;-z{3<9P)RqMCLG?8#&!CHg z=FLIwMHdJ81Jq_m7q4W5oIijrUI87)L7$&n0i6e)0(B28-+aN9FXt^&Bubma~1;w11PM~#X;c-3iC-& z^&m4r>e1D0gzkF*$$>De-3+4>;!)2Tfr-QDf+YO%0f`X#*-&?Y$}*5Wp!Ogr4$#Fx z^G=|CHM%&c9>As^lr})=23+=;Ry;WkUVJpH^}}> zs5mIEgT#ZN;g2p3THg-}TXb<`=;EMq5G1}&0%9*{Yy@OJsJ;Q2gDwur^C0od zQ1!6z1C3*V);pk!gYp;1%^#rZL3tEpKB#{T%HOP#5PLyc7J z1zr3aE2MonAq$f3KxqbK4lKPYWJAP3Vjv9j4~&MLi-T@%8VjUkWB0CFFw?*}U1(ZxaW3L4u&7YC(Xkov#S za0K;7K<0qf$%D#hLhT>)eMzwMR&eca!gVgu1L!$9up%#(|A01R+ymriY#4n%CurOSl-7ly`$ff}v@(>|gVJB1>HI&G4{EQ1 z%sOkV4a?L+9f9q8ixtdRAn zAiIz;EFKM`JFpERudp3LgXEAgOkD$1zF`M`bqk>O89>cNZ)d^I zf3kv}69!vH1Ush+W*^KA==DbzR3EIqK%d7Tq<L>L2%nm9KoBwcx@i%gUzdLhN=hU6Oj2wp?sJ>37LNx zsvc(l9Vj1W|6?d0*6w%<<%9AF$iAOYKCB(_8_I{p3m0@A3g&yXW0JQ#qwL{FH=E2;9F7Jva9|Glr(hewm(DOkmR30?2|K5&1}YDWZ*=?6=LvhD>S5`HRP)jE322=bC=VZinhW#yMJONU5A=L-gMj>f zs5~sc|A+EH?IuvTfzm%H%`!sQ=Wc+;+ZHIT0u}!UU5~5?<%9HsuppGr1Epo4G)NAF z*`RzeD7^z}?j9(u0Tl`Z4zZUDN{c{gIVi0GrH!Dp z4U~3+(g9F93QDIy={zW10i~OubPtrC2BjB3=~YmA3zXgmrB6WVOHld_lzs-KKS1eU zP?|*p;vYUJEdiyKptKH@HiOa*P}&Phhd}8#D4hYNi=cE3lx~C46QJ}gD7^$quY=M% zp!6XqeFjQjgVGP6^eZU+1xo*e(j3rzc0y2E21=_zX#*&21*Kh}v>%j?fYM1&ItNOZ zL1|b%OYniL=MnIP&}ijj15Dj0JsJX|Aut*OqaiRF0;3@?8UmvsK;004^{0t76|J2H z*9PkM!8u3_*my0H5SR(;KY}?(1Za#Mi3?|f)|A0HNDR(5#-pw!qLDfP@oay6ynstCD6de0g?-HYG4rvb`EkX5GW8R;3&{2;3$wN-~h96 zlL`&2AW5*s1~!2L1&|Jq1p*K@M*|mFF372Y17cGH2S)=lSPX15$SRHoNibC)Q6K=u z91Yw8AxQ2U!I6FjQRwk3fM&s1qb2B!ZkE zK_w5-$kD(f5CqW&QVtGNu#-R`05%RP+#m$fzyb0;C>em#3@B}Y)Pj-=Ge-f)6Cn41 ztl$&?g;)qE*?{y51UVN7ggF%m1UUt{7Dz-m7f3|H<-m~wN{&I!pl|`F7LaLN90dXm z%AoWn(Ev5R0Fqk59D^N0okAQVp>#r!BWUF^1A~x5e0+RyNn&z#d~!y1e0phOQ3^v~ z5yu3F`1q2XviPL@{F365qQnA*h6(!_4{!-ga7d}lNr^8mDNTw`%FIhiEn;Z+vG3o1 zFkqYj%T`4zmNN=D#5+3&CFZ5%=enonr4}WYq&iiWq!u$wU?|!EmX7xec26yFEJ{xX ziS0lYD+7rgfQW_Wm1pLq#5*M>XO|QuCZ~d=PM}D+q~@fSq`FolrxuiC=I4PFTtHFa zo?7CPSdti&n&XsM3^w2fy1ZuqNZSKUVb8p@e2~-&bSa<2y!6t<^wi*j)a1;x%w&)a zAcY^$6$YiIXXfVxmn0UIfaHIm%ZH>^lpr~+VFf7k;-TISO3W-q^;!q2d}?uNF4#*G zP{e~%OWcD%*3Cc{hWK;=L^wXFDzymgzZFn{)S{yNB9O)nFoB8!Lj#b&4yZtJW?piB zaeQ$}Q7YI&2Vmmxq?DGJnFG>s0wM!WHR-7(@tL`a>8bGaVfJlMTa$-qpMJ8DA16VLIDIXde5N>g1 zUIxg;4J$!`pIDLzPDLGHUUFhiPChu5PJr-p@{3bd{QR9A{hVEWK)Pli#8WcCfwurG zl9E}JmI6|+0?I8eNGwUth%ZXbNrfiF4X853skw;-8Tm!2@ySK0i6vkk?!cv_I5Ryj z5ggD5a49KIg!uXdSOF{yEo$2-%wtz!C-d#hDfHxv9B{IhpBT4KJYL+4GOOUs2RDXC8-thDXGOJMfqU=cVLmu%!9=21T1nn z`N`QJr_I13S5j07lU{&Dx-<_ay8=@-v81F3*S}{4XX=aIZ$AORNjE|QZkF;QDYA#150j(dg23ID5nG* zZ6Gs$zyzVWt6>c&c0lDFsBDY}XYdY~SPmo&O@Qz#i<9&7z+RXE7AP;u1ZQG!-N+cv z&;y!m0xdvgVR-P2iGhXT!fkB^7KVm?Z3YIW%)HFv3Ww}2t7sI<5wJ~bo0AhoDCKQA#S zv!s#%wCo=2jRX7}m<}*Cuq!BRV4T3Huz{_Cbpdn00>%xD6POzq6*e#xY+&BNd|@fm z0;UDr4igxEF}W>Z6i`rTV4lGAfaL;n!UiUT2F3-93*fpG%w1f~PRAk!x>HZU3}Twr{_Ho@kDVL`wH=7bN| z88>h^ED)W*IDzW}(*wp0ObZwnux{W8m>@QR@quIlNXrAp56lS#3z$DJHHcqe`M|q? z@j`L~YXeh2!UpyYtRMIq7&kB-VB5gh!193AU<305mV$x}ObG`VvBM9b;9&%L`+(d5 z<_U}&7!4jUPvH8%e1Ula%L1i@fDOzF8~7Sn4lsRSF1Wz7f&Boh!UvuS91j>9I2*VW z5++DCa5S(zU|ztofENNE@E+hhz`lX~0^z|z1pfvJJ@0m}l;4N@D#76>fh07a#N z!UU!X%okWUK+I-TXkckzZeUF?Xkcn!f55(g@c_#M#tBRfJQo-ncqXtaJYc`TdV%c& z%LA4N>>pSbfTE3S0<(j|1V#me2TTu`E->9^(z4V#&h(z8m1!rl>m5d?ZXTx%%nO)7 zsj-1EU;|@g9HTu1pxs8fdh;im^Uy!VA{Z_ zFoE>~;{le035*jM8`ujzFh1ZkNN8YEP!JFRl@tjdxIi+XA_Sz~0F*92fIP@#aDYW2 zKtSLEBZyRJU_8L|fKj2~0Fwd8hZ`6THb^W01qR~nLVFL36#sl09j0qc90v>>zz$BnxAYgES<${{Q1V#f;$=JZw!2f~$ z0!stq1jc{_us;(L8kicG4lr(DZQ!23)xcP=fpLSUgTe+zHmejdF^6T0$>|Rm5Abc^ zp1{3;X(wa40;n_ZVzGqDK|rA(p@A_WS~MYo(_SDTAi$weUSI-KK*0k>g$In&n5+UO zFm7P}z!>20fGI%W19Noh2gYMeaWMi42@9AWFm7VBwQ*$tou|Ri2w4RIsG=l+6f zFK|7e$O1l38O{#`o$<*4x(y2~&2W|(RUUK>DkGA70GfObnt7mm=aALwqRE5K9YB`1 zKr>H-3Dy2)G^1A^W!< z%|6h5@yPOb(d-XJ<4dBszZ#7XItK;Wyx(Z01KLjo+QSCgmj>E91=`;QDpf)Imq2j> z+V2nApAXvK2--giT3-g*dj#6!3Ob_zbVdzm?>K1RH)ziwXq_2o4=U(f70^Cq(0*3X zIYyv!4?z38LF>&xaRb^53p(EobVeCyuQlj=0MPz$(7H3wS#+Rt0YGOSfX<%-?ZXD0 zj{w@Y4qAT(I^zp;wiD<~JJ12Ds7~4MzqBQo~V!As#x) z0ve!5E~sRP$3BXHDvZ*Rk1xnCX8;XcfCg{k;p4Jp4DparvUu27qphkJa}La zdC(0qa10%6iw6%kp;{ad?y{nafgJ-8PsxOikRiswz#~{7&w++|Km*puqmS{qut6Ri z!_D#W@sI({_+-$i9cXk8>}cdM?076)VW{6g-ED~fpo5m+QBUY7KzwRN0V23TO2H!t zuz?@AC*d-%0Ux+baVmUZ1LQ-{=vRC$WRNO87c^8A9}gX%MUDf{0PvVJTnK&m8!mx1 zb{vlw`G`jw(~1WVg@HT=8qSCZkAKEPh6F)DjykX#k0q9%Ly>TwL58qkV@+tn&@m=7 zQSi7E*p0BEnD|8SuxxxWbW{bd0X$3u3Ni3_U_4}y1)H;=gQy6pBIw8~JdE5^OOVIk zk;Op+`mk|%xYb~ffm1AO5DTs)2r?9pk#s;~R}ArKpkW;hIh4_CxJih)4p6oPjj^J{ zLOOV+CLS`n91k7|i-!)Tf_UKJu6XFEH6lMJGr-MvPc6YZdjVGv44yqe=6VL8Xa{k@ zISMfj9-oFXN(@d?$m6LXr=_3{(t&a(cy=KkGBgb7H;j^_AuyQ0`Co7U@qoLMkj^Wp z&31vEfuWTFbkijR1E{SAYV*X$2RORBM!5!o8aZel0JRm-gfO@ub51ZZFvQ1)xJG~u z&qY!J*53eXNg@eC`Jny%APj1Af@I_4OHwOJKr9GGH&1~B;+Yq(AUZ&NP&*c)nt=f{ z9t~$RfZPMBEkU=fK}6Bb^4QO&@>!NLIE`wD6UgTz3S z&ZSB58L2r1sh~UG(9PTP4PxFIsCghhe)pv(C&#CPr+?#15|hx)>-YsRj|bGaVqjnZ z@lnhJISL8K$0z6K6(a%=-Mj_%kn?gkfDYheU|<08LG6DeBT$&}@yW#{MVWb^dv8Dz z=;j3kK+abI^%X$!AU=qOVRW~~$CpB;#4|xt)1df4O)oJ}^8`Sh0|o{L5FgzPnAspY z9yHRQRs@=L1#vObUqLa%eJdV-r5QkcXw-lOxEUB=MqwqXgnIKvO=H4>A*k zL3tJ=9Uq^Q7++kOo0OjuUrFGWJL#9k|91mC9wo{ z=_NS-g8T`Q1CbTb^I{7?9aIJe1`r?Q9*`V193Kyzmn=&x0+j;j?t8%vaUTb`kYZo} z@sZ5~^>2~Qg`1aD3^ESGzaMxY=0W?3V6DjJfy!U7Ae4xYhZ+Ruqx#o@7iu2VeIRAX z<{=4#q>##UA5TATR~Ju6>SbVXfS3c1R|iPE8h|>^kg*?dIS4ica#vXcsILvu_kf9k z;eitm1E`*W$!j?CFw9_MU^w8(!*GHDQn!H8az_9U11LUqB6t`!urM%yN`2(80O<$0 z8H7RoXAmD7UJ=T}06I$=T@Ew`0UAR9xn94txJW-IGf5xRD}pKnjX8kap)Q!VXv$k- zkDY=V=G#9%Tf?dVR5B4u8EE!q)SwQ0rpnMB826Q)51IU~LaOn>dWdP>} z&=>hR(L7Yd-U5imahWFq7)QV{<^3=cy9C{u$hBLV++X1e$-oNeV7fA*Ds zt2tKwWoDYl$>8u)gVEuqJtMA$xW`>C@QyC^IGBgA!F*F1*Gc;Us zWoQUG#m_k905gN>i}MaEzwkT!lxGxq-O0!hvW1ah%L8WNRS%g(R%KcQEZRLMw zrillb2c0EQKpym>E`qj%s|t?(p*j1H%>&|1-D4Pmq0&teB=eU=~@`Z^bm_|6#_9-@;i| ze(`5r`L~*F<=QIR#Yse1O^1e+ zixtz9AMtxuehX(@`Nf}U<=<-Nm46R2O#H^owCXc6<0?@2akmoG56lH1%Vqj_r znq|c_g@dsnNS=Y=<5s4IS>mkoXE87{Y&pPmz;*#sgKfq-ho2b?3|kU5IsB}(VwzHZ zO>`wFon~xf_z8-ihs?sOnEV=o`mC6yWE45=%q(--$-yXX)KSE=v$Kq8=ih4Qm0$dw zR(=b2T=^sZ$O=$ec?3#t4ki;HFpIBx$Sk_*pe`akWN113nv_}KyC z2RZz-1M#CAetLlTaSlJNK>Rd^pBx~5mcvgo5WmRb=ZW9{wl}V|fcQ-g zKlMQTE{C5UAby|2Pc0CCn!`^H5Pz1#Pc;yKk;Bguzy5DYSmy9k3B(t3{Hejnu!WPM zVG;*p<_~#RhK~yw7((`2GG9#)bNrbo<@nR1mtjhV93FLYjz1HX9Dmw@)PdrXkzs4b zE{C5hK=dhxpD#f4J%^v33=Nk!89scHXXJdX#l#RI3<{@B4nG+{?f|L(_kT;mK8K%w zESaWcoO1Y?0pg!?`1u3GN7LWUzz_mXGmjj8ez1h(B_2|Gt$l^FA{(tOA({wwul2 z=S7g)_@L=m$l>Q%5MRpSCkLbBCJ}}Ymq2AABNIbNh8jc+#CHVojS&1s5Z?~LKgh%o z0&?dU|3l!koFM1$b34dfBr_QP{SQgda`?FsB#va(fxrJl63iTat_F!Cnb`mmcXId% zGk38ilZYon!=#u0-+u!6hwEqkByCxSi9C#)-SLdvue%uM&*EZa*b>jm@R5;)Aq13v zIT$#)IT;;(o@8R!D$T$kqAAKS@fEk@CV57#*DM!3e}UYs!NjlyIlMSn9Dahr>;W^! zsy1*u39sT{ZU}<817_w+28JyRtPVeGEty0>@-Xqt|Npmw@-xVdpfG1(arjwc$s__2 z!&VM~?BWoB#)p{0&rDD_6hXq46C59$uN8S2LNer_YLpy)CVN^|5#{~=wAz}|WC%OuN(xup{ z-~a!MXE;I31F3PbWSUYE=Cx9kiDAn$C5DOL*&R3i;C9>ust2z%F-+Xb#_&;rks;(n zQ~VEkM)B7Sf)T%1RyzDr`2RoT0F%Q{6HBHk-#}*i!ORp{r4M$8H&%D7Lv}|LOpoX) zC6K@R9Dc4~VAzt`I7*0-U>2*7u3Fe4nG?~ zd{9{rl0WD0vlhfhE9<=(7(zN27`DR73~>5*gec1jESN+TO0H%qlt0u$T1*)4s zbq|OpqOJ-BxeufsR5$H&_!$V|gWM0|pL6)>3*w`>AGxk_wE&d~BC9@`Gfm+#!rqnw zm0_X|4nH{bj4Sujnx`~ScA1E%;NU-_7(fXwXp`+w_OkeHIg z&krCmHHV)o{{7#2`Tu|MQfL{#0tzE0h! z*vlT|`U?~n95PV1DLMSS3NjO^uDA%|n?b?~Vu#~OP`!N`Y&L`qO0U@JDo~mgzTyC` z2g=B(2R4~Q>VXa>Sotc9QNFGOy92X*jMF^}9H4cGFiKq}%MVJokh%X_tU^}BmfD6 zOg@L793N{hbJ#LW~gnb!$S3?aqglCv`&Ijj^> zgw%~wk{BjRurpk|!N9PkqbdGJ$N&FZ8kih@dYdy%`T769cr`o2#0(~fpB$j_fz9Ek zt2xsYQ2y}Xcli0@_x~-R_JD&q)08{^|BHVH*$>SZ57-@kTA4FVDOPj%Da^#M1!M*& zd_eid1e8CL7$)xe|9?6ty#7`r`hP#-omc)!cUt);-x1sw)C9)|Zh27J1f@feIdb4| z7g=@MjA_cZ@VzU)_%p8jTg|lcZ!^P0kQz{0EhEq-;5A2W6L5m~NNoaEb4Z&26yKo! zILO|=)lMtF_&cur7JhieUo%jiMDRiGQ&4bN$;|8U^C1hv2E`7GiHjH-f)W_yul;8R z_1RJTp`f&2&%jj3#KaI%0xHv(9DdGc;&{ynYEQ8;eBgMfG!bOyS7ydl51BbueUWCE z0_q=TusQr>%rvp+U}KnQ#K0i((wJeQHe>y!|NIO)?HLpbA2Be5XfZl$(x_nz=3tdF zdST39`GT2yl{f=~hz6^}CVK~mLM29q5X~B%U`1rU3PX-21TN{!8|1(3z z2S8x}@;6AmGDAa<3PVHCTT`YfAoJh+|1XXn|DTzezFS-h(9%lDBQ>G~onZ;I3<#PB5vj2ZG zIGq3#O&$9AwHS5a1&Wsa5@diqZ#xV{nzwtTzWMpNyptwN=9H)yI8-fln z*=63|AohXu>oPEiEM#H0pvYnfHWTKC7t9Q+K`({me?&t3te|rI$TKn&u3%^gdI=iC z6zrJA!OC!fZo*}$y&0D!?-pE^_*-#VT*1MTL&d@JMKZ%r4h;v(7ugIyIdmK>Uz9Wa1dT8J z|IGUH#dp@9|Npc8{QsNv=L=@Gpa0p}e*XW>`18eo#-IP0nSMTCW%$6bzz8y~Q_b*` zfzkdN2ZMv&wHo54ShkaxpdpDKxa$bTTvqy((t@`Ldq*=YzwIwy(OGfBx@h{`udX z<>!BYmY)I)A1?h5XZiU^y}|a;`$pRbuN!Tv)!*?~|E-{*Pw<`CxUU?f+z!pKo3>|9tzN`RBXM%s>BcXa4!&JoC?2yP1FfZ)W-V zz`n`$p?Q<-1M4Q+2kV<{AIxvIUBEoe#(}xP_QB%@+XvqpY#%Idw0*F+(e}aoM%(}0 z3_l+@H`qSlZnk~E-fa7Tx!Lx?|0df9zng6TOSAoaCC~Qr;rhe24~3g;AI?8)+t}Q0 z^GciT=PPrzp9`6<**uhQwsmAaVB6T-W%J6J?dL0hwx16lAFzF>-E8~tJ4n3O=2bM? z&sXVeKOdMk+rDYmpV+I&GV$T^gSHD98*E<{v;BNo&-U|ybF*#lLzamT7emFn*?zv7 z&i3ZObtOVU$g!E|DNsVtM6<-J3;aK@v!Z~`exhL%j!odF^oNw>v+0H*-PImrT&&2sUk%1wEfstuqbQi;v7t@`8a{4y}B`9&u z{@ScR@nN(6L?-`+pyeh^Q!>~XCcc=>{PW9U{)rC`^G^ijN08hC6Q(KuZ{CJS?3&Sbx~|fiNh|9ky*iN+%1Luh~41Z?<({ zK49B`lujNzK4AMmyV>@^caS(Dojf#ewq32nGV#IkgSH8b4Yu9j{KWS2bv-EFn{8J= zVww10F+}baG@U&3Z?;{%h;`zF`A~6aI(Z%K{PV%;gSHQ%n{8i1)5$kzI{5}kCmXzc zWhcG{*>Rig=j;1yKe_uSU3$G><)`lagP&f@Kl{YP%CMm)Q+DF($80~}JZJl<&D;?5 z8Y!)Gu`q0K_^|Eb}| z!2bd*-F$U+`uW1z=_hF10+enVn3yJ>?PQqp!rlp-ZWbtU&i;&)ZX`{ZrmO&^8)v4U z513gdK0M41PEW!nOjAH*6(|ls;SUOPPW?dSj9Y(HP@XZs1tpP+D`!oqN2$~onUD;OJszT9T}`Qkp? z&&);a6BQU4)^h&ry_8wM<3TQ5kftSM%T;_uE!ec4R7px2yK=nHVOGD5mW2Pwz zevtYdl)fIWKWNLq!m#!QyZg_}tPB^5lZ+%84V`iE7s99=ah7kU=*#S+P9niGdF#u`v z1+-jvah?q{cX@$>k#!;`Q$x@TXdTkAhDglNNKc#h2a86BkRN$>H;g_;m6D}@!nQw z`ebA#PhHzArK+m_Eg!>GL~!`t)xI`e6hbZ-SIPp!oeD zh(CS@L)n87zmBN!n*ojAjF7fV8jd?Yz0f}Q$wT)C*7(gJC4LXGPJHp3@#o9`j6dOV ze3*Y?DxNrAU_`hKnrOr{ML`#P91|*g7PLaj9&B;j2#Q-!TLhYSD~DLzPGw=Zu=SY= zxNiB1R<~&8Pr0;m{pL@e_ilVzIr$4wbqg%-Vy$2Ps!e>UjGA}fZ(%^xAE0*hZ`Pmx zAM2ykA4*0{Qx3>O;||(q0oNbtE;!c~JV`7qn%ry(fr(pi3f&u01g4b!? zCNkMJ1PK{2O*!!hR8AcLxBX8faZF@jbzJ*kd$aAs-OaWPtWIkm>~FSxco@WXSPSYS zJlx!D3#otfCq86m0h_~M#54t5M;ob6DpD(1{z-=ASnu&v; zu_y+^i3v`O6Peo?rp!1u=TZgdhEF#Z-1#&^_4%h4@@_vRDouZY+dIl^@caX6BVRXU zn(}$M^UoLaL2YghaGU!;64S)<(o9osE^NH?Q)cm}owu)ls@(MR(~HHZ?^!V8Qm4SyPcI{Gf8z9gNNG8|K{HhYqa5ztjI$ix z0Bgq|FkqU}@P!dPP6r!1BDNep-;9r>1{9`AXgNI3fN4qx3&RGEl^PR0*&Bjh zJZJj}8m9oIcUU4oVu1UL;Iy<&b>a&KL|L~IHlA_0;gaUh4WB$E z4t~-U!rtawi8h{rrw-nrxl;?HtP3I{UA5{nO;I?9JzZU9W!O;6gr`pkZF^2?f|Plm zvsr$E>tUoauUMbFG7p}?9MmWheX4Ypsp*?xYR z&i3=6f3xkkX8nnK7O_rj1c@(Z`}qYlz8&3c3mP-~vY+iITEBGz$3@kNpfQQh_t}1) z)aksm^7e*LCpR7Wq#64A6KE{b>!|ib(3sh)=WIVWGdBc%hK^Of_|Eq8f$;%b&=}>9 znuees%cp2w<2EPHOuwYLam}Zd)(1aznm>btk);kijF7{pgN0!N z!_UKzFk)u^i8pMJ($fYygw14zh9FYIrUTS=@Kl=!3Zs|oj+So(scD_H&mr6*8Y`V5^@g5@(%8u|iF zKd^Mu0ScR1NZ5Qo&-ODDDb08spLprT`)!{(RPKD@c>C+qszvM*9T*wb!p8c3{Ac?K z%0CLs3~Lp*5qSs8_}&JNLS2mUy=NPs<9k@fpB43Rj{U&ft)Tra4hx`V`RaqVXyeZt ziVqnQ9N&{~gp2`Uj6d5p1Tp9_O(}WAKCyx6fGuc@pg4(RVgh(9nsaReo736{>|ART z*c{eAVCPs1ZW}=RVxM%OZG*kK6FZUG2B0y32BrpEaG0Ofn+Qrr9-#0Bk4t;5O#qEa zvwN*gU}IYQfZbzl0vp5H7px2$I1ZwYJuA*+M2tNv3#^2UTR{3ukC<5|s(t=H9Xyu? z8qWup^->d0@<81rsz0$)r6CA35ApIa+fPW@razHMw;||=F4L5ZN30V&AF)nkV0Brm zz{;|gLGysEf@TAF%tV2eX)Ob*^I8Q~=Cus04r>)y8Pk-F91y+Z(4y+7o6<8hDIoQFdWoXzbqsue}w2ps8-HF-%nK@oJu4fEc!6w=*{;~G*35JGAFaE#(^q-02^`Ui) zAuIJjtss|exx4ihw`tuuTZNP8VpZnPu3boll z`$rlsi7+%=GG}NAy1~jYLBd&NA{PUL$hYsVKc}!UOgzCP+%1{Ouo5&r^5r+{&*CJ8 ziTjyE!0Y~ISC{;#W&y1gmtJ*VmubpRW`MJGJNr)& zR)#GnlNctxv}gZ$6||-hx;{98g&~B2q2bbXHiw@Y$qW}c{yJZN`I&K3X6RJ#+J{Vr zh9J;f2S;P)EGbroi5~1y-4dWRlFSW39`(@p0j=$QU%N@3nd7wvBSXk-E{C5I%;2y9 z@8x>HEU`*MmuboZCWfsHj158H{d@`P6CW~5tjbbgnzEUfLF6T~*s7QD(6t<(c^uH3 zZ!-JOpR5cMEEgM1{GZPL^F@1vr3br)Srr3=h!jJ^C6HMipK33Q#xSfjKFn|t6qlmR z4MEZz3_rOTK3uY4Xt*RIDsuYQXV#w&zO(-11nn98?h2OUU=Zz=N@Q3m&B^fdI~&8q ztw{_Mi<1~Ge*ew-b3T*kYS2386HKDrl8Foxr5GK4#xrxhW?*6nk$CH{5)|H`^!M=Z z|B#G-i11=66I!sd@|Nk$3lS#DuXFS8tpZ4rOLH%n`S@Pny z>(3Y1{K%nU{vz4^CkF?^mY4DFKS6VFFVfk6YAkhFxsril3m2n-HQ(j~{f%iDU z%m?jcT5u z)r+hIm4A08FiZi3185F;p$>?RC`%t7wwlPJ%P{3jurqjFJ!l-ufrBCB2Po{;9|X^L ze@%A&`90nF=bvonpWpJGfBr6Z{`q2hgrx?PhWU%_5tc6)HOya>JO6x9&G7T{f7hQs zncaR?DM8i&M>R7{v}9+vSXs`n#iJRr4%mU&;isn#WF0W5tt!lrHcwI+qTaHZVPY{G z!^O)C3|lmy>J^wBe%eCSmw?yGORTa4g{Kn3L|7gI?Z*L?V{_OUF5Y2a*pdNVbDY5B z@KX<}7qZS?e3h0C(-bpChAkP)kaf+V{W75SQW@+BzM>A(lm*R97eQeLs&k}gF-+Nf zyKLnHX4H85?9T9$Ba6Jad)Vx<_TlPA+Xw3#ZC@rc|9q9s44&(M3~F;S3$FsDsr+V! zDKEmEe{yg#Yyqtc`yB85vzVP>%eP?XpWm6qRy}BQ_`}elpvS?GHc#dt<0NTj&^}}a zk9r^4nL1;L)LwR(mY3F><^D$ptaw^s}5>2O*#Mnzqkfx zLl7w4d(?8wmUm^`2wErqBG~!oOOA#~9*Uf^859{WZqsI(@+CQ7)OjB^B(VGmYX>C-AwVZh+haEW|?dKnEwx8el*?#^OX8Q>$YhH*$=2nV1nWnsOcmMe+9=ta1G;+NtT+2B- z1nM@>8h%imgYqKs8vZDl+nn7Y;b86lGgO;tN+S!y1jRGT6G3GSXw9)PJJXZ~AI2^A zEDRq(dt@9_8A1D2EalfS6fR_B2w`Ahn5ey{ z#t`|f426vh3?b6o3?lX_3?G@e7(%2O8iG6+8-h3)94s{tG6dSIG8DduX9Sy9t z$inbZ@&Er2uzMI-!R}#VY6#+IXPN?92fx6BamyoS##N4_0DhHVvRyDXW zZuwH}@YBTo=XMbkdqh@AYcowr@MPTb#hl?M*nD%S`OZ-DnMGDfz|4Q50`>zaOz*Kl z_U$DwGHe0uD`)ihxqUNAdqj8@3&?CQ#x0-A8GeG!5&-R&0<9&LWMB}1g`MK}|64Na zSIoArP5AVheZCYIgM+2lL59G1RffV9;fx{O;f&^rEDRSxaTLwS$!*WbrJelme~2SP zL(mQ`29f{eQ1^O++$*vQv}XN{7Bme=sKVX53CX=bm>E|cbo#ly0wpXYR^8BInzF!^ zam$ZvhM#QU`2TPTRMxRGFo^umhT4-1vqxgpC73-v5POuE8iHmZ*#lY&f6)Ht_EHpk zL|5(8Vw$4h%(&%iJd!=}3_rp4#6#@)%FMbFbPmHKX3BuL=(BGdxdF~x2 z#BB%9OuwYKam}ZN)(1Z|nm+@}J?Dh<8x&RNUusNR`{|(f%})o7o`B^Za6;NR;QUgf z29CQVriLJAHl`_{Hb{aYB)@>m9%hbJOO>E)EmnrY1VhG<|7;8&AA~#qeIY(y>OLF8 z$Cu*srS`Kie0&uTI;X;8<*RUkm7w{Ub%Ga|Qe!lj0`1$(0 z@XFWO4nJQnXZZO#+2QBwWYLwc;~jp!-pugxwZ6m8*V`F>zSeg5`Fc0Q&)2~YKVR=> z`1#t{;pgjgiIsmQGw%E~opI-b^9+A~FJ|2NYBHPU%js;EFJ`k@g2H6`C(vFXzKM`M z9pbB$xtOLrSPbHetpeq3c`l|Yp!Lq;3?D9m>LAd*;zmY>5LljhkqtV(L;Ur_WQU&% zSs6m4Kgvop-)G?fCO^xbsg? zdsLP4#4M1TK;_31X6aRZ?-{3n>M~f}!lK1A<=bc1pI^Vb{sh%IFT5FkKKSqYmxDn~ zj?-agkZ1AoB6&v6>;uXSA(}{hMHTGsUGVFF$b-XH6CW{4teVZnGzH|Rm+_!;PdHv5 zFkuLJ9q<104X7;+8ViN4p98HW@8IO=7X4UzxrR}+`z5pJD$pLClNO9yJXjrmdM#v_ zsJW2gqC6|Z#)SX>L&O;xE=e#p1pR!@v}xzenV-IZ<^vzuO$=se2zs5&@becl_o{C! z3>&WH*-d=OEVas>QRa1n6hnyBLWYT2tPVdxV)3jD8y)`t5Ak5+cp}9~^-U6W?Sz`~>YS`}wZsGN|taR?95e zErz7l(wSjmH9Nz{1%LjBynx#G@E<5l`6q(ydB8DI_#Gp7FE=<|jX`NZa#ihh#wlMv zyMo)(p!J>%ii{y&zPtYX{M!|NZb$5Y#wm}P8CE@EW?Ti<%M2+)g(VoLJm5ZT`;h;z z?StKowh#9=+P+YB1NW=IYu;Bk+CE(0X#0Tuu?s`6|I?HcL%h!o&pc`z$nO4C!!o`167lat;Y-jbo)I)071!j9WnUA_oJ*U(k3q zXdTmo|BOFDeNIrAJ=lHF_Tm16;PvAV`I~JY96o6K@c2RSI^c)m&9<<$k)ZXZpf$q} zHXpQoxcwk_ojGVNF{m$}QDy!I76+g-30far3v%0LKXAY7AgG^x-E1PqNB=*tuug=A z^+RUyRiHgK%?u4eEQ}37of@Dvoa4{e|No1F)Pm{?NZVdym9Zw%lo$W1FQ3q3-1_BJ zHGE73)UQ#}V+=U~-M<0aZ)K&)G=)Kraf>KJ!zECkTauyS(szCKpAU@Lf0px!cmFqM z|7pR{5cHc_c-0nGh6_^`Sx@8z*H5C|Qj82gqnO3IrJgdZv}Bg)mgHslS*^@4@rOD4 zPYHI0iCdc(Ce|~HzXr8?IM^93mK8f_zSMXB2|DA05#+CA%ZZ?RChRTaln2J{V84Ll zEZK5m&IiURpmGORF8tGAn)2(j>(3XUav`1pQZ{5W{QSMxapzeE29Y%!3|qdutGWCl z+2N;TBg4fghK8WctPCHDlh`LB(zZyqSSBJLOmSkExOEZ3#jR`%TQssuLFb!nbYx?I zr}IXT8$si`%)+ap-ZD;k|Np=ES8Il!-^v+&g4_lwCqZRm!}b3m&;R`we_-EW3kzEl z&>jiKhM-3pOjG0;7(PyBYM3=gZ2qhVyo_5`@PO9mFj&3-jmzo^g7?UP)}l@Ujb|`? z`1HvBfbEOxjz1sSgXUHkwmi7)_;a5I(-g4yVaJ~xptkUF$Dcc)Vxall*$fSrq!~VZ z`r+;HGl`Kw1eBL885)8>eV6U53|oGI!iHID)k|iHRiL%;pmImkkzpc-Bg4h{OkAr& z7)83B|Njqp0Gb!rU_WsYE5iq;1Dq2VvNZ(BD=|%R;$#pBk^Q-S3p2wOm>)suXB8+N zGK0qAL{>2{L&gRkaxjR1%5GTP^=qK^7rH@wr2ay?24sG{)q`P*F+)Spr$7J2%@`Vj zKx}1jT5GrjVuSYFz6fUi`5CmoB;5_#BM54A&Mo zR}H2qp!EEr+r9DyFXNWShn;u6yzlt)OT7EfN6GF#!F8uIKRC~T>dyUd8K=B>?YtAY zUQ|?K2>JEe@#l;8jz3?3^2uk%pP;hg1*{LnEV4>lgK5gQ!;U|H9(Vlt9aKMGcl`O{ zw&Tw)hn;u+XA;Qd}G-9&YVf}&3eX_ulN~uc7Wu~8GdqngvfJz1DzSv z8RWsUs3@a;#cYnhX_sHtGzf9#a!Pa6HUw$ba!GS}bOgO(Tu{WJx@tBeguL{2_<8a( z!giRpm9x(*WmGjc!!@a zvmJhVzGK(|@mt)F7y1rA*;yGbBKh$`OM(s9k53sO{*%JuhJzxI{^w_5ho3Kk9d^ny z3TG>V+9Uc7J6}qJ!jm%_)VBqNwIirbW0qL;l38*UXn))a8HOzit`2(~T^;sturgd^ zP*9WuwV^L*1xh7eHy>i1-ZogndeW{%ef~7XxcUP`5hM6o!`!TYfV$ ztkPg;2m?h8O!bZYx8c3X3W>qwjIB3l2C9}Ae zO{2&v&{;FEGW-X##|&nm{WGC&|4;wVY&Q|tnE)@9kLQ5) z)WgbmQ)nG#s1B*aHZwKM+9Ni9mckVV)H*B>QHOnd?fCO6sO$liJKhXGAANWH3o3g+ zVGXXgm_=4`L&Fai?yzyg~@-1+C{XlL*qNruLQHV@1jY`>;E|NNQl{PR)1^WT@o4nM#AOSp_~#)J3&LmtFC z{QSktx$3bRB;G;oa+n)IX~O}e#@gZM4`$9)x1efny!*ek;{Si~@6Ww=hA=a1`Bg4& z6v@yK^p#n7)ecsM3lfV}CW89bZEB#thr>?JT83bIL57bDnHfT^FfwcbjUhG}&6))n zLl<3T2?}c#hK&uNwg>3k2quQt&4(F6wlXnve_`fW)$#BDRtbiNOCa^L)tIJiJIijC z2Wop*JN(=VbrX6Ra_sxR_0!+~;*zxqn?dOkq_;}K)*K=V)^F2TwYMMhA$ z#qk=HcN$lL`g)*tMZ+af{(3Q)VW&I;g9s=uKJ<3@$-u!7^1#~R=c9OspO3R0em;83 zI0Yo`z|LT1&Cn18OGlu*xGP#H6z4~A$TAdx=0!mBBcQX&K>etP<_;{A6TOo(E#T zXXaYP#h^LwwZ6m7*NY7-Ur%=1`Lbdc=Kob`O@6s=gVNWpTEP|e*TJP`}uOR!%j^`2BDXajVxa^yX{;xk$1-_ zLx&x&_Veug^PBbO=l_uX;@{ZWe*WZU`}u>P?dKz5w!fgh0%%Y9`_HUDUotY7y_oK1 zd14}i8Klf*g_N(*G8a6*1S)qyd*q zamrV9ho3Lp9e%#BX8-xZ-r?s9d-k874_i*mV1%5x`WZS;3O19G4WkbDa@cAjC_jCP zX8!pC>L%4&j8k4XL(Qy)ni{VY z_}Iw85b}WC>F4MFj-c~aE+{I1+bE~l+ zCsefw5VbIM=lGeXe3f_j2|DirWG-Ai2h?m48}RuMpz|+M85)8>W*_8dnxY7r>jjw) zb4v|Wy^GC6&=@hy{B0n22qU>;15{lCs=75Gb?i<*AM-=q3o~y6MBP$Gh~10$nWlW^ zcKZ2M+2Q9FCWoKjx?Of|V;0--Ro>}mISa#wQU->tFSMP0a0jj^9c`HHn_{(J8$_GpbY+p4?O>}5%wBcfD2;yScTlDHQ+s@a~ z>_1<|v;PFiy~<|)`7)pV=gY&E6G8bHG;a(VOIUuBamor%Tj3!i!E`(4CgYSx(oR30 zvM_8|%3(8cVKdW32L@35iLQFWEWGM5Khu;)>P|l&YeUtDAk;iz7Fl(hpJ@tcoce{i z(@&2r3|oX58ZLpx5B?ZC{d{Td^z&sh!%t4Jh9J=S37|3e6HLl-p#C_hPY3cNsGnTP z&=3SFLtijUf%ja#U}U(!aZz$2D1Czck;pg^)Ytn3D&Il*?y%Lw7tzc=zd_S^_D#kq z#fO+cXAgqsisc!FUmr+k2>I&F{Ii2uxSM08lcmOAc5|HOKV-mbe(+7kDIjw=7dlyr zEOoM6$--ck3EF4N$T0DxHTzEwMurcdbCE%L_P;ax&tJl_mA{;ME59W}`i2ke57;s^ zHrjyJNpUdjEqZa9ZRg8q_MhM4*}-j-7uoDTzvZ+4d~w)v;)`VVpTE-Ce}dxC6cUdY zI2g^r?)wJyKdAfx`TsxE|JpYhr);5VZ@Hc*&l7w^d6q)%AAy1_W5lUcZ1 zLy=*kCa1$sO-AtAiJuOP%JaA)R|GjSs>?BOD9R}^Lh35eyaB@#2K@!hVtc@4E-cO2 zv;SPt%ybd7mPUh#;RDAv+_%6Ksbf#EzfPy!HZG*8GCzwSXIpQ+_autm5Ez z{Q){J5L_-8I{eIJWY_{49|pB`LFb8r)8b0km7wvW-=O(Ks601l49gVM-emaroqfI( zNIhtdPtz3Ce}{-^FmkNwkYLzCkNB6l0j?`9a41^g4O^ibj8lFS$jjidaw?U5->yT_ zB}XFT#2=tKkCEYm=0(Ygpt=uKM!rmE{|Sz-M2Nd_#n%&%Ia-iBwUCJ+1RP%n876Xo z%6#;+kj(xQREL7iRkQ|&2QG8ZU1yvEic7Fs0_pPbb;c>;T=>gDYKGl%ke``_yEz*f zCvq%goCvCGUg8SBhxq+D1!NX__$e|@ zriq~PLz{`=L-t3#iLdS1e{wW4P2`%)G!fLFdhO2s6J*Y7fA*iRg4ut*4rl+V$*>^k zC62Q3B~pC>swZADv+w++%vbqqGV{+D+Uz@D8nf^G)y!AH0&g}V7l(Aq4`>x@%o_qqQN@Avotnrms~W(b)HsuTG=erR%o z${fzu54jNa3a!G9{W{~61ZGe@t0DKGm~HQi!;;|egV(3_>_5LavxClihqUwD*?<1@ zX8-xZpKzV>!rS@hOK<0&ZS%C%7R>w;9N`7)guQfF`G+xe?mw(@^AJG3pcujs{XzMa1g%T~g|<;`I$%(=L&5hu*(;gf?Z2NHkVcQ4$8*Lx%ZnS-{`LHeM z+%eGlN6vdK=~dNHpuxNmIk%1F;3b4=f60pAI9Oqu;sI{)6dKzhn?pYJ=ne? zXmZZZESY%er0e!ioUvCvWnO&xiKF)9Y>vOKmotr6CoW)RxWKSLa3ZLUrL;#$PV*qs zL`Me&InY_r4nO{fXgS92P-1l08_&p4=p>^c=b)e{ckHuhb)w0n!?V+AcE}P zc#!*$+#3zzBe^#m>RyqT|EH_{f{dqv#z;Y9sGvQ^png6dXdU;<>dT|}jNGTW8W}G3F!G+RXXJSe8ZQL3Awg@2l9nUO&Rw3eQcr6H(CglWoo5TBvp zl9vM0lwGg?i-X&{ptUcD`N8d<1FQ@i7#5gKY-VNHket9Uv4xdkLyH0j_tT6bBMbc7%|-aN|tGg_UHfNO{@$X zoElic=>xPkfB-0dUMg|d(-7xon z)^2?fW}0G)Auh4%oiNiBbEr6Iy%|i;3t^@yhEQ>s*h6N?RgZ+3rf7r01+=asnE5Bz zT`pP^7lYE?0riPXKxxlG7wqPPEDb?VC7GtkGct(4%y_^ovkK&%3&KoOBth#QKzl); zep?K>|B1l{Vg}gX`e1)vkz|^}$H*XZfQ8`#Lx~+|o?wI10d~-S)gVVnrYY=<3?iWO zM!{>^n)N3}$TLl0!eZxUVWug6K?mjFDKGQH#o@vT=1_lwZ`xAt~XYzyD2ZX@q z9)ix0if3Q|jfrkhY%rN9EX6eCH3Ngl0#Keiz&{am4m8MKMuvta3zJZAm*?K$hu->+GJg4XhXe$V>zL9z<`96&(@ zrYRY63_=`^41w~z4B&IU!Rv>hYr{DeAnVXUV&DHVg4d)!aMqhBeuH63A2e)0>#E#^ znWl6xFo=NGn1JNBU0|5Fn5Lw`!Y)C5Vgm!DToo5) znvwu9e*%Mp^+b7LrYSLSe~7FS6=s?OT00IZ2f%Txzy;33oWe{~f?)1As4@}cPtbm% z&sgdXNZD@=K0lEAGUJp7%?v*q7#OyI^nm7&Q~xr8&wK*qj~Cw|XYrl@`A^YeBFDk~ zKR|oQ5?R6X^cy&!V?Lm9O|$ci;C1I;K<9fm>reDK&p0K4g~7a$i9rao9)|rNWbS&! zm;YNHe0TlXB+oQu(f|MAaKAX1Onk^Jw(6P?crF^6Uo0jrl>nW^56wRgf)fQm^K7s( zz(xvEjwGl;%76qF@c!j(7Hig@DEpUjiUI?Jh!j+;1w1d!0AAnN z`0W4ma7NHt1PH(O+5hRYVf@4V6Bj-IKOK}-MgK8Q0qw_eln1Sgl3Ddmj%f-T)I3;t z?f%O+1+=CRWG@@k4A8!{3!N4E_D)T_<5_-I*$!h z*C6wKVd|mw2SNFuFfn@ef4VXwgUAsHL|E%S`#)WskwIiXTuf@B#Z0aSEz?!3TXcB{U63D6Brpp)<`f-Sq)VOlIH@+H%m=?{)cf& z2P1>X5(%a$i=pycxj|tlzUsOh)08NvnJ}|qe$R)BgUa+P6%12WFo*qk!N#!VfxE-c zlX6T`OkncPdK07nGENERWH1AjHK+eDPH|^o5b1#01zLy3p~kS~LAdizU1+!%FfxdM z&a4H6;X$Z5C!q9sD18k|--Xgopfsq?0qOe$<^P1zpt=hr4ocrApZuQ=s$c!&n5NW2 zxD`yxtg@1W#vwDy z#11=9_*+dZ`NIg-;{)|CXdh)2c)g9ps(Lv{f8gvlMsQrU`~lIH6WO5dP?KYt;smw- zg+6pYjwv0ng3_S1BY$O?rd;EPtYfT(ii7G%(3%?9S|0G8 z&v=KQ0&+}XcLy^vn8Vi6z|?^5LIZ`{XJ)QdY#?>a!mF}Cah~k(lSz(g$~1mZ_)D(( zFAFLMSthDN)6Xhds6U&fCaz_Jqz$d7kaJj@p8TIakAXpC7u5Zra9RRZBQ;SGs;2VE z|LIdf_kSU&0qqR{`CkI0hM8qz!IS^fL1D83NzEjvIs70shxsR_KlwksnSnuMK9ZVF zs2Vnq8fKP>aZmnF2kmE`hNPwes^%}qKZp4zhCcZ}y^w)Hq!+G6YGOH9jsC7eqw8c9tmRLvuh8fKP>R!{y<4`W~uDT1m2 z-Dd(yr{>Ujw1Lu&P#QV?&Xr}Ff}BTYg7~m?rSa}Rr-Jz4bnLhZ7RTUpVD0d;4`ZS3%~9Ap+~ zZ6za`z4{J63qW$r&?urH7Q^onSev%hM!=zXL-+%I!Wthmp$klDn z$o-mwasDh1CWbAbF&ak}h7e8#hKU>v450gVSH56onD~+zbYBR^#1-mxD?xY3g871+ z6IZC)uUrAz>#QgYUKau?Zy&HT{Di8xpt+C>tS5s7w3dVc()LtmT>0`p)5;ai3>RMr zGyGIwaaj4tnPK9o?+jBwYXV{S!zw61?$k*H?PX+On+V!B2$Kijt#g>^;;(eJl|SOy zSAGlUSoy`DbLHP^u9bhAnIz64LXOKo*1m!Er86=zgn;gl zcmV20J1|ZJ?WF_7HAroOJVOYmp8(STUk0InI|2QQP`eUj8A3qqa*+P_Ncxu(&@Twn zFU=6LfDz(;Z;05&0}>1&3z!>%0%V!MYkL?rvi%g^==M`j7P8NbPZqKl5p-7*h<#at zVM_-S!)Z>=%d<2W3eV1B;yi87$nfzcbJr{pM$T@~nt4!v6||nknW-s=g`q)+m7(F% zbBKF4G6;nHddSZZ0#nBXT9+ZiH06OHgJ}knNH_SN;|4?U{lf~3_SZxh7=D7q9w5X( z_o4l-XIlBFnPFlAsQ<^%a0%qc7s8;ut_)j1;to)8ZcrW&b^xsj+A77sAOc=%#9C^} z!N_0=VVi=}4(H$b9IK8&);BS1W&5cCI`c%IVP^+3!v+q}e4H@D&xcU^m>3!^eU)LF zf^5%68Kx;9`#YEzwnEIsZpTC->;SnPWXByD=s8W|4Ym)QTWlUUH`_dLZn9aS+Y_X5 zc5TrMWrm#(ni(b@lYzt?$3p#ypgZe8_l<(W4s?b97ZbzQQ>+XVd{(;sJk825A=}Y( z;(S(yjSb8UA)xy<-ZJy7%Bo#^Vm~9#>o?52t3Z2d5&9wbV{@%#`+4C1{|yWZmJ{cH zXPg4c17JTYn!@`;jJ%+GTfJ6(@%LQ$E!<<}k9hZ$ztY`S{yz-4zuHKWX^Iu7%$0`Z z70`NLtwxSj8KCtDjT{qSCNoTY3CiEvj4L7Q(wkQ zR)z^v4jD|G$ppHOTV$02Gx#nS(A~-xLFxM{E5n4XN`@16f#pS4IWXH_lV@PC)L;O& z%fNSxgU&NJ!G_?!m1dfi;9Q2w5s9hqwstdIKmziM|vR#)z`wf`IS9M7X)<8K)RpF-}Yhwp!T`&Dp37Y zA<8r*QyP+QIS!gmgxuw<`2*~?0|wyxXF%Zus-r;b@j+n=mQyf<%YnjGT#{+ZBW8wG z_6!WKSAxoL=sn8+*&*@G$k-5cR2sC;$Kj_u14AJLMW0 z(Nyr=OrY`7UT~O;tSSc6sP`Sh?lybK%)IKfG?U1GXU2;^;u%+d3ujvS#h-cQ-)feX zf0-F4g6=Ydxh092Vap082TS?A429sfDzhkftTC8@VM+rNgGn*xkL{p*4my_zvCLeuCHhu`~R9$;>bjlt-uE za##s!TZ6_ptQi=lOk)C_uhS6J$ql|6z0g9iL+S;SSU1PVcnf)Ej_d}|-D*q@KkqYf zygtal5V8=o=ktNmL{J)YV_=vvoz3Crk1R6_&>e--xEOwd?B-w;J6+Gj@%jZ5L&yUr zv2K{#?4_BefX+h#-OC0#Bk83wXwHSHq)Y&~e8JPVjrzg;yo>F-`ef zy?^CjW`>Ce8Nv5?TFMtP7J~XbpmqSrEueD~9xyP3aIhS(lxJi#>}FsHvFBm(G6Q%|wJ0Cc zlo!m9bqb)pj-YUxs>HAo`Hs2&(EH^;X&4kXDtt^+9<)2G1hEy7!hxd&lnyvn!TbaZ zpBK!Gs~9^Fa;;w&L|}3pOB_}z@j&DrGIN6Uma#F4;JSw`l!amA0S<-`7EXqcFU(A< zzB4nf0+k)Gv}?T|9?oQ6T?N&9+XT52Jqet5dYO-hD}@w4WAr%Is9H7X592J>xB(d%fmcJnT3AZ zHWGe13~BSa8M(XS8MR+Eg5ul}VjgI2u9J!5H3K6<2p2=crPoXx-8q?A7Cnp%CSI%z z6YUwfUW4rXdYfV87k)^2B+kGvC83RBC1~#uXm2HGtjv>;v?m^HQ0U{g@KS5^|*)xiO z&xHZqSw0!G7bnBSLYR}`r#++a>rNJi5K!3#N?V|I;0h^F9Rl4q$t=8T2`|$WH3kL| zCn<=XptCC`voUOJVFvAuVc7bWnPU~`+%VAGCYa6Y@bd*f!_O~R)28sMJYGcFQ~>3B zc88yvnHW~eXP8)Q<^r9+04gUDVF630AhSVfwTYEsf)fMJ#0qd2C|m)hSKGZySl?q(Ecp z{P}l!kR&5Rr6~`?#23pQR(@T~wDP4qD4#PM%$(c+HKSA+Sb`Oyk{#N5|hnj%)E?q{1=gP1DLqKkV z%twi@;s@EC?6mSLzr)YvP2C_}>~ zQ27HIgYV#T0Nrx{-p3BAPeJ7bH$%fE9)^ZXybKMOPJXMK#J7-P;(KP!RTmiJeq3aX z|8aoP;U^apSGP73clQNOho3wQJl(uZyxr#*87ICMWcYaS-~W&-My~GFj6B_?j9lIK znK;2^&?zR))0~2w-I|IF6SY|#f0Qzcc6+fhd|U`B_xK!szGPt7!olkBbH5$KM~0aW zzZNhugsk|twM+D8{bf_uAKOD2QPXmyBopYI6Y#leps-rZ13oA55~vRR%FM9xKR3fq z(3v6!q#S-Vg6jDLp!@A1_qpEw%rM1)k-_u<^8s6MI^c$+1LXP{RyQvI^|>{yCML5o zd`NcSoS4GO@S!DvXX2xJhKUEc8iM+SK=Gw;B>~h{f50`dod;ZY`~=;_3#!vV@c`=k zNJHc7gao4g2GyaUwgLkS=eD##f%lt$+JK<*m_GmgKOGb&8Ax{k>oYKjyf9{%h;B&ohK3-}ye|hU$PCoGf4Kx2g0x(Cj9($${X2!B zAqaebun2=OCnJL;=w4pX9nkph{RPcktsvpvUr`<=@O{6aaD=rpK<7niusHk#ja4y7 zI{Zom^)(N0P6V}8EI%`dpvUtgrXSlu?P*XM4LV~A)XxL$?SPcw!mDP3=F^xMwt&tO z0<{@I{S{O28JeIofrMAhlwg`NISkg`M7=8*oJJ4uOuWYpO0%H4S$Ne|Zp^!>L1`A$ zCj^y+p!Nu8Jj#T_;b#c*kL{)`4nMa*)|rFvGkw4;vZ_piY03d6NZTE>Z&QH*lCLy4 zAo+@efk6bkkA+!m6)0VR&dCqZV=|=RpgGkQeW3H`z0Dyw0#-2zklO@YB+O zVGAgJKRU!wf%fi#hxRo!x~nQ)HEn1k)4-BZe(Mm>IzROwbu2 zpmRVWXP=6!(vo1Ba&l3~$`{MUR-Pzw+WDfMap#F5ho7MRyYh_EuR-T;g6aVdMwxDg zN{62c20yk-mpc4>u$kfK0|o}r+JA7I2uUzaaWG)m@*Qdqq@M*^w;;Xh_h!&pGt#U0 zL2dz+@oEk~LFdY9K*LB;iy`FCb%viBteo94A8Rjz!fQS#yfhg?exGOfd3jan%F~O& zR=(C2UHM`;!_QNT!dJdnExhv7qKK6*Rtv2>wJ37si`9ZFPc4dC`C_%e%2SJ?SH4)y zzw*?gn3XS9^Q}C!D0bzG)x0ZDEs9(DVl~go+e!{Uf1GCc`SLcy&lr?+C9~>*IMWmd zRfa8~vt(b~cKG??IP}c3;|xC~85@E?XDUL%PG;3@aVF3h(xn?l4nH~m*IquM!mw55 zbM569pX)Y(-0z{n5OQ13;pgXghMzWQ_8bH0RfgoLAIVTVk{NzZh2|@m9fv^b7#c2J zwu6LO2J|dmM!0ylE58PFuKbnEu@aUhUS0>al{mX) zS{;7=k^iw>y3yh1AEYz^nv?7S*~`GN^*?mI$pdLnTNZTguFR@#(7b^-!_PnV3_nk@ zIsQDs<@oc(a@Lh6`5b><&2m_IJIHb62_cXe%gU2tjz3RGf!NF|@5nj+JlN&2a)C4R zMOQ|KDF?Z{RxWU6x+u=dFy-Z8j)@AQ2W%hoH`uOV4*BsynenHB>H%A@cw?i@3g(C( z8jVap7A$7Ic<>O@L=MmxfHTv@gH9|Hcjhuo*`8j$a)mR~MTS;~wLcDXOk`+hT6>Vo zcjbb`Oc%M?7^b{2k6PKo$}n+7bI^|$!i+yxG>83o!OZyc09(-7lSYm|PnkLXe8BGT zlY@b`TQrklrF0jf+ui!-{B|sL582NXFL4lKFaX(^>l}y+$R}+ zzMkyxllv^g&)5A9Ke;b5{CwT*@RR#0!_U|44nMhXGW>kq?C_KOF2m2)^$tI|A2R%W zUG4Cb`zgcE*X0gBxnDB;d|mADllv{h&)4}5Ke<0L`~;ny#{HGy=j(KbpWHtge!jML z_{sg3;V0<4HEu@6pP;kSxLFy0g3eOo=41q)rN+(6`17?oDC`)2zE%c>CF9T6@(w?_ zB^iIp$TI%qG-UkAWy<*TWxeB0&O)Z0pMxEK3M(@F{N?QMQ_7Oz=Sy*ipPa1>KOglU zuw|a?u$HNxWi5+-gYBZ`i#Cti8*G{Soz_0;=Ucm|`KryM`Ucyt-VQ%Sbs2s#^}DQP z(LZ3z#LKewQTqW~7Eac+kGcKVGBx(td=Gc{`Es+vPw}OED?w*za5gghd{hon%ej_; zRcP(CN1PM)Jmj4C<}$-iP=I#U(0P7E|| z2RfGzG^Pxb2dM|OhhY4toS-prsQS$gKW}p~f!Dr(+Q$!>WmaA11hoMau7KPOwwFO@ zB4}OPX-+2a`P|B&`FPOz#g`p^mT)j^b%F9hbNQfh3$%9&MBij&*rFjW*)6}6VIz1P zM@;fGLnFh*crm6aptb-=4XA8rXJG*I6$K#XgYx`)7SK8dj)|*T7^Z;sa%0R>N;~}Y z6$70)3LTGjcKGQj#x&*gWrv?X~ zpgD48(N&gWkZ=IG>-YJ{mERsmth@%6lU!va#xw;~7K7wK`ZJgj^PiwI9$;q(Yk=JF zoZ;t#$7~b#f&9QMwd#^6s81%oN>Pky%9nrtxBmG5U;G(Afi}xw&WjKA8GeG=TcB|R z4p5t=hGVuz4WzyDFdorf>4;|t`NPb#%F+)}&x7heanL+L9K)6vKhU|J3_l;pGyDYg z-}W~P{Q&j1e|~0K30hCJhB@lTS7V2ttDA#3*s0vyw}d zVdYD0@0DDtj4NMid#&VBWm@@C+jAwCD)UNDZI_jtnk*|P%DHk?bI6aE_Z@z6E#+FdiaF%R%i9h=xm0;pu4)ec0jj6C zmh!G##T@+O<$91f|H@aOvk4Z1)C;V9Iosi)FSWy0a;eI%e7W7>CnqE0 z&zGwMS8}m3{^aCj{P|MbejzT;0$UB;iD z#*W}J?4`crPR>lGoiDFD?BvX4+W9gZQl5FfcB$li$x``px#LdGo1B)O_dP8+?{ZqM zVh;N8rP<-9h$X|%Z_6EiiUl(KT*(~pgEN!i=Wg|im7p?dC3E->&Y289r8hGCe5vg4 zlk*|N&xgehKe1M!G&%dwT#Ki3JOT(5S zWDT>_k5$d$KRj-GTE1A$xYJWPcIAuZ5-UBm<5s>r&+yYzKYr!Q)#58Xm>qwL%mS%pXt?BQ z@9RB}AE~Y-^7AvA~D{ ze4f*a=8zvNngyWt3;d8~YzP9$KVX)Ihp`~c{$y194;o_HuVKg#5*ZKjyT}h{yn^qz z2K8Bfih$1s1NSk&ZM*x>cJqCPpW@(f2K6@};rvAe(l+2pA3;ckEF=!4KRGxZPL*jLX4r;t==rDvt zmOK31-YofJB{QZyheVjB9MEFevJzBAU5DCp9byltuLZMbAJku=?GS&#?&e_BVgTRG zfgG0^nhYUf*-kq>*&Tm+aD&>;f-57N9e#e(XWS_PNxyJ^E)rpyvOt|-%h&Z#yVpbP zUeGK7_v3u1-C>g*exCSOd)Y&sVe5@gkp2!R?VM0!2#M@>`1yM|!%t}bfScbX!Zc+& zbNG)0Rfa9!7DL85|1SouS#Agdi7#N5gxk{!wI_PM!_SQWHJ4wgFl?=OUjyzJg6zpq zVF(H5cl_xg3|gDVzcO;R!_P1C8GfSsDHjx`N(@_m&xZPGHq=kd;&8jNpmv3?cKG?? zU(Mx(&0#-0lo+=Dc!#j>gd#&o_ z`1wYd;itE}N}(vw$4EEFNZWk$YXBKl@HyyR(`POUir?PXXTrG zhMyk!PCH**XV@9J+2QA(X3X#w1m#Z&hAm&pp;FX8D5w7;c+fnnA7<%QznR5WLC4>qew19b8{`f_&=`*7 zD$w}bH?TU8dQo^k9@NHupbR<#UUJn|(0&bQ+w0wShm{Ndg7&&W?r8zdN4;Q{S@l5P z;pb;&ho7LaJP<#FiD6|87sJp0he6|nh5iR(hJkKkU8MTRyL+7o(v2kKQ=?+4JP++v%}AIY>>6c zFgxLL%wnsSvoTGv2Ae7Q1KNKA`*9(2t9nIpXa?RmCTG^PuXJ-6xM{~cs!%xuJ?x6k|hS~@yp%eXAanYc%STo6SPm3#>O;d z0khPP7slXz(AHhdQ9nTYFdi^Vtzs2sngZg3=CDBh7JPQpFfeTW)9mmQq<2Ac_>XTw zpfk)t^REmdFufr8FY__o2U>@m!N{;>0dvHU7ebJ}B}@-C^`Le&=xiC#Ivx$i4A42j zA3{CueH@bh;n!;}~84nIL> zoGfS#`*B=|3B2~bmj}|H1o<1AJ6_C&n7@EI{KqaKM4uF-9y$C$@$q6YXdOAokC8vt z3o%UrLxL39N2CR9$n#kEQt3fyd++wmjJE@N>2h(-c-{9~xvgD9*uV-G;>J zj^@B0pmB0g+iHRk(-aUN)Q*JNzvFq~$`|*+YPW#aytE53OoURLztB|HVP}fX-YkeFU1P6JAx##4zPEv*@Z{p!rf}u~pteOjCq- zn5GEwFirXI%yjWjKJ&_7=`1UM#Ivsa7S6Wvi$D9yzttQo|1vXy*8i>o<;Bn!)t5c~ zR9`;9&9D``4)A^LCeYc(9^4EeznGa;iGt3*W@cQ4Xw!?Xat6)kaWQQ9)y%juiXD7L z;ic(H3=?G>7$*MWX86em8e?E+xb%X_VJGM;+-i1)iCui4^uV!dvazF}jzy4%% z_-V=dWBV^Aho3)D+Jus;WQCZfG_Zi`9nhUplB+=Z{Uwvb&lk*`TAH;9lY$uNCv+`Rw2bA7GW`gJ1nT1!a6$GVm(N$4`OjC-uvAY#C zej@^Rs|bo)Pa?S$G~Wm@S7g<6L8d7sVNNSWnHaWAQ(~A1x_9#jx8o*Ip1;-vz6 @@ -678,199 +678,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -

    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 z∨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 z

    1|2`ZajGIOqc>F)6Ja8%|Y*pA3!7 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%0A+5s71~F(_h4FmA}$CR{n_RT=^}WYvmVz?v;P5 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

    +zfBHKqD34 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ⅆ)m7 z9#YQeC^InBbU^H*c6%Mi}EHsq-e&b+X7yxY^_?gpfUD`#Fv2r>3}|=>n$JP0bOIxo z!vIOAJlYHlJD~av5bi)KCqQeVP|FEWI&slvVAya3V($}#KGb>>+_*wioFIKQ+6)Xa z#~}J1AoOiOxEEY%Y=AftkuJArGcd56fan9Q!2^30X+0Si^9_u232J}6(Pm)aI0Mml z1Tw4u%C`jS=@2e#+Nq36O49!FKtWnk!d z1JQp05l^`DBa;i3oCk3aDBeqS85lU;LCn)Zn1?7=K%HJl0Sbw9=(@l)x(p0|p!z`O zfK5b-_Y_#M0#$!Umw|!dJ;WYD`4CjD^XM@!yn&jpf^Zi+9fOC4AxRiqj@aliFg$>o ze+H)B2eDq1c@4aH29M+B=rJ(Ve1y0s15ux%uH*7x;*obitm6W?Yl$8M!!{sSoamG4&+WBeFg@J9}xZ6)3>X+nenZSdS~s--@B9X{0uzt?0`%z( zaDE9fU|?wZ3o#GxxI5bTbO>g11Dwv57%(twftp8Xy(TCgt{5;dtbwXW>IcEoG1Cu3 zihM@CGA^O3GfPti3_%>k7yD;BimpdugpzyPQ{am9#%;RZj%ye}X(gD{f2xj^MJ%pF{y)gmxX4BSH? zcgYwtFti9j%xOUMYZ2{o&;mE)g{)l6e-I6F*g957J_ETc$C!a(izviA9YlVAfPG$< z`3)nF`~ya`iCs|o-eSzaFhd+-4xw@#^N{ARklfLM;SNxEo0u~&h!{fjfzF?XhBrbVycluehHrrYD}u}? zmLTZ|nFp3aE}!5V5HR(F^S?O*g8Ytc1FeE_L6H|YRSTHa| z5UCy%jxH7q3;|H}1&HLx&M094oNu11-?S)VIZgf#HNP zL?6<850ZTn80|ZdJKtC^Fnln9=p)uXQ241>GB6mJLG&T5dqCI+Z`iqj({~7x`Ur@3 z8IadSbCtl_AD~HoNP`+=Pmd)7LjaNXfXekfNb2G1I|!{y0k?fImZgB(Z9G;C3|lNA z{zRJR!oSW1RHtJa2kv*pSTQiT=tI`cAdM%Z^t(XSC+cz`NPvOjYmOBI!v$-IdqDLC zSO$H3A7wo%q#bg_ih;6*qbVA66iP z;>X5@fnkdW#9XBL5S0ELc%B~7oddV$OKcbz96TZVK;a1W2RuLJz)D?c`+A8D1A`A# z{SU;rGM4p2%oQx4#yQdgA}-M4Jk%vcVCz8n=Z_5o!yA8yJCNGPDDLM1brxX#QP7|o zXn6*BBLhqjY#g{e^RZ=Mm=g#w4{4kk?k_ITUR_9p!h#X3uf>*uVMY){A5yyzp^q7~ zR|QogXx?RyEdxVO2t;21qJ2tWJZAxD01c&`18PZegh5@HX0el!4) zPC;{j;B@W-+SLkiCulq;#*Tqu2~^(&M174h4$B29032MnL2f{->2TrZV*Y?KHVXUy)M+wpn9&i6*$G}hn)sIvU!25@wW<5BrAVo7M zAAzA04~^<$1~eao+P6#W85n9(A?6{K z%b4bY!x1_02OxJ0UARH}x)IiaLk?st`3I*@%Hx@7=ZzSXbH&zxfLdz_e0ig7(~ z{sQM4c=?`B=l8b2pmT@+Sq*i(b#402>H04_Xp{T?wwQOtq^l# z5b3`FJwCwYK><8UK@~J;#SpRxcpqYj8v}z$JH$Ms@p_DXR^Va_u~7?T9s_8g1C+Qy z>12)@1H*z&h&iCUwZJjp!Z(2lZ+>A0B92Qpp^3@cP1_8^sWSlc_GZWeOu2IQ_7F9wDK+aTdiXq+0P zzQ>D!foBIqJ;;8jzXIUX_dY!G0pN7w!VQw&^E;P{S6|n*k*V2n*y+6K@6v7N|auJD~1_hd*-v6(pbH4O*7~u?K0L zJ)*t^H5Q@OtqV7*J3;m>@n&E+1GNv-jsV+-V_hsr{S|Kph7VBnNb9!Y>l!N%%NoJu z9*YkHL&R~2`(XFZBCn@{Rc0W2LH3*YFfb&1fT-_5)I%2tl*trW=70csx7x)*rHHalK4u%#K~HGDC<$O- z*pLd*hqMnEOTK8q*cT3Jk8BBGU@%C7=m&)pD4imYuY=Mt7wR}DVz>sZ7*cNh31DFG zftrJK4j7jF<$#=$L6rfbQihrbD(8Iy85nY)=7Gjlz)~KV`v_2a#b8B{c2G|s1H+1R zh`*5f02j= zfk7h^Vjrd91v0NDh=JiwF2p?i{UNlC7f4IAu7WP4g{H?dK@1EQc@XoyAj)l|^az@h zL`}$`^urR&!0;s>qK{VVqe1B-CzyfZNj1bCr2P+A`_JHUb9h(l`VCOMyC#@{p`Zq$ z{|=(wT|l7T4PoYyUx3*kf+RdpJg|f?Fx;38G4}>5M4Z6|_kJ-h<~J-nC};U0$}Ld3 z3<+UiNLUClPXKoDI9hv`i}?s2B2&3=gWS;*!oXm$0isU>cEL7*bHlip3%Ic@tOmK` zP6z{oz#fQsq~s5gPzHt>Wa#q=WnfU)3$YJgzoH#O3g5#7?OA}_*%HdYU;)*)12J9< z&-bA6jClbYN*M+UuRWol^{Np2;PXdl;S6f_fX_ihlvp^+FP<<4hA)R9=E2Y9!%}{M z`d0|!An66<9-lA<2Av}i{YdAtU@1>?5K#~6)*$mB#)0Rp=7cdYR6xx$fHh2A_%1Nx z@Bj1g^T=Od#!*j$+8QUX#>sDv{x{DG?PK#Y&T)uXK^ zf*gUJnu{U;FlxB-7Y2DLLAaAjlg{+Ki23=B(dL(H9nNJj+b zgIq}3e*sFbCJ_t_94{gEAhnMX;Q?N@3<*J4g8@`;=R`0tw7i1olR=cvSj#`~9w*e{ zD{#sH*}o-%fg$2G#2ixc^_vI=h6hl6#MXPD^r8~Uz!32UVjt3ebhv*(!{iX(LhA95 zNCt)-Q1$TnXQc5HP+u8YeM=+*L&;l+`AFxRAk1Hb=z)Rit1XcX3=Hoe>fa#BYXb2+ zg%#TjAFlmYp!!NCih-fvH^d&K{qk7j3)C$`jxJC<#Y8bMT=)mkkJMg38FvMhC9qL- zPy~bYO^IS)P+K2txD` z8xNp-9umXA@InZpPX(4feNfxMpfxb?o(iaUVu3XD<-!eKKQbqVfnkXW#2j+YDY_HG zz#w4?(T|j`(Av4E=U##GyGSep!xl4$J|uhL;f1{T8x&41u?!3{mJs#C+5@UDYhoD~ z7C`m=ftA--&V>Mtx5C2;wA3z!n~UiR^0bHxH>g}V6U)FL;s|jEE!(jsaSRMRz7Tsz zZO4Mbw=^|whp1H&Dt{uPLFW&?ro1JL~C27K#jdg2)vbV4BRLh8T3(;cWC z$c0FJpz;SiE`21Pf#F3cM4tfcU~jZ`AQ$rkc0@A^vQF(!JOjglScpDi^EW7+nIteU zSj0i}T|vyV;xC_=Ye?F+3W}#W2@DJsxe$9uO=lqecM=#Fa`GVhvF`)%Kx&=A7x94$ zVvs*&5*Zj496 zp!{_uk%2*_7-Ak$`iAdg1I?d8cCkUmU0ISC7c#2#YXVW4;^ z0oe!D2dXbX?#39uK{+1+QO$zv-IBz>um`Fi-p?X1jtPp-H%SZ(SD^YVVD%Eg^}`QH zPPZY+3=BIOA>l%5y9qQuH6`&&mJ|jC zk8X&4@b$Dld=v1NXUw2AwiD2cGw?dnkQ4@nh#rV}NbBJ6o(shE1+y0qav~^wEJMK?kfWjNGGbC07`9A<*oQQK zhRBCsc;sRE5R@-^QW+Q~Ooyl^HQj*n#hFwF289_A{qS?9FxH3OKx8HtZt#3POBw@1 z#Vm+E9z=ii0W-G!99+ybpnWOKXcHcw@lKyK28JFkNV^wl9V71ZXSkS;fX{qItEfXj z0gECASvS8Xje)^nKE$0!?EuXEOkAKjG^G7Z(01^fGzNw%Q2i4S{%k;6AEW_}ROpN$ zC@MkasY*Hn!=1$td$FzmhA#_-o*NK?r2YuP9=zo_c&#FOfeso#Sb=r>0=Rv=C!K-8 zVmZWJ7ZCPf8E-)uFae4On{~nc=?UgKjx}{wf{gvRgj)CD1WTUU|`s@3F2O)d4G7jkqgwBgeXU7 z1Vtir-r-FK149NrdrJ`ZBA+1vG6H13P9_6G1yny$djiWknFl=bD63*1{?5r{V3@H5 z;(k#0gUm&qX9cZ)MRz_ZUoXjIVCaGBvjA%X?-xS&8=e3W-UjKrgQSnx`E5{pL?nxW zp#o}O4I*7lV8XefssOaK1|`QrqZ>Sqmy*T6uw*;L-_%_9wkL~$!DbJ{Ttev=6drG~ z7#L!p>Ot*za8RO`k2xsCBY1vXB^xvz46%n${s*O#kZcBqh651wEr|X*M*kl&C5IRR z0EHPy|CDS7h9d_d`ax^7pzg$y&U!G?87N&{$!1`PIRw#<6fW?71bhq{I&Z>~!@!Vn z9HJiQz5@836>zr$94?`>pQs&3>@5$`d9;z4sn#% zPml)`KuagV-UGQ`C69q&3e^3?mLs6}j>%(SXn^V?ly5-ldyv!B0pJ(zJ7!pX-2MR})0tSW$cOdp5 zl^5{(1-=~-T3(nGFfiPJsz*wfXzp8qQjmh|$thr9nDK}>dqC>v6fiKHc>+-n>c4^G z3u!(LUL--=+h;)TdJ0ibXgxM4oc|OsFr0v@M>?km)gNg4%RufjDP&-{0o6y$c{nMB z3=9W|R1eDcQwkXvRzTHb%QwjF1oUZAoVGt z(Fv+gLE-VFh=JkA3yA-SN#7#H3=B8eA>}4K-J!J;kf#Aa?gXVLpJE0Ehc6I&knV3m z8*h$4Ssx10*HX;D;PM@!?+&7zMoovz8yJx@6sUgOQ_R5d;Ri$?ygc*Z!)zy%@W`W# zAA5j~*+Vo?LGA~c$5X<HalF9xgf)!@g7scz|ip*Vjj}C5|(k+ z0*rM&pm?8C!oaYF4H6GX{b#J}sX%jT$SbKp`tOu5Fzg`Heo(x~lrk`A{DZhZ1knzL zmy2A?1{miof$H6mQU(SW#=qeCE2QxcjPwdxj>yFX+PMIqh(f7%=ae!q7%)N1xdRPX z^!keF2qIO0lz{wwrIdleLUjXktbb16Y-GxPNa;1VS+G3KhpRi>b%_& zlw*3p{ZXGX1_m8bh(4U{9F+PM;dYR_d&(FX3?v}>NnIxk(toCmf#D5QKhpRB(mGp| zb6nPdrU1dsQ)qhSDQ93Xk%HKdH1C5Yy<)D{1f>_Bat4MKQ2j{h9WDQ(uB!sMyQQ3g zK|z{4dqMh-lru0)AyYrd-7FOh3=u4le1=q?qq!S(eK@GRu&H2RxB#`c1d%`Rk8>~| z!8)J;az{@E1H%;!NH`$%6XEq17x+LaSdRjf+9B!nNCgAKmw1Ri0pxK#ct?bJ2l}`U zD4+hRU|?WLf#@TyKZtq0HAtUJB?H5i42U^M>jQA)cjE5r1-WldB?ALbCd6EL`HnWP z176>Qwp|D@&vW$}NZ*r628J_Gb9xZ-7`W~eSpXV-bKwTHOdC+QtAH9F4UCYDx(FI& zBoGuHAyo_v5=9VqYarW)f8EX=@K_+m2sR|1LG}2WDh7rZ#Sn9eX{X$&Vqo|JRgYAU zBld~-AnxD=r9Yl(1_q51i1|q8O(LJi2iie}aUP#bH3P$jQiwjJ^&TkYC3qVjY@q{m z+_k2ffnh-zL?6<)HqQLR44SS&9U}yfw;!oyV7O5ZF$do{3*bG?h>jv?un56}1T%O% zSfqx5fu|N?9w_~QrASP7p!kZZVPFubhnNFO7hrR6>{|eZe@_hqLjohD96}mjgzsB` zjFEy%Y*5nmR`Trbz2NbLtrwWT5(R zKBRUiO8N%3KamC?K>ibHU|?ui2Js&$`g|G~7;2V7^dXhEX!fC=PXe;9rGbHA1&R7V z`Cv~21H%=lKBWE@W;++uS%ZZsq?rqDul{LZV9;3saWAR*LGHF`WMF84>L+&o8>FwK zk%7VBFT`GA+gqUWV@)Fi!xgA~Nay6@IzJ9{;xxwjaiH}2rICT5WEI4I2SmH=12eYq z{n?yzxVXTLwhzomBmAHsHvYj_72p%Zz*r&2KHIj7fk8lkN1g#RrUDv=@e%q8 z?hC>0Zsu4FZES+}>R}GPV%z}*vIpdUP@ZuRhL{B__c+SoW?}YIL5mU*-CS^;0rEen zZLcB%F$Z?u0*61`931U7&|o%ZKMgeR3A!6DMgn5q3~2esp-jL$X3#k-h_MP#$Bzrk z2rS6GpuI#>q#)+Q=2ba9Geas$v~q_FTxlROG-%f|f(O}e3|h1MLJndcY~KaPakzQd z{a=FaIBc$koM!=AD`O%LF&{Qh#?cHnAMZKAK4=Z>mQ4`z;rY!UZazi%4P<@`C@pS=m=9a`!NE4X-G3Z9K#Ju&TJX3Qk>v%*d>1wbhA&X_WuWyDM>E`fLi;j7Cv84J zUxx7;Lsf!Uxtr;BbbUk3Ias=j0JrK7-Dzy|M#hKD>P9hMSMt{vo1#4q<0t zaM%qo57s{CxDOo&b>Sm)PRr>XR+(3=C`bLClBO$+O|+V?B2tydNV0rK=Ar z9>DYaEF7Tw0U+kNfGT_j1_q93gn8JS6`=kNB)@}>bw%yeg4!0K{2s%>z##AlVlMQW zcLok^c(`I|=fxloeSj+sv}PZ;Kd^;^fkEUc#M~Xw{u9SzgnO|rdjc&`!{UEXxyi!G zz_8^N#JmMC^LE3{+kkq$6lizR2K3{l!0z(lWMHUx12G3S&%x0SHwQ<&{J?sz6)2wP za56BQc?U5M){f_JCuH6goaVjZWMHuP05K2lK6XOp;dY-57XyRCM~Hb7VBvP06;hvq z&Z)*45#W8y8&C(PAp7=vxEL53K0(Zbt-Iiu4ma-r#(B8l3#JYr&ck)#2A>aig^Ph< z&u54^u=7kglHukMD33q``Z$Xtu=yt33=C(!K+K1oL&hNtHy?k$6nY~bMgs|)8zKAK zr*Jbc=zN2i&jGy%hT|~2JxZYdm2kjZs_eoIDyKnb)7)`^+;hJIR!(1r*E595B?-_z zR9xjWD8KvgFfe3zK+Hb_Yd18*%_rdQ7g*a3pnR}|hk>Cd5MnN4 zfq4!yXyGhckq7E`e&JzY(20bY2Rmf#RPEaT^FmR;9?K?ogz8&m5@&~Zk7lTz8Vm!E9OyOf-I8p$y7q+jI zLmzH0YW)g20s+zC;X=$XL0SZ$bhd?$fniM{#5`F4o8vvcdSwa0dIeMu@bEJ*xD-Lm zg^eF`EQgzmEnZs~K_^wfyOof925Q@GD1n#*FBkLS<`BwfkaHez6cAwl^D{8)sDPM% z0Xn|Mp$|8oKt9vKx{nZ4KJW-IFc{QB%!RiTli}uKZ)cIvPRtQtV7SlT3_WcS^UnxF5+KKAc>P8wKdlhNnsXrLfWlozkb&XP zY>4>*uyF!JJM;tA^_7tE7tl>AADFO=zktfO7C{DvFLNR0!tQC{5GD{VpffTtLIE;9 za7B=Tp=BP#Ja{-?hld09cqB5MLE&H`#K7P%A7XxhI3#>Hy5Z&%NPjo@an(;fLJSNR z+aTutfZo5!@f&V#1A%?xd-!jP^CsdK{GK{(odU$C~*K>2WrFatx)TZnl9F!Mgc%S#;Q-9a}GG<1Z~ z;su%iMVNsh=N-g+*!onC^>FjCrw`EDY}E9IbpQ!ueuxMI!xO0a8lb){s6T<|=M$=D z9x&snP(bEy5dqyN1aUt+eTNfpKauGhWWI76}H17lsgXVdtxI7{kpa(4PU{2;YFK zUjRyvDH03}EyfV@U0~(edw6=pI`0V@U*KYTf_Y307p4%XBnPFJGZG98GA%uDJoo09Xj9q{LT_rARX{ ztVw{FzXWDJqTNW~+!*j7^EeNV0lEK(Gy}tjWQh4Cu=)zoemp?H{Su&l9X|J~$S^Q8 z6hO>}kH50R!v}kPMdbJ^$o(xcp!G%&^Wo#)=i%)>?B46aG(fcKD=LpXs=>7 zpU8dLL0^`!) z{y)gPnGo|#VCnEZJe&!Y^A*?@e1HzBuK;CS9Qg}0&XFR=!0=%<#NH5CeYhBIFQNPk zng_>n?gAIqTn)1CiW~#OmAMf6UclO)-U#~$jB{|X^2l$%tOr2$VTyvrlU(E(7;V*-ySw#YLuWUPefp8)I6pNH!|fOj7^7xNod9{B^f>R(X) zdLqxjFk?NM`OVZbA2erhVk?^Y=F~HPi2?(|kKGXSr@+R^Uc<*j36-C3K<#nNn1kd~ zQ24MYGB9);g_y4cGk-qZd_w87gB@#1M3|qV$iSd*4q`q$euLrWQyjlX6d4#~u0qV; z0Lx$OaPzU3Z;-XP@R2KA>lZ-zNkxf)A?G^8{5P=n+jaQ-Jf3r0m~+5$St!k97j7;O z&;%EnASAdLK=w^hVqkc217hC^n0@8M*ax}<0ON>M@bxqpqEP!l?JyQ)1_p;)5c^=~ z|8NMy?Za~Z6zJrq3CP2_;P#V`G6TaFs5vuW?taY#8wbD<51>VYs2yvN(?IT;qs+ii zvI(-z2euxbV>R47Z2RVGKuHlEsnGWQ6J-VlgF6uat%0=<%Hie^>d*Rc;OWnT+Qlv^ z3=B5UAm+o@+&n_(WzGTJ5rQkfgZd+1 zR6ysoLCiOS=NE+eMD>qBrzk97#MwUvwF_cY85pj7h1g@042o3-29EvE1+oOsEfMj@ z)-C{*S6fsW7xl5q;#B$7rn@cDiCa@AnhiB9n7}lgg+zsz%PKTRcfOR?& zbeK*7+9VMJ0|ThNB%{v2upk{`o&&5M5f3+yP&x!H5W{kAD_0Dj<6ObvG)0|(AtMK3 z?+w`asx;i*4+PpT62d(4A8@6AQ2c&TXJB~H3^9L!7$n{~5bZJi;~vZ>1aYNH9}NZu zjad+L;o*gt_aJl+yblZLOm{r($Tb=a3>@eX#vd90>bR@&U*k*jfe9OciLIXpANUg9%hW>^v}}c^a(cA}D^Q zXfiN-*a5M33M6_MIE>-`M6nmVf5ZnZ|6yL622NLRG#MCtc0$aBuYW`2YeMZ14i-G^ zOpv>Mv=|tk?1PvO+yBGy8lG-(rYkORGXZrC2`CIfUI&@GM2mr;<^aT8_TvT3_1{1Pi#W&WK<4x4Ffj0(fS3XdDP%KP*R=fx+hr#C@>)%{Ugr%_r24 z0pB-|uUtQ)%fMi917beB-d+wjAG6#bvfftFV_*n?n#%zjSB-|7OQ=4Y04mpUxf_%& zTJ#tgemsP@TLc#Fli}tQN*AD+3Y-geK;}QuV_;bG1Y$nCeOeATpHRL8)#G^crHeiT z!;%jW^F?6oenj|SE$6{!PNTMexj^^Dq6$IgbC>8dFhqPrGoK&seu~X!F<@Ze`2;Z^ zwoe;ry%F)|C!o*OL)@QYz`(ErYCb%C5bOSlH$MZr`9};G7;-*C+;0I3e{BN&UnbB+ zu<)J|xclpZX*{^zR54^=aQOl;AMXD9@ctpi?r$+h_2!^4zXRCTn}hO|jS&OGkCl-ADcs%2{bu~*=6Kx= z+J895h=Jh>wEqh#-$CsGj`Q$-8hl?NNF3yzGe!&yIeUJB`$Zlwf3AnC-$0<2spnAtf!6HWB{zAKEiwjG-0Hr4pa|VV8sJR8Oa>O5Q?gA#P0|^PB zZavCC0{E=w1x#pSUog~x(l02z^q4a+s*ba9O zp7R8m-!Ovu0$7H4LGfK;!N8Cb0WlYpjzIqBXos5%KVKDeZaU!9>^w+e0cn0TL%g{O!oqE z#sj6J7)u5Qm8lSOGGOsx4L4^2fqB^l96a(1Fz02tm~ya+gIoz&KYqoMfk9+G#NGyF zh&YEZ++O_k2(yYDwweS~j_6o1FxVV|mwbAr-6p7p_?d~n5zfkEIH#60-=+tUdDW6i$c^N=x?%YpNO ziZug60Q7#v7clqXD`!9#eIeJMT&UwSAR|ESl^SaXh6hk{;q8fFxI1yQD-WQzQn9YX z1I5=JYX%0MlaO$Loj=5(4L2XNd<3m2CE#w5xh6IY3=^Q{E`XH_kKy?cfBFI)?Tb3p z15OGcb9-zU7xj>SZp%X~lxTe<~>^Aj7;Ia?5O6=3bQ({OWfgmVB! zIAiOdf#$1RY#A6F{y@y10zJQmqaJSl1w8HRJD_}l-o6Iit#JWe5E2~VcFPu928JFE z$h|D^_21@j`v{HW>9FA$#{-3zh#dpN5@Cq>Dlq@N{s*gv2=(`QnDC50gYsvI9Rov# zIK+I|zI%?{aP!gfJJN}q;2mLz%nu3@P`Wu{$G~s{YTg-9kSGHKM>pI&{OOT-4KJ>A zBV*6N;G+w1Cw%|aWVpG+ryDF=&q3i_W6!|Q0lkk8b{{c^I~nHV=y%_-XJGhH4spK$ zY`*0;^xy&)K0@gPbRY-bbph67?jV~I599d%z=bEydQh}H!R$-)MpW>6)%>07-TMsGXp~h)Li&}!O3uQ z@s}T<%h0jZXP|TvzjYn&MvQlRGXz|x60 z+&oPGgVz$HROlE7$%EX<;=;fnu>j&u*gf1F_kY2{71f>a!>2*#-GVn@B3uofR|#=p zU^oFa5AN^HILss9Z&1Hzi3!q*uBym#c=ab`$Z)@@<`nx@VbmAE({Dyp!y>~ zi3zkHAsntB)qh9_Mq#OL!1ID8t_%!c7D2)bZk{j>^D>b0KO`TPxH2#lEQ9EWm;3L3 z!onLzKE%=f_G9 z&~pXie)#wpJKTKKbcRwdK<>xrac5xI1Jw^tSI>XI(gmu1Ea~cuI|D<^Er|U$VDsaL z;ra>H_a=neg*qM#3=1AY%-;bkf6C$J6PkYn^#SqDKlXSqFqHg&m@flcho=rVpU^x_ z1-^B7pnUPhgMncSGvqv2*tyCax4*-}7qk2%d>jF|J?`Skz~BJAcM#rA?1!66e7?ZZ zPF&*2z#sy>$4>+nF3xcC3B_v;zIGNUTv)sq78E@pUx*JvQuSCH|6C0+~+OQ84p!PgI$!_7H>V_(7qMo>Qo zW%La)9{$CPfkDO=;x2eQ$Qy1R<~cb`2^jYYfWpnjn}NZ@6=Dv2TvZru4yONz7=HxS z$5Xr+7+Rp_eu33j$G^hj14nwoGC2mVkDqumFle|z+_?nS|LumGhqZkPIg1;)9SLdw zg5uT0hk+r+17aS$9p(==4?bQGzC#AK-kA$_nm@!GQ25pOFfeR^n)3se?&RU-;7@lr z+Fc;`UGZUH*y96nAAEhEFv47nVQFRu(4H&!WFW|epm>n+Wnh>Cz0VJRUfJs}1ma-^ zw&p9i9g^eAz)%6b=kE$E9#+H6!x0bQGn&v+F(e)D@nv9G5DD>T2h6-|xOwRPqZ!!x zN1%F(#gBpEK@`Lscz$q)n-c&(h8MI4GywJdPEdHe_%SftSON(a__`luxPHuY-I!BA zXDXnCH^?15ehdsf(EAJ%VD$?#+#J;NjG3o^4%dWt_dx0Cj2{ETns|sm;QJ~+e}*^= z+xe$x>!d*U7NM3tknvO%e+CAf1cbo!7YNLIg6@~UfNS0pxbPc_f7!$e$s>3=A`BA?7K-(hWb{ zyaibN*#X)Sf$UGvy!afDyBZ+oz{g3s;pU*MX9L-LCYXWY1oYlY(3zH?l)>@-11!F< zh3^vd@CB8_A|VV6Elm)6w}1=*^_O?U%|Y#NgQlF3I@}<4goH3K*tA0Q!_WQdhU>@X z4j*)PfaVG3gfK9ifSLmzM+k?TgHf-5oC?x+C4_;&qzz&}T%SIEeV}~-JfRE>2cY`k z^Y!d-eG`b<7jT0EbS(kieE}t*3=9PmAnp@@rH}3JVev$uKFPpZpMdMXPzDB#Nf2}4 z?a^+yxtQfN5%c_@a!@4m`ZKLOEy1J>THN9e~kuM1jI!^LcaR>y(H$sy?gG*8bG z$-waBB*fea#t?B1XSlf&7*p`9)q$iBRxf^5Zvj>xK~`TOF6JIhP#KWI$8hiN?ToN<+Cl3k4`e~?g6-{RU=CqKz1V013eEObayT2c-Wp?h&`~o*f{#(VTV||1!`NM z4i$im9mS&|Fd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71b2;?NN!j6!^>MH2*1q?7+hnE4YUV#Hb!!*I>XW-5+;Ou)biZ zhS>}Z3`QU}KSUn3jv1!EKnfx*A_<{GQaQl-L0XV8beTEB9|?#$m>f(TMo*BzFaH5* zpT7shozQJJ3~%hX!1lt-34_W*x1BI_Lir+4i|0c5UQoWGC&WCM|3KGwfYhRki!v}U zfQ}GG7YAJ@0>bFxAoH+^gRV)&W{x=n0|V%YK6GTWp(1_sdD^-HD@ z|9eBlg*g})KyoTCAnMhj;>#Hr7*wEaWC|8;9v+Z%Rx<$-4 zgpT%w*aKR}4pOhj#J~W$YPuRKE)5c7U|@K`$iM&!k7-bGkQ+f#Qj8GyqKk_$GBAL` z1YJCok%57mfq`KQ)Et;QyO6B7di=*nDlf7P-wFo68{4{AOr{ebKh@q>g5jGrYBi6>ZiYeD5fX$_>_ z4$23G5r~hjo&iTbGT=C7lVJA3$axW0<|Ld}ssBH?VwgiwTnc_DDh884J}6 z+NTUU8Wvq#nUR43l$M``K>V2wRezF^fdPC(Dh~H5K;7q2%?NfUvF=@C25}ecoEcXx z1_oXR28NlzsP<Bx2hXxbFgU1ke!^F|eyUff0-aF#r3-LE}n<&F9XgYx&Z@|Ft1l1e~0_HAc zVPF82W$5;uWr5^Rba8H01_scPSLot;&~%S3Zo$gHV1OkYF0*0}&wMuQ@e<31-QEy3 zNcuvz*Nu&V0aVtZi@QU`apfOadRzcam&B&WLVrlO!^&gOzHUhd1_pHTy$q0Y16|yU zk%2*;fq}sTEq>AKGg$h7r5kkqo*;-jVB!G*5I%IEFd{{mL?STW;ndstv43PYdE)L3fptBv&#X zz`zg;Ew`ee>gAXi7}T(cgZ5p6(gV8sHB1Z)An{edA?Xv={y4_KzyL}^=;EMrEC+L0*uN24-oV-i=;r?wVqgHZ)%QZfAGBW|6a}ERHYhyN#oeLx7rHp8zX56wql*VH zK++pFaZtW9gtpgU;r5>a(w@gA4r;@ri~nSRwAZkSgWNOk5hOjp!fP%XAH99R%*4O| z>SLXQh8s*DI-gLvNosp`N+mP6Zie+oVErr+K^Cw$A$hqNh(A+7k;edOD}wtH(Dry1 zR9u$<65kAH;*%K|7(nGFy7(d-;vX3xUZU-j@{; z&gkVox_(|ph`P*BNIHPEFVNd*==#nxGBAMJsp#T+p#5`n@uiFm3|gRmGBh4x zNP7ZZeH*m@hc4dD2x%V>iU;&|IC}Vi>L*Y?pCJb_-UQ1xFxsOWGL8XDZZH~_uVMY) zmC$eloo54513G^KG$!%v7bHAk?VaVUkn$K^d?r+Ua~MQDEFXf(5>WpNT^!UF!6ptW z>p|jy;Sh6R;rkRiE`l!pkP%Y;p_>otb73<_3feE-2yJ(Q%59K;(bE??AHDq3iiEfi zmdL z17utZU3?`DanL!i zb3o#t_V|fxNc@8G3CLez(0%~AIOyI0Q2mcC4my7m6p!fQ$D!>zbaBx6u%LW|E)Hs^ zfa+>=@f>KmhAv*n2yYzdq3N7Tp|v9Pa0Xic3^M;!6({ zSquyewb1@Ix;UsD0Qn1Dd=)g@(8X8d5MPf&oD14sLN^C=?=vV}p^JmkCpPzh$|Y># zpz~X?iQmN$4#v>=0d#vo=Ye8#&tqu65MBLy9O7r7;ya+>4<3tVU|`q{4IgxI(0P|2 z_oIu0!VQ}^EZw532aOkl#$D{HA@L3ykE(|9L30qGq;L!B4s`KHQ1Ka1^`J5fq<#mK z4>J!W2EwrR4vY>+g!CIgav%&7htVvHA@Uc{#@QrlAnt|DJLp6Cptt~;gRcGontls3 z_2~LRW`HoL>;%y;_q>@0agWbJ2z>@hhu1>f1FP?Ea6!fe(8V7@#~q5G>S5spI-d_z z2ce6D&eaC_X%bXDsJsQa=N^;~D_3KfA>|Tp9mE~rJ`!lm2+9Ywg+co2pzRLO7%qrk z2yM4x69unUHxepz1+w29SB^>XV`22#eo2jF5gPYXc;_Vd>;O3uL?r z)~*AY1;P$cb)Yr|h>xz%gq48-G^fS-9%3F$Ke{^5JO!wp4}qEotDiw*4xqLZx;UsU z1!{vRwL#1U#VN>sU+8!fx_BhCJ>>;e4@#3D^{W{n^K}L5A?ASkVj%HOC?7ON0^%Qn z@?rV@3X~6OcY)*?oFMu^Z7L8SUH%(XJuDu0HbB&a$|#U}boYir=g02chp2@d$B|E8@f5iq4AF{&czC;@6pA7F+<$r@(|*FSU!CR z9gjj6UjQ9nKov18BShlt#`&#X)mu zAgR|-KFocf`U#}2=sCpQu<|B?nE||}16@3l88SXA3LV!1jkkf!T?VZ`Kx||TOaHL> zePlTh8z#RZ4u8MdKo3ATvQ2mXBfO$Qh{l0`U-YIH2oVvY`F|r7@5` zTj+Qox;Uu+3@WqG#X)%$n>c7b0Tkxw>hq!f2Xt}J{W73B8eP1I0WvRuP27WlfdO5- z7`l!FUA&F~GS7)F4w}CJmA&ZVTXERi#lXPeiN)S-2FQ3mx;Y?mP~CP9?ujnm z1ReiF7YEg=pfVC&ydBz)S`LjzSh@zS9Rl@p(8WP+!6ptWTd|3Q#w$Q|9lAN7c>_?s zK^F&w52%bo7nfsXU@&B0U_ci)gq8#7;-GtHKx5xh&~af{xg3C2-v&eFVdZiuln*LH zK2=zK7`IA~r3RCl0@D?`^Upo^<8LFO&c#oM9nZglZ0%nS^mu@Q9f zQ!Eg1KIps-D1Jcx0gao0>TY!LOz1cYx_B^j9P2C898jEr%;|-WOItpLlzX5!28p{v z`Jj3g#0TAb=fuFkFa;_On_mL;i$QHKba7Cg1hr-FOk@X-qr=*TuyTlD5=6YG5Lym_ zECHRz4yDn@4`KRXv_~04ANssj1QTT41G+e9J_@uhL1z=hUi5PDPA8;2fJ^=dR34`8 z#By%1zZ#(C1wiQ|P<1fzDNu3raq1*!J&7J4&)6XIHt6DW*%%l=Z7p>1Eo_kSXLNB# zR!I6o7q`M8o(x@Ig04OWhxjID$h^X5XnKIvfA^s6Ds*#Xm>C#AV}+d1`V~|LfTFq? zT5h3>gVH5PJ$k)czzms3fz@{)GeB4wy3P(Jj?V9Bgrpbrcy~DqNe@S$w9I)3-=Ymd z!`gMQboXR0M0~+M2pw?%LgP|@25QcMc8I!^P6&-lea>`<{EQh8I%gJy#-*O+2t@t> zl&&}i;iIoR1dUUI$_sRHJ7|4{E)JR(#HJoJE)E(iLst)4-vFvNyrJ;`%4?u_*#(VX zbn%spko1f$4x0Y}#SyyrCFr;fx;UtQ0F|5Q;{J?~^oA}T37ua-7q^G5qeB<}4OL$R z^%t!CO@^9}O&rvgL>E8D09nt7E8&s5t2CW01J)OGx=22jVa=FjPbNptuGJ!R80&K*jfi1Q{3@DqcbK zf#$72LeWq@s0|0=uY|6fK^OnV0$CsM2C5!bZ^PVa_8MX?%zjW^0y2*eDh{i^KQ0c` zK<-4J2ST^Mmj%+lD}?F;g)vAUXr35UrguTbLGuG3addSZ(Do0yI43)#KZ!10#{p>{ zzk`|!O9!WzApHS!@sCW9aSU|v&(QHmba4h|$a*GpaULAvWzc#IU41z-WPTD|yoVVw z{*5l)f zxPEB6Mqo3<9CZEH7$E5psvX>i2C-rK9hO7%Er8M>IS_`)!)SvYP<2omn?0a(=MQy1 zs1FA+N1hSVZZ3t2!}>#@yaSToa0+5Bdb{*UEkwMe4nl*}f-tO{gt_ZN8>Bt)07~0` zfRtCTd=v!b!^Z0((D>-%V(8^d7F0c~JgkTEVeRu?C?C|n1G#TMln?5kgZMw8d{Fxw z#FvG((?De$m=7IK3it?dH)u>2BpwOn!|Xv{SBbv<4OhEL1lpbgnF+$^?I?rUsNst) zF9D4o3n<+HrDs5CT=Dk+D(`U`V!qEA2+csiJyW3VtUXX#0OmfZzG7&&z{)#9;qm}l zUO}(rVL(^k12qp;Pw$8FVfEJ=C?B+r0Th1d;jZ`z5^ta}FOYmXBc$Jho{rGVb9DJ+ z=)6B{of>+(+3X9%9@w50P*asSC1(^dH9|!eC4nV~nL4phn450W0 zr4e*-&^S0Y@jmFdJR@|y6=*yUWEAM07tkCGx_BORJt4X{sJ#cOTce@o!0H)Td#xNQ z4jMBBnGb94p|9tPfXpf{f#aT8s`M{R|Wq-;s+GYAbUaW3Q$>&zHS+m?m&4! z61uJk6t^JtFbpf7VYI|wh(B@3!}e>UuTu)+g0!bk%R%N5L1_zQ259^gWGA{f=zc^{ znnxE0%>#haF1k2KEl3Z#IA|{)NDsQW0|NsCX#5ae9F%TBuluIA~51RHmSd`$N}(p^JmoWrOlNy7*M+I$U&d(0Y4p z>Ot$$u!(Pj&M%;wa}&BA2wnUw17zJTx;SV(5Vr7PXJlXiac3OjM$qvZboH7z)Pu&a zv4sz4-zzq8NgU>Y#zC;Dw}zHm=A-)#6-Z};?e}eKADBYrqgU0Vb?Gtoy(EK(w z^`LqYl+V%CgX#~EcqN*>pmr3fyhj%YHh^(9jJT+ zxJqAm1Wrc0jiHd`q0gr z&H(8rpo{N>)^F(Ip!<12WiYz<1!%d8E)Ke%9GiO3dQMOrqpSY|Z4aS~|HB~;nsmYD zFHpY3CJwr<6jUdnn-7|Y1eH(d;xf?uiY_h;Ef3JeLFFZ=JU|zhz+n!k90m2Ec0uD0 zl-5A;_Xx@dt#1MG6%`=$HfSCV#GeISM~p5Gnm-1$>oTG0L3s?M9$no==sK7ps5)4B zn*!y-`dOg#4KiarR2-K&P`U@HL)Q3KDj%`A2h`5RCJtI}19B6(IiP(7An`ehkaz)wIVhY# zXx}C_b2i~n4_a4&P5ln2`gj#cc!JUgC_Jm6eAu|iS|}eD-k|l?pnC8HR2)>Mfb<1H z%|jOloudM>A6*<29-y)lT^v;2g2e4iAnpQa|#xF0x0+A1dx(8HtfXo5)X+ilW4Jr=egTyo^n4jme*|O> zXdOGa?jW%L3Vq)e?3@%_`@e9VC*%k{_X8FlpuR0APoVEZe#s7*&lG{~gMp31p{sk( z4rzxFYF8RS(<8{w*f9Ej7|^&2D6JPm?XQ5+El|22O3#6s8*c%LA5i@PGXEeuWPXc~ zyUw&g(jBh!_y}q)sJ{s^w;Q?-3&aLtSo(y~2cl8e?ZL!hG_0QwQ|ECCvi>9mdfrnG zlrAZPxCwy`3E%K3>tT6hw2BFB_RFi>OkV4a?{ch67TU)x&}%w zg3>3T^aZGSpmqevyh6~Pa0Uhjbnz(YxfJN)pmQ}qZ6);m-w&bZ%Akw$vqILDg6u-Z zu=x5j9TLA8Gaxjw8W0<%?g~_%V-|jOHnSn}3!pT}3}g&5k7W)--5e;5-W~z%e*^Wm z+Mwqd!PW!8&Y^;t12Y@Fp1B6q2dh`m=Q{}Le+$(QDlb8HpzF7U)?=V@86+Qx#;=6( zVf7e#z1Ibmhs~4pLiwP!0?54CP(G;r4B{_?@4t9+R*)V0d4*If2ev;egc^<2%R5-`IC_O@=*0K z`!%3^nEm=tKCFFW3FU+G2*^HHC?D3|aEJ0?@e&5*!~Bmvf0YK6hw0CN^2ya-4%H6} zUsCP2fYvjx_DU1fJeYgXy%I`;+ycTgq5LUOdI^*U z$$@Ymls^wj$3V?ZfYKYF;vluixEm@y8%jq($Kzn^QY4*>M@R5MhT2R^oO8Y_SC@9SU%`d4?ejb#rgwicgdLopb3#C^=X%49V z-BA8XD18-5KY`NUp|m5kU%&)izrYWrrJ%G5l-7jOqu~jO&(ZLNgu`h1goMLreusp^ zX!!{V2VCVRvF$WiyJwUh4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7=GZ& zH^}~%2T=MAl>PywS-wN$1)#JHl-7XKCQ#Y|O8Y?R2q>Kbr3;{R4V3PH(o>-H0w}!( zO7DQuN1*ftD18S?zkt$Tpftk|h&y?pv;>q^fzk$0+6GE{KN^gMDd!Y0QD18M=KY-G2p!5$Y&GHN4P5~$_1En>fvRD&5(N@Ljtwjv4V(f6 z8bMAWP7PcF4Qw1Bxge(o7J*>rAg2a4fdYjBjsgLYIv%i4fk1&k0Y`yG0Y`xZNTz@T z52?_=%F)0o5aQIp3o(eJK@g-{qClVkggF|xKwJ*6kx;9HoFMjcG_Zj!Y+wc{0bz~? zc7YHlkk%ll22O}$8#p-{TtJc#XDNU^#mZ5j5aNWakyW692V@J_GX)@{8#p)`_yr0$ zK&mtvSR_K68l<7#EZ}Hh5eRZBkO%@D0gjb$@|Ch!YGph(Iichd=`dR8<2z zNCspf$Oe9a0*zqTU{_GkH!uqXIR`s}LrtQA6%w={FBM3HI)ylgIyP`{6exg$7OEW_ z_}~D7coAeG3&?m7S0UIr#1Se9_6o!;AT>M!1sb8y7?cQd0z0WeT_6ae9Ar2|EtqUz zK@n&W0$BhO0?|<1APHiC!mdCAoO+xZm^lhSo@ihZ00mY9Cn(e)Q3Tc-!cx)TH(gB?Si zLL4KZbV3W0GD8C7W^N&e`1ttZlEmcf_~eZ2`1I1mq7;U}B8~|T@$n@&W${V*`6b0A zMTrFr#V^DUa0yIsNU6+8i7zfGO^Q#-%u7ivVki=rz`#(>QMsmy)0Bo|>0h zlvtALR9TW*%rJqWm;+>uU$A>>iDOZEGDt)KS)>dkA^{Q!%`4B$ONn<%OwKMTN=!}# zNhlymxTNN!mZZ8?B&QaXWaj6Aq&1MF-BU|k5=#<;QgfUVi@}-=P-Q&>Kw2!&1wHf9 z@<9>~s1iPjdFiEz>8Zg5smYmXnaLotLCQQ(l?A1yXXfVxmn0UIfMf$uWkXUcN{}2B zfg~H0m|2YKnFM6n)Z)@yuqQH*M1xaH+=D>Y6rc)1d{zMxj882p$}a+`X<&r#D+&w^ zK>Q8}zc@25Ilnl*xTGi*?BWSfVR$k~OU%pxshF;d}zjE6&Wz06Fynm{*brP7^mkoTRGMB5H6p7;0+{A*6{G!zOz4Yd42_iQvh<+1`s3n$0Whby zBoQ29CqTUP)RN?Kux~Cvxv6>JEO!IM%`8qVDX9dR`T)eu$xnx*l@}mhE2@K55O$2M50p=7G<(H%;gB`g5EKr&U zm0tnk<`<-bV{!wG1C5;>Abvr9ab^X`y2PB!bg;4m5aDu=H%@@rB^gDji7D}*WD73d z!09dz?0^ec6%=PyrN%=P+(1^4TUwG@5ucJ;TvC(|_Ui*o*~~mhguTF&%E?d82D$13 zrc_B$B~11Qrfg{*OtOJR$RWM}>LIXm5=%;oke$?lT?Ncp6R=Acr0mVk0LG=x@wq(A`-QnvxdNy#jV2j`(3P!U*`I{@MrXQn46!%FQFFp-=R zaP)x8xd0V|=8hX+Zb=ciG=rG?04kINNgOZ0+{)tQ{5-IOKY;k-(7?pN!tmg?5(5iE&qXB$2Byrs%;F3NVNmXoWMP=|2PE_57>ImwNr{1h zC5=HIl!kKhOH=Yvi;Ed_!GUUAT95+D=9VlBEg)q~sfmU04E9B-1&Kwe@i4{lNu_CN zsYMJ4EDUcBh%zv+#OG$jr&gpUmzKbbQO?}N%sd9(0*17r(&Cc%)QtFo)S}}2yu_T$ zl1hd)kkdgvSs-wLX${i?Rt=3CjB6N0X0WbdnZg`0fpHDv7p5LYi91XUj0>1|FrPWZ zc!%)^w@U|O2XoFGMhO{(8%!&hHn41AR;f6_q|w5-f^m!B6}bz{YZyD2)-cXt>tX!C zyoO~0;|ta&j0czwFoonCap+*Y!Z?Q|X9sfw;|-<w#skbb9gGv0 z?=bygy1=`JaSz)JwmD2^)LU3CusmR#!*oIKhjNG48Kw(NHyBrN&0(4&G>35wqr(lx zKP)>8AE<{cV1B}4aD(X&^AVH7!NQWVBEuagt3K5 zU&oFLa zeZzc$c@N_Z=9)Fa0vi|?us&hSXgy{j(0;UN}dzb`5_8epU!sQcEu!R2! z;}NC}NO%Iz*k-BYOcR(|7}v0LaD(6(&JOMy zydqne=CD5CsM*2T!g7H51N#CFj|prt5;8BCUodW9dcypGc@M`C@dnWz{uk^QnAb3w z$Sh$x!~B9}3+orAH;gJrSk^Ejah?EDdZOEI&AIFx}v|!{lJoz^I||f$0a+1EwEL1`!5tm`<=PVCrFZdBbRP zhTG!?a}Q&}0>%P~k|nH17;S#=Hn41ATElXIE8_y^0>&RqJ9ri_Jz;KOS;KgR=?&8Y z)(wnH7}v0#;W)se@PS$32h$U_A1r^EdzjWR?qIvavW0a9r^*G9H{vQTKNvN5434lh z2xrV;n!=cKgLw+$2NszJOg0Tn2SikC{xF6Fw6M7dtO)91Kf!o_Wef8b#sw)DK#yyNPn6@ykVcfyAhw%?@K?EqC&oJ&_UBdW;@d)Dz)-TKx z1U4{U;hZC|gY^t!#uuh3EMFM6Fdbn8x!fgS2iFvV3yfd5{;)q_+QQVp+`+Mevxn&d zV+T_Qa}V1UmM4r8*f%g59AUq}K85iM;}T|#9gGVYf3Rn4VbQTr`NQ^sc@5JQW(|!H z1%nGr5)u*;9n2dTTbOS!YCK@M!FY!`At7M}Tfqm$6&xlBFBnw}L0tYC2Ut!zAbcb;T>ki2|%%DV{@Pzdb;}gaT9uXc;DX@ic3*#5IIeaA- zSUxb{V4jdZfy00!WDUy<)(MO`a~OMAH}I@r5fKqdIl%meQGi3Gfw_h84aXG5oCnMy z9~e8BmN2R4uqgasejsl!htc2<(;B81tQ%O)@b<9#&+ zI5;?XcsO`wa8)EMV7#Mlqp*cdgTtgiLc(MXV?;y`(+9>Q+)o$}a2?AHH2ya<`}5HFztHTrM01}#n*Ek&>ZQ@tyP>I9LF4zL z*?$hry>rm`)6wiJLDPQ{&3sNY^$ck4^F%WrlyH&L?-Dfi8fg3{Xy$XFnJ0qgp66)t zU(odbN0WbpCZB-jUjsDx05o|QH1p=5so#di7eZ6N2#tRg&HNQ;{QYQrO*H-6(D)r_ z;lqZ;zkudGCA9DdwV{#Y%K%N@70o>_(fA9{_zh_GrJ>3HMdQn&xz`y@Kd4Lur730x z22j#tU}a!nU}IolU}s=p0G;K;$-uw>N;BLH3=BLB3=F&s3=Dh>3=I4X3=E)BRgi&! z0Td^~3=9mS3=9ln3=9n73=9mQbDAU>7#O4&7#O4(7#L(27#L(37#QRj7#QRk7#Kk3 zH7PPMFeouFFeo!HFsLvvFsL#xFsLywFsL&yFo4c&0>zCM0|SFL0|SE&0|SFD0|SE| z0|SFT0|Nu-{3b&N1_mPr1_omW1_l!b1_o0G1_m<*1_pBm1_sbMPL>P|3|0&b4Au+` z3^oi547Lmm40a3*4E78R450Iz92poGoER7woEaDxK>Ry)Dq-z$M|^g zC>`=38e{+$I(ill9!5g7I3C=UMHK@(1|kk0^+F78fd{5Qo&$~RfCi3{2NL6RVPiV+ zY2fiE9OJ?9@z4Rvc+emZXj~^g88jLPVu3wTj5N3$kEP2D^(&}b4)HH^L=rrp2^#v0 zFJg!X4NgGDBjQsl3J@^?(ghxIfDH)2{STLcjRe7Eic{fZA0W?zhQQ);A>&o?xu8+2 zc*s~Ia^!dhfCrV~Lg*vca0#?Q;CRF^Nj%y(7dX5aK)wWxf5az(20P;+qky2eKpnkB z3|bu3P}YF2o*)p(N=g+xu=#OkESDwgNEjVpu_%f7l8c-PSLQTF1VH;$ly9g zQUZ-}F~p~VhJ!HVPzJBzCL!i3K-m^F(25cnkQtSD$bd39RWigwM^!;Q@CaEvbZ{Dx z-;){O=DVksV4a76D+mV92q1Gk15mVsxu9HNXuuE;8Q+DJ`V0&VzwYrIU|^60RRf?Z z6eJF+@<8HD3=EPM3`{JbUIwUYtzckahyW>IU|=}Lz!UR8feBodLWS^9-0)+o8OsyP z;!`ry(sT_?k}XUV4UJOLEE0{3OhEM?$b|WMA9r2r=ZdPIKC8EOPK}|<+LLDw9=(@! zW8b~!>G#%YiKw}z&rMlU{v`BtlXtN1T;YPPB8k3djwZ>Zob%GtR*WuVs9@l_YtYi9 zy0wl=@5%;q7xR;e_44KSW4unhEZ_L>+mxnd!-SN8o73Z`i0z!exc_xX)bfHvsVK8ks-bhQ8_jMiKBlmTk4;QCU`LFo z-r2noUAN>Gx@=s%Nxjy_J?_TGHU+)aa}`fS7i#+)za$khY4QhV2SFXZ0?}_a)^SJQ zFKBAnw6!q)#r|7&_4d73(X+h0{nfc0wni&sWB2CBu(EuaT7P+)?C11}D)N7{Zb-7| zpRKxVzT@@Aq)8JW^J%2Kby+3d->~+nQDk%Sz9(&tv5r+AKRA?p*)`bFgViN;@g^1CuC8oUBS8BHG=|Y+M*;W5N&ds_mVso;ndi{)Nf2U51*Su2ulK0qR)7z^rJ*i(LTK0sy>gJjc z6IZz#I{#Q-l~tE#yLFC#m$gZJ^z&mgCRG@(xoB(RaEIYd`G+#kw`PC8hG}FS{CIHL zr>CJT<#&(SEcOX)iIw=b@Id**Gis-d?!KD;a*t8Q5u=2*4=+j%Eilqsr}ry&XR6Af z;A5$u*Dp0*#=iCs-zT;m5=$%BX9P{OjP_dbTP@^s*u>7CjjgjD#yMTo`kK<&6IGzI zcqjK1+YtNX9p?NcnHL$#(mgAbu3Z&)`Bc&7=5Ox#N9}bs94Ym=#46_XjeqVD*|PMv z#XTVjJ(Et?a^0U$_FULRqu4V`NA|$s8E1}7SGw`=%%h1Fd;i3E)lT-@SFw~Y*j=o+ z__+8E>x2vjanbzOwO8hD=sWn_pg^E{$^|z6f(vqs*ZuZ+%26LA%+e9sRn>Ce@s?C= z#P@>SCvobjE|WqU_XPMv_Z~j-=;)IrcUW13i~nrQFW&G>!u?>!dg(>se$_6Ex7u7c zun3=}@HNNug0)odnn&D>t9~6XT$EP7OQd;CX-P$sUfTbIRwV~m&NUp+`fe@~Srxm1 zk&`1X{`@{ihst_gHpfqr&u<0nDAbU@=+$ex{N6g{TbsN0C(W6Ad0s?q_{>?ZF{?DK z<*gpyb33!~OP)2~vN&D02`45f+}!4IB(XgGL(&nINR5)mo0C}+7Q8*yaaerIw%6wq zQ#QG$zL9^seb+Q|hvV!Q)|?dZcD9-HRGV$S#{((GB&qwa4jpBh{$&Hh{~0&aFNe1t zuQHEd+t_}^H8f7YB-Uj&TiOSEPljDpq0)ajR^9VUIQ>3;+pIaaCQrM2_eRgmXKx&i zKV!@D?)&5O@6J){lkehf{?GlpHvFtl)bg{hQtP!IPjz~J!fyALi5!b3U)_Gg<#**| z8DE!{?-SO>U$Xix$`t$jXZ-89B^%F~>U<1$zrkEE_p^9a?XB@LV{;90q%l1u2X{Qw5Yr~hD-W>7S zm2qjq1k?WPEAx-KrKV0(TqiF!-}k|LsVkw@mLci6R}ZdaVol#X(O`o2pW?}t{F{v@ za|z}}$K0vWk@e5@aMIeC;j%Y$USGZ#^_E-#*_do0@H-C*@>nS#rE zlbT5qQ<#ok7g0JWHmhZC?Q-k6uhW*e9&eiOR}nZ*N9z0)Q@PED=FV39B0W98ywl~( zFCCBP%7^YPJ5xBh`=n(a@6XlhdZ*_<|NhkB`LcNle3Cn2woOTFf1MG_ANcl|yxp%q zbNSco?T?yjk^ZAhDR)s=U3BQ(x5tFNS6?$ca?Wr1^>4MeegCKz?e&p6&Go!<`o1sw zzAs<+ZOw!3=TGHYy$=bf-u|^usIq;1+V6>uVN;`ApSoNRzPUa^(Jy^rzqWR<7s#4+0ni9hQj@c-$ol-V$ zH;>KbwfiDtE!H2A)($< zeIFPR;S;THJo@n`2RX(H5rV8!c^rb@82YU0lU1zxmIJi~0gT&)kig^}S?^ zNHEJZ;eGGiIYW|s%s5^+1x7?U?)t^IcyAh0zBl`nr>ft*&+V>wsrDz;%pPs{at-nptL88Fs+?$PyHUFHeP^@}n^I-M*LpxZv$N61!%X|7iIPPv? z^i-zB-FxC$&YasN<|-5AQPuv7(RsR|>4J$&k+<7Sr~El(lJP0);ldlnvAnTo7ewkD z-rvQM&Aga>XA*DTt)NE9gHv4eExgo4Uvl)#JsM#?Z}W$r!V@wU6{@J3{$H%xyp+YP z*4*H2Kd`Tg6o zS@_Qc)6iNA9CSWAouO~;1TL+M)07t&sLa%O9P~h@YGKZg2u6PH|C(2d{!1PE-|MKrC0+jN z`K+TK+l|&}99}5%a=EW?y<)Rt+ihbxxu8atDcfi4&euQEci196!9?Zq9A=r7CxY_& z{b&Ccn5!h|-ei-{`kf_$$2&05j#Zu~dHbRxJ9h=#i~A6}Cdd1)skP5R>8;;xM}8{0 zS$DecR@~dXpW%&FyzfewW_O2#>1^GdGLM(zv81qSs!$u7RJ?5Ew1lhu6P+Y7+5g|N ze-*nw=a9&?u7JJDf{c^5w_cd?G+fwXPZhsi5%<%Tp3P_9@^U-dq(;joa$2uw`dv8v zB+n<;f*U!df4E+zMzvkdD=5)QS;cK{P|MJ}aY_Gl-dVfP#Vnh!vn%h(qHnjKPFG(( zE5$(VvwyL$Rv_y+BY*v{TbJjx=M?fhNn!O;_&)Pz#;M=E3*&blY`=eP(lYT+ySX>RrS$!@e0|41-lnCr%VX(-?S?aER<7Dw@IbP5J|ExO zpGV_-3=F3^Dp)Pv`mp=WYsH)iR}ALQUwHo1@q5Bkzq~YZeO!BZlGmwwKYxFbeip!I zP`hk)YtYlRdrVG#w|Nj~e7~mPh{pB9s;-ajvp?89bzX7AZF2|XCs+2fzMJGVtzW_* z-*0xcowdXLO(pM~Bb3WhY~IMSPigz!_d4erw-&oi>87MZ>Al-;l^%c5uk#??h(}TC zroC>=T2u3{iyP*id4FAc-;Ny`P9n~m=W=YZZ%F+Y-gmKwqwB-eqdr|(ij!mHeR6{~ zdWs!8bfq{zt0SXa@ZgkEiKwh851KU!Cmi|YA$v1)WygQ6RPo;vyk6#W$XweR$amB0 z?UH@pc(2@6I;H8cWg$!C9Bu8&x`4TxmTCMF@)o%&@=Ul&MQTU$sUAnQ)r&r=yph>i zTG6_6zNx^KM}ie@t7Xn9#T~8k7N4YTb5oo-w``Ax%j>Ie=2j$i+i^sBFG~=fbIVuw zSnZwoo64&ktYp83{m#1g=ch)^zlZq?B?WIJ1YA*QH*#2{Q6GKTV7GdP^s1YCnH{!1 zYBz1puWI+_b1i(sd@|I`@x%k)3%Ogr^G(;;aZmPe!&=eS+8b~5-n20tTlJr1PLt2l zJC4V`@wpcCth1bR>}urIvhbr{U){+FpIwAawPfBpoWhU$6GChk6cYu4wySHi`fzs0mq^_A>e!o6?j+wcoh zqFH&4nqI5FG+}{4^!|5#56j|aiXIoJsGU?8`YM6tvc=m8OYD`Vtf|*e+AnSA`dshk zifi?kJ!s9Ta}fmd`xP=KDx&-pcSa znh|1cNer{fiMC%76*Fh+z6h+3oz&Vd z|4HCZY=P63r#-V9S&C*qG_+BfXza_j!$whmBIosptG`~QPdU20qjpZmnj-y>i+Qn$ zdLJ6(ox)ib%+G8{2x)tpGWAtgPeA&zcdcIA7#`fYQsWWbzBJ;Fv~Q0X7xT4U&Tnp| zT#%Nl{pxZu{?U%o2QHc>#}{6VR987;?4x0IAohci@2;!-t|6y(Up?@^nKx#s*yj`f zWQ96ztiS7|ZSt>gy5m#Uwr3??jJJQvdrVq3J9$yv+P_~{f9l9loe=$7qiTt*?V~-7 z=9U!(bKNKI-g{a?J0bB<(bgYb^ER@)U8X#}XQ`F9N6OK^)$1hNL#_w*$~u4D&tH+o zvbd5}C7>b0CrIY)(nDW2{W5=D%JDzOG%)Ly!;iMhC)5vF&0g;NBg14~%&TYXqo-Sc zo!Akn8=CoEvZ1Q_MnIj#ikquF_wD&I|8;VN+ubx-r3<;&9x6Ty>pgIT`>sP-!h(b& zWn9Z7y@WRH>EA1~ZL%+G_v@1p)6XWUr@feZI`;7UcNR^5{_NCs+_$A;yHA~38-HWf z)Fn!5m`=p%<})vNZRWMK+Ny9}`}OTTR{UFzh!rpyEl61|bYJRK{q*GrS6<4Paq)Ii zvH7mHMI~2y7BT-2TQ#TgYx^!iFR_P)Ep6U%_HHV&AMS;5#2;Sh(Y1O_UB!m%A72{^ z|JlV9#AbMy_I;frH*>4&hmQ=$KW)^Xpd8xGbD5Q|u4dhf^Y6+<*%C{iv57g^pV$&F zys-OI%VCYs8H1mrYyL z>GVt0qOow=C%Xn{La^17+>*p6W%sYN4{%&!w@A}K~KUdx1 z;GV@y|E`)n5Xx&hyj*|g&Z^Yu4eK}8Ki%@vP5NEUV-`sl(aie-%Dd7+|B2b?6zt)6 zyy(DlU(o_>y6SgjtA~xb=Jm9*k_5q@&#i>B{9towG)%U@eoX~syZ*c2ew(8y6_WE{ z#>;Ry$?o0lEpgqSUi)jN9+VYJE9T|@w2w4>^2wKe|CJh(RELs>D%1<3>UZ# zoi+&2>*`rEciSxAxig>l{C0fC{=mz2>e(Ywk<)L^ZQR4CdpP-}NxpGU&WHL}fo|?! zBxINF{#ln{Tj(D7^>O=>3rqJF>EB&B<>}rw>Dg(AJA?FJCxl0S3jLV+Fe-W8+q;KN zyn0S>FSYq|a6?RDkbJm9#`^M=-#xb8JDc@v zBa!JXdfK*sZO^|twxCi`|Lb|F&G+s*s$H*JxVcogURj+_WlnI~>G`$`@7~>X_{aC! zi8KBz(RXL8R}RzK7_@p?wV35}2Pee?OwZ=CO1Ne$>X^qP^>>Tmk!L-B^MjUb?J3bo zJz(`ROxy0It>J0yDc;YgCZ|`kvuvt&+IS_|WBDNue<6F3u;M#*4>qyvQL33Exjbw3 z=lvUx9PByO<$Ppf9JlrKU78Q|qN`3gw!YspcmY0;m@|_DTn66@XbC-w6Tb~K@{%8DZVtS>s$LVs==gYDOSKNEO`p@~TKVB%hP7zhsx>(`7 zSUF!Wv*)nFZsE(1dsWyTfJX4wOhYz zx-71{!|eEa)4vT9&z##~y5((PhLzUklU9>gu04CsWXp+crAgQKiN_X6F)r9&Vb=9_ z(b}57KT_vBaTTsf+rShmz39#2_SAQWHSe#x9P4as)Lp)NuT^De(cL*Iy;s#9PWM=_ zb@!Y_!a}{N&$;@)m>zP!8&^=_edzQwCD7w%X&HQJ+t1YKq&M;MSQ(mTIVF) z-i+C5WN!^lq_o7X6@dw+_Cj=iIR|tE#WKzc0e<^Qy`foWWu3kHL;-!|& z;Z@6?_|}OD{C0iUJYnlh!F!pLS4EujXFm3{*u8my-Q8~&ohE(Ux;Ko^{qg;Lujhrw zEqk3}ytE^JpAP-D=6;}kXak?b=UqE;cSp>;-Y*`k%{L{wVdq`d8L}P!mKv;^D8w6m z@7cn@WtTKn_4Y2QjbnerLdbuF>ibU)0>ihu3(D%qy@*9GcUfj&+OOverview of licenses:

  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
                                  Apache License
    -                        Version 2.0, January 2004
    -                     http://www.apache.org/licenses/
    -
    -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    -
    -1. Definitions.
    -
    -   "License" shall mean the terms and conditions for use, reproduction,
    -   and distribution as defined by Sections 1 through 9 of this document.
    -
    -   "Licensor" shall mean the copyright owner or entity authorized by
    -   the copyright owner that is granting the License.
    -
    -   "Legal Entity" shall mean the union of the acting entity and all
    -   other entities that control, are controlled by, or are under common
    -   control with that entity. For the purposes of this definition,
    -   "control" means (i) the power, direct or indirect, to cause the
    -   direction or management of such entity, whether by contract or
    -   otherwise, or (ii) ownership of fifty percent (50%) or more of the
    -   outstanding shares, or (iii) beneficial ownership of such entity.
    -
    -   "You" (or "Your") shall mean an individual or Legal Entity
    -   exercising permissions granted by this License.
    -
    -   "Source" form shall mean the preferred form for making modifications,
    -   including but not limited to software source code, documentation
    -   source, and configuration files.
    -
    -   "Object" form shall mean any form resulting from mechanical
    -   transformation or translation of a Source form, including but
    -   not limited to compiled object code, generated documentation,
    -   and conversions to other media types.
    -
    -   "Work" shall mean the work of authorship, whether in Source or
    -   Object form, made available under the License, as indicated by a
    -   copyright notice that is included in or attached to the work
    -   (an example is provided in the Appendix below).
    -
    -   "Derivative Works" shall mean any work, whether in Source or Object
    -   form, that is based on (or derived from) the Work and for which the
    -   editorial revisions, annotations, elaborations, or other modifications
    -   represent, as a whole, an original work of authorship. For the purposes
    -   of this License, Derivative Works shall not include works that remain
    -   separable from, or merely link (or bind by name) to the interfaces of,
    -   the Work and Derivative Works thereof.
    -
    -   "Contribution" shall mean any work of authorship, including
    -   the original version of the Work and any modifications or additions
    -   to that Work or Derivative Works thereof, that is intentionally
    -   submitted to Licensor for inclusion in the Work by the copyright owner
    -   or by an individual or Legal Entity authorized to submit on behalf of
    -   the copyright owner. For the purposes of this definition, "submitted"
    -   means any form of electronic, verbal, or written communication sent
    -   to the Licensor or its representatives, including but not limited to
    -   communication on electronic mailing lists, source code control systems,
    -   and issue tracking systems that are managed by, or on behalf of, the
    -   Licensor for the purpose of discussing and improving the Work, but
    -   excluding communication that is conspicuously marked or otherwise
    -   designated in writing by the copyright owner as "Not a Contribution."
    -
    -   "Contributor" shall mean Licensor and any individual or Legal Entity
    -   on behalf of whom a Contribution has been received by Licensor and
    -   subsequently incorporated within the Work.
    -
    -2. Grant of Copyright License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   copyright license to reproduce, prepare Derivative Works of,
    -   publicly display, publicly perform, sublicense, and distribute the
    -   Work and such Derivative Works in Source or Object form.
    -
    -3. Grant of Patent License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   (except as stated in this section) patent license to make, have made,
    -   use, offer to sell, sell, import, and otherwise transfer the Work,
    -   where such license applies only to those patent claims licensable
    -   by such Contributor that are necessarily infringed by their
    -   Contribution(s) alone or by combination of their Contribution(s)
    -   with the Work to which such Contribution(s) was submitted. If You
    -   institute patent litigation against any entity (including a
    -   cross-claim or counterclaim in a lawsuit) alleging that the Work
    -   or a Contribution incorporated within the Work constitutes direct
    -   or contributory patent infringement, then any patent licenses
    -   granted to You under this License for that Work shall terminate
    -   as of the date such litigation is filed.
    -
    -4. Redistribution. You may reproduce and distribute copies of the
    -   Work or Derivative Works thereof in any medium, with or without
    -   modifications, and in Source or Object form, provided that You
    -   meet the following conditions:
    -
    -   (a) You must give any other recipients of the Work or
    -       Derivative Works a copy of this License; and
    -
    -   (b) You must cause any modified files to carry prominent notices
    -       stating that You changed the files; and
    -
    -   (c) You must retain, in the Source form of any Derivative Works
    -       that You distribute, all copyright, patent, trademark, and
    -       attribution notices from the Source form of the Work,
    -       excluding those notices that do not pertain to any part of
    -       the Derivative Works; and
    -
    -   (d) If the Work includes a "NOTICE" text file as part of its
    -       distribution, then any Derivative Works that You distribute must
    -       include a readable copy of the attribution notices contained
    -       within such NOTICE file, excluding those notices that do not
    -       pertain to any part of the Derivative Works, in at least one
    -       of the following places: within a NOTICE text file distributed
    -       as part of the Derivative Works; within the Source form or
    -       documentation, if provided along with the Derivative Works; or,
    -       within a display generated by the Derivative Works, if and
    -       wherever such third-party notices normally appear. The contents
    -       of the NOTICE file are for informational purposes only and
    -       do not modify the License. You may add Your own attribution
    -       notices within Derivative Works that You distribute, alongside
    -       or as an addendum to the NOTICE text from the Work, provided
    -       that such additional attribution notices cannot be construed
    -       as modifying the License.
    -
    -   You may add Your own copyright statement to Your modifications and
    -   may provide additional or different license terms and conditions
    -   for use, reproduction, or distribution of Your modifications, or
    -   for any such Derivative Works as a whole, provided Your use,
    -   reproduction, and distribution of the Work otherwise complies with
    -   the conditions stated in this License.
    -
    -5. Submission of Contributions. Unless You explicitly state otherwise,
    -   any Contribution intentionally submitted for inclusion in the Work
    -   by You to the Licensor shall be under the terms and conditions of
    -   this License, without any additional terms or conditions.
    -   Notwithstanding the above, nothing herein shall supersede or modify
    -   the terms of any separate license agreement you may have executed
    -   with Licensor regarding such Contributions.
    -
    -6. Trademarks. This License does not grant permission to use the trade
    -   names, trademarks, service marks, or product names of the Licensor,
    -   except as required for reasonable and customary use in describing the
    -   origin of the Work and reproducing the content of the NOTICE file.
    -
    -7. Disclaimer of Warranty. Unless required by applicable law or
    -   agreed to in writing, Licensor provides the Work (and each
    -   Contributor provides its Contributions) on an "AS IS" BASIS,
    -   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    -   implied, including, without limitation, any warranties or conditions
    -   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    -   PARTICULAR PURPOSE. You are solely responsible for determining the
    -   appropriateness of using or redistributing the Work and assume any
    -   risks associated with Your exercise of permissions under this License.
    -
    -8. Limitation of Liability. In no event and under no legal theory,
    -   whether in tort (including negligence), contract, or otherwise,
    -   unless required by applicable law (such as deliberate and grossly
    -   negligent acts) or agreed to in writing, shall any Contributor be
    -   liable to You for damages, including any direct, indirect, special,
    -   incidental, or consequential damages of any character arising as a
    -   result of this License or out of the use or inability to use the
    -   Work (including but not limited to damages for loss of goodwill,
    -   work stoppage, computer failure or malfunction, or any and all
    -   other commercial damages or losses), even if such Contributor
    -   has been advised of the possibility of such damages.
    -
    -9. Accepting Warranty or Additional Liability. While redistributing
    -   the Work or Derivative Works thereof, You may choose to offer,
    -   and charge a fee for, acceptance of support, warranty, indemnity,
    -   or other liability obligations and/or rights consistent with this
    -   License. However, in accepting such obligations, You may act only
    -   on Your own behalf and on Your sole responsibility, not on behalf
    -   of any other Contributor, and only if You agree to indemnify,
    -   defend, and hold each Contributor harmless for any liability
    -   incurred by, or claims asserted against, such Contributor by reason
    -   of your accepting any such warranty or additional liability.
    -
    -END OF TERMS AND CONDITIONS
     
  • @@ -1725,19 +1532,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    // Licensed under the Apache License, Version 2.0
    -// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
    -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
    -// All files in the project carrying such notice may not be copied, modified, or distributed
    -// except according to those terms.
     
  • @@ -1746,6 +1540,17 @@ limitations under the License. @@ -1822,44 +1627,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Copyright 2015 Nicholas Allegra (comex).
    -
    -Licensed under the Apache License, Version 2.0 (the "License");
    -you may not use this file except in compliance with the License.
    -You may obtain a copy of the License at
    -
    -    http://www.apache.org/licenses/LICENSE-2.0
    -
    -Unless required by applicable law or agreed to in writing, software
    -distributed under the License is distributed on an "AS IS" BASIS,
    -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    -See the License for the specific language governing permissions and
    -limitations under the License.
    -
    -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Licensed under the Apache License, Version 2.0
    -<LICENSE-APACHE or
    -http://www.apache.org/licenses/LICENSE-2.0> or the MIT
    -license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
    -at your option. All files in the project carrying such
    -notice may not be copied, modified, or distributed except
    -according to those terms.
     
  • @@ -2037,18 +1804,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -
  • -
  • -

    MIT License

    -

    Used by:

    - -
    This project is dual-licensed under the Unlicense and MIT licenses.
    -
    -You may use this code under the terms of either license.
     
  • diff --git a/scripts/tools/Linux/wav-diff b/scripts/tools/Linux/wav-diff index ca9113cdb32624d8d1cb32c65346718b68fc0994..29e63f3d9a4bdf15321e09c65ba87a3f437436d5 100755 GIT binary patch literal 131 zcmXR*Eh^5;&r`@KDJdwn($`PVEYZzLE7nWTEXgQM(o4?I)h{kcP1Y|n#8lGcYzZ;sO90cqf?v literal 944536 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
    1l%8uj!*w0BSXW4S*K>9^Ka9!Pa-6Gk=<^ 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�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 TE}Gy8Zh~a7;Xy% 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

    sb1qS{W&>`)<-DwV<-C-UcnxBe;cYwOt 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�sM{qf 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

    &MufTO zUOV=iSZlye08iSLS%AtFi5I6J!{(q^IBa<7`^V0M9=mSC@Ay37@%@p2vFE0fSU=&T~sPy;Re2$C<7!1T9v~9nG%F}DB{IW z$k095Lms`hyM8k!q(eOhJ?5AXmKR^4QU* zIY0Tf)m#~m&ikN2rk5(9Gj)D2mX>>T7Yle;ekv~W=ynFpp-ut~YI$_~2!OWp2xxdT z9|YYA&Fs-D;$V0HdVWjmr4mPv-a-kF=0h($nkzLJJUTy?y!7aHN!=sxWl+M8?%77RGprQa=e1Njh zt_P6fL&2lhM+JNtyaq^-fk!WE`bSXcf)*!%PQUM|Y5e%SY+zV<&ce44)GEIIf9?2!F9j{mQN#w)GCW3k`=i>5P!;-FZ=r#smIWY}w7r1tGm30(TC6<`La zK@6|}-OjD$(_QW0+072>N3!~Ke*b^Lv$>vwp+v~1^E

    @?Z5c6LQY*=sXNj#R<|O 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

    Q5 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 VhS?1ghvC&wcc{TMKqb()3;-i4ldk{( diff --git a/scripts/tools/Win32/.gitattributes b/scripts/tools/Win32/.gitattributes new file mode 100644 index 0000000000..bdde0e8fc1 --- /dev/null +++ b/scripts/tools/Win32/.gitattributes @@ -0,0 +1 @@ +wav-diff.exe filter=lfs diff=lfs merge=lfs -text diff --git a/scripts/tools/Win32/thirdPartyLegalnotices/licenses_wav-diff.html b/scripts/tools/Win32/thirdPartyLegalnotices/licenses_wav-diff.html index 0b17bd169b..dd386d5ea2 100644 --- a/scripts/tools/Win32/thirdPartyLegalnotices/licenses_wav-diff.html +++ b/scripts/tools/Win32/thirdPartyLegalnotices/licenses_wav-diff.html @@ -44,8 +44,8 @@

    Overview of licenses:

    @@ -678,199 +678,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
                                  Apache License
    -                        Version 2.0, January 2004
    -                     http://www.apache.org/licenses/
    -
    -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    -
    -1. Definitions.
    -
    -   "License" shall mean the terms and conditions for use, reproduction,
    -   and distribution as defined by Sections 1 through 9 of this document.
    -
    -   "Licensor" shall mean the copyright owner or entity authorized by
    -   the copyright owner that is granting the License.
    -
    -   "Legal Entity" shall mean the union of the acting entity and all
    -   other entities that control, are controlled by, or are under common
    -   control with that entity. For the purposes of this definition,
    -   "control" means (i) the power, direct or indirect, to cause the
    -   direction or management of such entity, whether by contract or
    -   otherwise, or (ii) ownership of fifty percent (50%) or more of the
    -   outstanding shares, or (iii) beneficial ownership of such entity.
    -
    -   "You" (or "Your") shall mean an individual or Legal Entity
    -   exercising permissions granted by this License.
    -
    -   "Source" form shall mean the preferred form for making modifications,
    -   including but not limited to software source code, documentation
    -   source, and configuration files.
    -
    -   "Object" form shall mean any form resulting from mechanical
    -   transformation or translation of a Source form, including but
    -   not limited to compiled object code, generated documentation,
    -   and conversions to other media types.
    -
    -   "Work" shall mean the work of authorship, whether in Source or
    -   Object form, made available under the License, as indicated by a
    -   copyright notice that is included in or attached to the work
    -   (an example is provided in the Appendix below).
    -
    -   "Derivative Works" shall mean any work, whether in Source or Object
    -   form, that is based on (or derived from) the Work and for which the
    -   editorial revisions, annotations, elaborations, or other modifications
    -   represent, as a whole, an original work of authorship. For the purposes
    -   of this License, Derivative Works shall not include works that remain
    -   separable from, or merely link (or bind by name) to the interfaces of,
    -   the Work and Derivative Works thereof.
    -
    -   "Contribution" shall mean any work of authorship, including
    -   the original version of the Work and any modifications or additions
    -   to that Work or Derivative Works thereof, that is intentionally
    -   submitted to Licensor for inclusion in the Work by the copyright owner
    -   or by an individual or Legal Entity authorized to submit on behalf of
    -   the copyright owner. For the purposes of this definition, "submitted"
    -   means any form of electronic, verbal, or written communication sent
    -   to the Licensor or its representatives, including but not limited to
    -   communication on electronic mailing lists, source code control systems,
    -   and issue tracking systems that are managed by, or on behalf of, the
    -   Licensor for the purpose of discussing and improving the Work, but
    -   excluding communication that is conspicuously marked or otherwise
    -   designated in writing by the copyright owner as "Not a Contribution."
    -
    -   "Contributor" shall mean Licensor and any individual or Legal Entity
    -   on behalf of whom a Contribution has been received by Licensor and
    -   subsequently incorporated within the Work.
    -
    -2. Grant of Copyright License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   copyright license to reproduce, prepare Derivative Works of,
    -   publicly display, publicly perform, sublicense, and distribute the
    -   Work and such Derivative Works in Source or Object form.
    -
    -3. Grant of Patent License. Subject to the terms and conditions of
    -   this License, each Contributor hereby grants to You a perpetual,
    -   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    -   (except as stated in this section) patent license to make, have made,
    -   use, offer to sell, sell, import, and otherwise transfer the Work,
    -   where such license applies only to those patent claims licensable
    -   by such Contributor that are necessarily infringed by their
    -   Contribution(s) alone or by combination of their Contribution(s)
    -   with the Work to which such Contribution(s) was submitted. If You
    -   institute patent litigation against any entity (including a
    -   cross-claim or counterclaim in a lawsuit) alleging that the Work
    -   or a Contribution incorporated within the Work constitutes direct
    -   or contributory patent infringement, then any patent licenses
    -   granted to You under this License for that Work shall terminate
    -   as of the date such litigation is filed.
    -
    -4. Redistribution. You may reproduce and distribute copies of the
    -   Work or Derivative Works thereof in any medium, with or without
    -   modifications, and in Source or Object form, provided that You
    -   meet the following conditions:
    -
    -   (a) You must give any other recipients of the Work or
    -       Derivative Works a copy of this License; and
    -
    -   (b) You must cause any modified files to carry prominent notices
    -       stating that You changed the files; and
    -
    -   (c) You must retain, in the Source form of any Derivative Works
    -       that You distribute, all copyright, patent, trademark, and
    -       attribution notices from the Source form of the Work,
    -       excluding those notices that do not pertain to any part of
    -       the Derivative Works; and
    -
    -   (d) If the Work includes a "NOTICE" text file as part of its
    -       distribution, then any Derivative Works that You distribute must
    -       include a readable copy of the attribution notices contained
    -       within such NOTICE file, excluding those notices that do not
    -       pertain to any part of the Derivative Works, in at least one
    -       of the following places: within a NOTICE text file distributed
    -       as part of the Derivative Works; within the Source form or
    -       documentation, if provided along with the Derivative Works; or,
    -       within a display generated by the Derivative Works, if and
    -       wherever such third-party notices normally appear. The contents
    -       of the NOTICE file are for informational purposes only and
    -       do not modify the License. You may add Your own attribution
    -       notices within Derivative Works that You distribute, alongside
    -       or as an addendum to the NOTICE text from the Work, provided
    -       that such additional attribution notices cannot be construed
    -       as modifying the License.
    -
    -   You may add Your own copyright statement to Your modifications and
    -   may provide additional or different license terms and conditions
    -   for use, reproduction, or distribution of Your modifications, or
    -   for any such Derivative Works as a whole, provided Your use,
    -   reproduction, and distribution of the Work otherwise complies with
    -   the conditions stated in this License.
    -
    -5. Submission of Contributions. Unless You explicitly state otherwise,
    -   any Contribution intentionally submitted for inclusion in the Work
    -   by You to the Licensor shall be under the terms and conditions of
    -   this License, without any additional terms or conditions.
    -   Notwithstanding the above, nothing herein shall supersede or modify
    -   the terms of any separate license agreement you may have executed
    -   with Licensor regarding such Contributions.
    -
    -6. Trademarks. This License does not grant permission to use the trade
    -   names, trademarks, service marks, or product names of the Licensor,
    -   except as required for reasonable and customary use in describing the
    -   origin of the Work and reproducing the content of the NOTICE file.
    -
    -7. Disclaimer of Warranty. Unless required by applicable law or
    -   agreed to in writing, Licensor provides the Work (and each
    -   Contributor provides its Contributions) on an "AS IS" BASIS,
    -   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    -   implied, including, without limitation, any warranties or conditions
    -   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    -   PARTICULAR PURPOSE. You are solely responsible for determining the
    -   appropriateness of using or redistributing the Work and assume any
    -   risks associated with Your exercise of permissions under this License.
    -
    -8. Limitation of Liability. In no event and under no legal theory,
    -   whether in tort (including negligence), contract, or otherwise,
    -   unless required by applicable law (such as deliberate and grossly
    -   negligent acts) or agreed to in writing, shall any Contributor be
    -   liable to You for damages, including any direct, indirect, special,
    -   incidental, or consequential damages of any character arising as a
    -   result of this License or out of the use or inability to use the
    -   Work (including but not limited to damages for loss of goodwill,
    -   work stoppage, computer failure or malfunction, or any and all
    -   other commercial damages or losses), even if such Contributor
    -   has been advised of the possibility of such damages.
    -
    -9. Accepting Warranty or Additional Liability. While redistributing
    -   the Work or Derivative Works thereof, You may choose to offer,
    -   and charge a fee for, acceptance of support, warranty, indemnity,
    -   or other liability obligations and/or rights consistent with this
    -   License. However, in accepting such obligations, You may act only
    -   on Your own behalf and on Your sole responsibility, not on behalf
    -   of any other Contributor, and only if You agree to indemnify,
    -   defend, and hold each Contributor harmless for any liability
    -   incurred by, or claims asserted against, such Contributor by reason
    -   of your accepting any such warranty or additional liability.
    -
    -END OF TERMS AND CONDITIONS
     
  • @@ -1725,19 +1532,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    // Licensed under the Apache License, Version 2.0
    -// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
    -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
    -// All files in the project carrying such notice may not be copied, modified, or distributed
    -// except according to those terms.
     
  • @@ -1746,6 +1540,17 @@ limitations under the License. @@ -1822,44 +1627,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Copyright 2015 Nicholas Allegra (comex).
    -
    -Licensed under the Apache License, Version 2.0 (the "License");
    -you may not use this file except in compliance with the License.
    -You may obtain a copy of the License at
    -
    -    http://www.apache.org/licenses/LICENSE-2.0
    -
    -Unless required by applicable law or agreed to in writing, software
    -distributed under the License is distributed on an "AS IS" BASIS,
    -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    -See the License for the specific language governing permissions and
    -limitations under the License.
    -
    -
  • -
  • -

    Apache License 2.0

    -

    Used by:

    - -
    Licensed under the Apache License, Version 2.0
    -<LICENSE-APACHE or
    -http://www.apache.org/licenses/LICENSE-2.0> or the MIT
    -license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
    -at your option. All files in the project carrying such
    -notice may not be copied, modified, or distributed except
    -according to those terms.
     
  • @@ -2037,18 +1804,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -
  • -
  • -

    MIT License

    -

    Used by:

    - -
    This project is dual-licensed under the Unlicense and MIT licenses.
    -
    -You may use this code under the terms of either license.
     
  • diff --git a/scripts/tools/Win32/wav-diff.exe b/scripts/tools/Win32/wav-diff.exe index c06f7bb826..8ed1329aa2 100644 --- a/scripts/tools/Win32/wav-diff.exe +++ b/scripts/tools/Win32/wav-diff.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbefe8e53d825cff13c5a88cd286b5f28307e60849123fde73ea7527f54e2765 -size 872448 +oid sha256:edb7f677278f8914109247673fca07c3a79ddede41a0cede61de8a435809b911 +size 880128 -- GitLab From 86f102421f32888f667736f2cf74e49190df40a3 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 9 Oct 2025 09:47:16 +1100 Subject: [PATCH 222/340] Run python black formatter and some more include changes Signed-off-by: Ripinder Singh --- lib_util/ivas_rtp_file.h | 2 - lib_util/ivas_rtp_pi_data.h | 841 +++++++++++++++--------------- tests/rtp/ivasrtp.py | 984 ++++++++++++++++++++++++++---------- tests/rtp/test_rtp.py | 321 ++++++++---- 4 files changed, 1369 insertions(+), 779 deletions(-) diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index c0757834f0..2a58dc1c96 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -33,9 +33,7 @@ #ifndef IVAS_RTP_FILE_H #define IVAS_RTP_FILE_H -#include #include "common_api_types.h" - #include "ivas_rtp_api.h" #include "ivas_rtp_pi_data.h" diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 6a84b8f2e3..3fc95f8d8d 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -35,446 +35,437 @@ #include "common_api_types.h" -#ifdef __cplusplus -extern "C" -{ -#endif - #ifdef IVAS_RTPDUMP #define IVAS_PI_MAX_DATA_SIZE ( 32 + 2 ) /* max packed PI data bytes + pi header bytes */ - /* IVAS PI Data Types */ - typedef enum - { - /* Forward direction PI types */ - IVAS_PI_SCENE_ORIENTATION, /* orientation of audio scene in unit quaternions */ - IVAS_PI_DEVICE_ORIENTATION_COMPENSATED, /* orientation of device in unit quaternions (compensated) */ - IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED, /* orientation of device in unit quaternions (un-compensated) */ - IVAS_PI_ACOUSTIC_ENVIRONMENT, /* describe the acoustic environment */ +/* IVAS PI Data Types */ +typedef enum +{ + /* Forward direction PI types */ + IVAS_PI_SCENE_ORIENTATION, /* orientation of audio scene in unit quaternions */ + IVAS_PI_DEVICE_ORIENTATION_COMPENSATED, /* orientation of device in unit quaternions (compensated) */ + IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED, /* orientation of device in unit quaternions (un-compensated) */ + IVAS_PI_ACOUSTIC_ENVIRONMENT, /* describe the acoustic environment */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ - IVAS_PI_ISM_NUM, /* Number of objects */ - IVAS_PI_ISM_ID, /* id of each object */ - IVAS_PI_ISM_GAIN, /* gain of each object */ - IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ - IVAS_PI_ISM_POSITION, /* position of each object */ - IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ - IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ - IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ - IVAS_PI_RESERVED13, /* reserved */ - IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ - IVAS_PI_RESERVED15, /* reserved */ - - /* Reverse direction PI types */ - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ - IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ - IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ - IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION, /* receiver’s preference with respect to audio suppression */ - IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ - IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ - IVAS_PI_R_ISM_ID, /* id of an object for editing */ - IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ - IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ - IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ - IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ - IVAS_PI_RESERVED27, /* reserved */ - IVAS_PI_RESERVED28, /* reserved */ - IVAS_PI_RESERVED29, /* reserved */ - IVAS_PI_RESERVED30, /* reserved */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ - IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ - } IVAS_PI_TYPE; - - /* cartesian coordinates (X,Y,Z) in 3D space */ - typedef struct - { - float x, y, z; - } IVAS_COORDINATE; - - /* orientation data corresponding to any of the following pi data types :- - * - IVAS_PI_SCENE_ORIENTATION - * - IVAS_PI_DEVICE_ORIENTATION_COMPENSATED - * - IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED - * - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION - * - IVAS_PI_HEAD_ORIENTATION - * - IVAS_PI_R_ISM_ORIENTATION - * - * piDataType is used to identify the correct pi data type contained here - */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_SCENE_ORIENTATION) */ - uint32_t piDataType; /* one of supported orientation data types */ - IVAS_QUATERNION orientation; /* orientation data expressed as quartenions */ - } IVAS_PIDATA_ORIENTATION; - - /* Acoustic environment corresponding to IVAS_PI_ACOUSTIC_ENVIRONMENT - * - * acoustic environment ID - * late reverb parameters - * - RT60 – indicating the time that it takes for the reflections to reduce 60 dB in energy level, per frequency band - * - DSR – diffuse to source signal energy ratio, per frequency band - * - Pre-delay – delay at which the computation of DSR values was performed - * early reflections - * - 3D rectangular virtual room dimensions - * - Broadband energy absorption coefficient per wall surface - */ - typedef enum - { - IVAS_PI_AE_LOW, /* center frequency 25 Hz */ - IVAS_PI_AE_MID, /* center frequency 250 Hz */ - IVAS_PI_AE_HIGH, /* center frequency 2.5 kHz */ - IVAS_PI_AE_NUM_BANDS /* number of ae bands */ - } IVAS_PI_AE_BANDS; - - typedef enum - { - IVAS_PI_AE_FRONT, - IVAS_PI_AE_BACK, - IVAS_PI_AE_LEFT, - IVAS_PI_AE_RIGHT, - IVAS_PI_AE_CEILING, - IVAS_PI_AE_FLOOR, - IVAS_PI_AE_NUM_SURFACE - } IVAS_PI_AE_SURFACE; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ACOUSTIC_ENV) */ - uint32_t piDataType; /* IVAS_PI_ACOUSTIC_ENVIRONMENT */ - bool availLateReverb; /* AE contains only late reverb parameters */ - bool availEarlyReflections; /* AE containing late reverb and simplified early reflections */ - uint8_t aeid; /* seven-bit acoustic environment identifier */ - - /* only valid if availLateReverb==true or availEarlyReflections==true */ - float rt60[IVAS_PI_AE_NUM_BANDS]; /* time for the reflections to reduce 60 dB per band in seconds */ - float dsr[IVAS_PI_AE_NUM_BANDS]; /* diffuse to source signal energy ratio per band in dB */ - - /* only valid if availEarlyReflections==true */ - IVAS_COORDINATE roomDimensions; /* room dimensions in meters length (x), width (y), height (z) */ - float absorbCoeffs[IVAS_PI_AE_NUM_SURFACE]; /* absorption coefficients for all surfaces */ - } IVAS_PIDATA_ACOUSTIC_ENV; + IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ + IVAS_PI_ISM_NUM, /* Number of objects */ + IVAS_PI_ISM_ID, /* id of each object */ + IVAS_PI_ISM_GAIN, /* gain of each object */ + IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ + IVAS_PI_ISM_POSITION, /* position of each object */ + IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ + IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ + IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ + IVAS_PI_RESERVED13, /* reserved */ + IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ + IVAS_PI_RESERVED15, /* reserved */ + + /* Reverse direction PI types */ + IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ + IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ + IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION, /* receiver’s preference with respect to audio suppression */ + IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ + IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ + IVAS_PI_R_ISM_ID, /* id of an object for editing */ + IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ + IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ + IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ + IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ + IVAS_PI_RESERVED27, /* reserved */ + IVAS_PI_RESERVED28, /* reserved */ + IVAS_PI_RESERVED29, /* reserved */ + IVAS_PI_RESERVED30, /* reserved */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ + IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ +} IVAS_PI_TYPE; + +/* cartesian coordinates (X,Y,Z) in 3D space */ +typedef struct +{ + float x, y, z; +} IVAS_COORDINATE; + +/* orientation data corresponding to any of the following pi data types :- + * - IVAS_PI_SCENE_ORIENTATION + * - IVAS_PI_DEVICE_ORIENTATION_COMPENSATED + * - IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED + * - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION + * - IVAS_PI_HEAD_ORIENTATION + * - IVAS_PI_R_ISM_ORIENTATION + * + * piDataType is used to identify the correct pi data type contained here + */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_SCENE_ORIENTATION) */ + uint32_t piDataType; /* one of supported orientation data types */ + IVAS_QUATERNION orientation; /* orientation data expressed as quartenions */ +} IVAS_PIDATA_ORIENTATION; + +/* Acoustic environment corresponding to IVAS_PI_ACOUSTIC_ENVIRONMENT + * + * acoustic environment ID + * late reverb parameters + * - RT60 – indicating the time that it takes for the reflections to reduce 60 dB in energy level, per frequency band + * - DSR – diffuse to source signal energy ratio, per frequency band + * - Pre-delay – delay at which the computation of DSR values was performed + * early reflections + * - 3D rectangular virtual room dimensions + * - Broadband energy absorption coefficient per wall surface + */ +typedef enum +{ + IVAS_PI_AE_LOW, /* center frequency 25 Hz */ + IVAS_PI_AE_MID, /* center frequency 250 Hz */ + IVAS_PI_AE_HIGH, /* center frequency 2.5 kHz */ + IVAS_PI_AE_NUM_BANDS /* number of ae bands */ +} IVAS_PI_AE_BANDS; + +typedef enum +{ + IVAS_PI_AE_FRONT, + IVAS_PI_AE_BACK, + IVAS_PI_AE_LEFT, + IVAS_PI_AE_RIGHT, + IVAS_PI_AE_CEILING, + IVAS_PI_AE_FLOOR, + IVAS_PI_AE_NUM_SURFACE +} IVAS_PI_AE_SURFACE; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ACOUSTIC_ENV) */ + uint32_t piDataType; /* IVAS_PI_ACOUSTIC_ENVIRONMENT */ + bool availLateReverb; /* AE contains only late reverb parameters */ + bool availEarlyReflections; /* AE containing late reverb and simplified early reflections */ + uint8_t aeid; /* seven-bit acoustic environment identifier */ + + /* only valid if availLateReverb==true or availEarlyReflections==true */ + float rt60[IVAS_PI_AE_NUM_BANDS]; /* time for the reflections to reduce 60 dB per band in seconds */ + float dsr[IVAS_PI_AE_NUM_BANDS]; /* diffuse to source signal energy ratio per band in dB */ + + /* only valid if availEarlyReflections==true */ + IVAS_COORDINATE roomDimensions; /* room dimensions in meters length (x), width (y), height (z) */ + float absorbCoeffs[IVAS_PI_AE_NUM_SURFACE]; /* absorption coefficients for all surfaces */ +} IVAS_PIDATA_ACOUSTIC_ENV; #ifdef RTP_S4_251135_CR26253_0016_REV1 - /* Audio Description corresponding to IVAS_PI_AUDIO_DESCRIPTION - * Describe the following audio decriptors per object/type :- - * - audio content type is speech/music/ambiance - * - if audio rendering is editable - * - if stereo audio is binaural - * - * number of valid entries decide on basis of audio format:- - * - Stereo/SBA/MASA = 1 entry - * - MultiChannel = 2 entries (1 for center channel + 1 for all other channels) - * - ISM = Number of Object entries ( 1 per object ) - * - OMASA/OSBA = 1 + Num Discrete Coded Objects - * - */ - typedef struct - { - bool speech; /* audio has voice/speech */ - bool music; /* audio has music */ - bool ambiance; /* audio has background ambiance */ - bool editable; /* rendering audio metadata is editable */ - bool binaural; /* stereo stream is binaural */ - } IVAS_AUDIO_ID; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_AUDIO_DESC) */ - uint32_t piDataType; /* IVAS_PI_AUDIO_DESCRIPTION */ - uint32_t nValidEntries; /* Number of valid audio IDs */ - IVAS_AUDIO_ID audioId[1 + IVAS_MAX_NUM_OBJECTS]; /* audio id as per format */ - } IVAS_PIDATA_AUDIO_DESC; - - /* ISM specific PI data related to PI types : - - * - * - IVAS_PI_ISM_NUM - * - IVAS_PI_ISM_ID - * - IVAS_PI_ISM_GAIN - * - IVAS_PI_ISM_ORIENTATION - * - IVAS_PI_ISM_POSITION - * - IVAS_PI_ISM_DISTANCE_ATTENUATION - * - IVAS_PI_ISM_DIRECTIVITY - */ - - /* Number of ISMs */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_NUM) */ - uint32_t piDataType; /* IVAS_PI_ISM_NUM */ - uint32_t numObjects; /* Number of ISM */ - } IVAS_PIDATA_ISM_NUM; - - /* ISM ID */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ID) */ - uint32_t piDataType; /* IVAS_PI_ISM_ID */ - uint8_t id[IVAS_MAX_NUM_OBJECTS]; /* 8-bit ISM id of object */ - } IVAS_PIDATA_ISM_ID; - - /* ISM gain */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_GAIN) */ - uint32_t piDataType; /* IVAS_PI_ISM_GAIN */ - int8_t dB[IVAS_MAX_NUM_OBJECTS]; /* ISM gain in dB per object [-96, +3] */ - } IVAS_PIDATA_ISM_GAIN; - - /* ISM orientation */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ORIENTATION) */ - uint32_t piDataType; /* IVAS_PI_ISM_ORIENTATION */ - IVAS_QUATERNION orientation[IVAS_MAX_NUM_OBJECTS]; /* Orientation of audio objects in ISM(s) */ - } IVAS_PIDATA_ISM_ORIENTATION; - - /* ISM position */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_POSITION) */ - uint32_t piDataType; /* IVAS_PI_ISM_POSITION */ - IVAS_COORDINATE position[IVAS_MAX_NUM_OBJECTS]; /* Position of audio objects in ISM(s) */ - } IVAS_PIDATA_ISM_POSITION; - - /* ISM distance attenuation comprising of following gains per ISM - * - reference distance - * - maximum distance - * - roll-off factor - */ - typedef struct - { - float ref_dist; /* reference distance in meters */ - float max_dist; /* maximum distance in meters */ - float roll; /* roll-off factor values */ - } IVAS_DIST_ATTEN; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_ATTENUATION) */ - uint32_t piDataType; /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ - IVAS_DIST_ATTEN distAtten[IVAS_MAX_NUM_OBJECTS]; /* Distance attenuation of audio objects */ - } IVAS_PIDATA_ISM_ATTENUATION; - - /* ISM Directivity comprising of following per ISM :- - * - inner cone angle determines the size of the main cone directed to the front of the object - * - outer cone angle determines the size of the outer (back) cone - * - outer attenuation gain determines the attenuation outside the outer cone - */ - typedef struct - { - uint16_t innerConeAngle; /* inner cone angle in degrees (0 - 360) */ - uint16_t outerConeAngle; /* outer cone angle in degrees (0 - 360) */ - float outerAttenuationdB; /* attenuation outside the outer cone in dB */ - } IVAS_ISM_DIRECTIVITY; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_DIRECTIVITY) */ - uint32_t piDataType; /* IVAS_PI_ISM_DIRECTIVITY */ - IVAS_ISM_DIRECTIVITY directivity[IVAS_MAX_NUM_OBJECTS]; /* Directivity of audio objects */ - } IVAS_PIDATA_ISM_DIRECTIVITY; - - /* Diegetic and non-diegetic indication flag as per audio format - * - * number of valid entries decided on basis of audio format:- - * - Stereo/SBA/MASA/MultiChannel = 1 entry - * - ISM = Number of Object entries ( 1 per object ) - * - OMASA/OSBA = 1 (last) + Num Discrete Coded Objects - */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_DIEGETIC) */ - uint32_t piDataType; /* IVAS_PI_DIEGETIC_TYPE */ - bool isDiegetic[1 + IVAS_MAX_NUM_OBJECTS]; /* diegetic indication as per audio format */ - } IVAS_PIDATA_DIEGETIC; - - /* Audio focus direction indicates a direction of interest. - * The audio focus level indicates the amount of suppression applied to the - * directions other than the audio focus direction. - */ - typedef enum - { - IVAS_FLVL_NO_AUDIO_FOCUS = 0, /* Apply no audio focus */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_1, /* Audio focus level 1 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_2, /* Audio focus level 2 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_3, /* Audio focus level 3 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_4, /* Audio focus level 4 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_5, /* Audio focus level 5 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_6, /* Audio focus level 6 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_7, /* Audio focus level 7 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_8, /* Audio focus level 8 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_9, /* Audio focus level 9 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_10, /* Audio focus level 10 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_11, /* Audio focus level 11 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_12, /* Audio focus level 12 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_13, /* Audio focus level 13 */ - IVAS_FLVL_DEFAULT_AUDIO_FOCUS, /* Default audio focus */ - IVAS_FLVL_MAX_AUDIO_FOCUS, /* Apply max audio focus */ - } IVAS_FLVL; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_AUDIO_FOCUS) */ - uint32_t piDataType; /* IVAS_PI_AUDIO_FOCUS_INDCATION or IVAS_PI_AUDIO_FOCUS_REQUEST */ - bool availDirection; /* audio focus contains direction */ - bool availLevel; /* audio focus contains level */ - IVAS_QUATERNION direction; /* direction data expressed as quarternions */ - IVAS_FLVL flvl; /* audio focus level */ - } IVAS_PIDATA_AUDIO_FOCUS; - - /* Listener position */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_LISTENER_POSITION) */ - uint32_t piDataType; /* IVAS_PI_LISTENER_POSITION */ - IVAS_COORDINATE position; /* Position of audio objects in ISM(s) */ - } IVAS_PIDATA_LISTENER_POSITION; - - - /* Dynamic Audio Suppression describes receiver’s preference with respect to the - * type of audio content that should be enhanced and the amount of suppression to - * be applied to the background noise - */ - typedef enum - { - IVAS_SLI_NO_SUPPRESSION = 0, /* Apply no suppression */ - IVAS_SLI_SUPPRESSION_LEVEL_1, /* Suppression level 1 */ - IVAS_SLI_SUPPRESSION_LEVEL_2, /* Suppression level 2 */ - IVAS_SLI_SUPPRESSION_LEVEL_3, /* Suppression level 3 */ - IVAS_SLI_SUPPRESSION_LEVEL_4, /* Suppression level 4 */ - IVAS_SLI_SUPPRESSION_LEVEL_5, /* Suppression level 5 */ - IVAS_SLI_SUPPRESSION_LEVEL_6, /* Suppression level 6 */ - IVAS_SLI_SUPPRESSION_LEVEL_7, /* Suppression level 7 */ - IVAS_SLI_SUPPRESSION_LEVEL_8, /* Suppression level 8 */ - IVAS_SLI_SUPPRESSION_LEVEL_9, /* Suppression level 9 */ - IVAS_SLI_SUPPRESSION_LEVEL_10, /* Suppression level 10 */ - IVAS_SLI_SUPPRESSION_LEVEL_11, /* Suppression level 11 */ - IVAS_SLI_SUPPRESSION_LEVEL_12, /* Suppression level 12 */ - IVAS_SLI_SUPPRESSION_LEVEL_13, /* Suppression level 13 */ - IVAS_SLI_SUPPRESSION_LEVEL_14, /* Suppression level 14 */ - IVAS_SLI_MAX_SUPPRESSION, /* Apply max suppression */ - } IVAS_SLI; - - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_DYNAMIC_SUPPRESSION) */ - uint32_t piDataType; /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ - bool speech; /* receiver's preference is voice/speech */ - bool music; /* receiver's preference is music */ - bool ambiance; /* receiver's preference is background ambiance */ - IVAS_SLI sli; /* suppression level indicator [0, 15] */ - } IVAS_PIDATA_DYNAMIC_SUPPRESSION; - - /* Reverse PI latency calculated as the elapsed time between the sent reverse PI data - * and received forward PI data. It is based on the receiving device experiencing the - * result of its sent data by receiving the corresponding data in forward direction as - * forward PI data - */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_REVERSE_PI_LATENCY) */ - uint32_t piDataType; /* IVAS_PI_PI_LATENCY */ - IVAS_PI_TYPE type; /* Reverse PI used for computation of Latency */ - int32_t latency; /* Latency as 27-bit int on RTP Clock @ 16KHz */ - } IVAS_PIDATA_REVERSE_PI_LATENCY; - - /* ISM specific PI data editing requests */ - - /* ISM ID in editing requests */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ID) */ - uint32_t piDataType; /* IVAS_PI_R_ISM_ID */ - uint8_t id; /* 8-bit ISM id of object to edit */ - } IVAS_PIDATA_ISM_EDIT_ID; - - /* Editing request for ISM gain */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_GAIN) */ - uint32_t piDataType; /* IVAS_PI_R_ISM_GAIN */ - int8_t dB; /* Preferred ISM gain in dB [-96, +3] */ - } IVAS_PIDATA_ISM_EDIT_GAIN; - - /* Editing request for ISM orientation */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ORIENTATION) */ - uint32_t piDataType; /* IVAS_PI_R_ISM_ORIENTATION */ - IVAS_QUATERNION orientation; /* orientation editing request for received ISM */ - } IVAS_PIDATA_ISM_EDIT_ORIENTATION; - - /* Editing request for ISM position */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_POSITION) */ - uint32_t piDataType; /* IVAS_PI_R_ISM_POSITION */ - IVAS_COORDINATE position; /* Positional editing request for received ISM */ - } IVAS_PIDATA_ISM_EDIT_POSITION; - - /* Editing request for ISM direction */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_DIRECTION) */ - uint32_t piDataType; /* IVAS_PI_R_ISM_DIRECTION */ - float azimuth; /* azimuth angle in degrees [-180, 180] */ - float elevation; /* elevation angle in degrees [-90°, 90°] */ - } IVAS_PIDATA_ISM_EDIT_DIRECTION; +/* Audio Description corresponding to IVAS_PI_AUDIO_DESCRIPTION + * Describe the following audio decriptors per object/type :- + * - audio content type is speech/music/ambiance + * - if audio rendering is editable + * - if stereo audio is binaural + * + * number of valid entries decide on basis of audio format:- + * - Stereo/SBA/MASA = 1 entry + * - MultiChannel = 2 entries (1 for center channel + 1 for all other channels) + * - ISM = Number of Object entries ( 1 per object ) + * - OMASA/OSBA = 1 + Num Discrete Coded Objects + * + */ +typedef struct +{ + bool speech; /* audio has voice/speech */ + bool music; /* audio has music */ + bool ambiance; /* audio has background ambiance */ + bool editable; /* rendering audio metadata is editable */ + bool binaural; /* stereo stream is binaural */ +} IVAS_AUDIO_ID; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_DESC) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_DESCRIPTION */ + uint32_t nValidEntries; /* Number of valid audio IDs */ + IVAS_AUDIO_ID audioId[1 + IVAS_MAX_NUM_OBJECTS]; /* audio id as per format */ +} IVAS_PIDATA_AUDIO_DESC; + +/* ISM specific PI data related to PI types : - + * + * - IVAS_PI_ISM_NUM + * - IVAS_PI_ISM_ID + * - IVAS_PI_ISM_GAIN + * - IVAS_PI_ISM_ORIENTATION + * - IVAS_PI_ISM_POSITION + * - IVAS_PI_ISM_DISTANCE_ATTENUATION + * - IVAS_PI_ISM_DIRECTIVITY + */ + +/* Number of ISMs */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_NUM) */ + uint32_t piDataType; /* IVAS_PI_ISM_NUM */ + uint32_t numObjects; /* Number of ISM */ +} IVAS_PIDATA_ISM_NUM; + +/* ISM ID */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ID) */ + uint32_t piDataType; /* IVAS_PI_ISM_ID */ + uint8_t id[IVAS_MAX_NUM_OBJECTS]; /* 8-bit ISM id of object */ +} IVAS_PIDATA_ISM_ID; + +/* ISM gain */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_GAIN) */ + uint32_t piDataType; /* IVAS_PI_ISM_GAIN */ + int8_t dB[IVAS_MAX_NUM_OBJECTS]; /* ISM gain in dB per object [-96, +3] */ +} IVAS_PIDATA_ISM_GAIN; + +/* ISM orientation */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ORIENTATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_ORIENTATION */ + IVAS_QUATERNION orientation[IVAS_MAX_NUM_OBJECTS]; /* Orientation of audio objects in ISM(s) */ +} IVAS_PIDATA_ISM_ORIENTATION; + +/* ISM position */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_POSITION) */ + uint32_t piDataType; /* IVAS_PI_ISM_POSITION */ + IVAS_COORDINATE position[IVAS_MAX_NUM_OBJECTS]; /* Position of audio objects in ISM(s) */ +} IVAS_PIDATA_ISM_POSITION; + +/* ISM distance attenuation comprising of following gains per ISM + * - reference distance + * - maximum distance + * - roll-off factor + */ +typedef struct +{ + float ref_dist; /* reference distance in meters */ + float max_dist; /* maximum distance in meters */ + float roll; /* roll-off factor values */ +} IVAS_DIST_ATTEN; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_ATTENUATION) */ + uint32_t piDataType; /* IVAS_PI_ISM_DISTANCE_ATTENUATION */ + IVAS_DIST_ATTEN distAtten[IVAS_MAX_NUM_OBJECTS]; /* Distance attenuation of audio objects */ +} IVAS_PIDATA_ISM_ATTENUATION; + +/* ISM Directivity comprising of following per ISM :- + * - inner cone angle determines the size of the main cone directed to the front of the object + * - outer cone angle determines the size of the outer (back) cone + * - outer attenuation gain determines the attenuation outside the outer cone + */ +typedef struct +{ + uint16_t innerConeAngle; /* inner cone angle in degrees (0 - 360) */ + uint16_t outerConeAngle; /* outer cone angle in degrees (0 - 360) */ + float outerAttenuationdB; /* attenuation outside the outer cone in dB */ +} IVAS_ISM_DIRECTIVITY; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_DIRECTIVITY) */ + uint32_t piDataType; /* IVAS_PI_ISM_DIRECTIVITY */ + IVAS_ISM_DIRECTIVITY directivity[IVAS_MAX_NUM_OBJECTS]; /* Directivity of audio objects */ +} IVAS_PIDATA_ISM_DIRECTIVITY; + +/* Diegetic and non-diegetic indication flag as per audio format + * + * number of valid entries decided on basis of audio format:- + * - Stereo/SBA/MASA/MultiChannel = 1 entry + * - ISM = Number of Object entries ( 1 per object ) + * - OMASA/OSBA = 1 (last) + Num Discrete Coded Objects + */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_DIEGETIC) */ + uint32_t piDataType; /* IVAS_PI_DIEGETIC_TYPE */ + bool isDiegetic[1 + IVAS_MAX_NUM_OBJECTS]; /* diegetic indication as per audio format */ +} IVAS_PIDATA_DIEGETIC; + +/* Audio focus direction indicates a direction of interest. + * The audio focus level indicates the amount of suppression applied to the + * directions other than the audio focus direction. + */ +typedef enum +{ + IVAS_FLVL_NO_AUDIO_FOCUS = 0, /* Apply no audio focus */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_1, /* Audio focus level 1 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_2, /* Audio focus level 2 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_3, /* Audio focus level 3 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_4, /* Audio focus level 4 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_5, /* Audio focus level 5 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_6, /* Audio focus level 6 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_7, /* Audio focus level 7 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_8, /* Audio focus level 8 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_9, /* Audio focus level 9 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_10, /* Audio focus level 10 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_11, /* Audio focus level 11 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_12, /* Audio focus level 12 */ + IVAS_FLVL_FOCUS_LEVEL_LEVEL_13, /* Audio focus level 13 */ + IVAS_FLVL_DEFAULT_AUDIO_FOCUS, /* Default audio focus */ + IVAS_FLVL_MAX_AUDIO_FOCUS, /* Apply max audio focus */ +} IVAS_FLVL; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_AUDIO_FOCUS) */ + uint32_t piDataType; /* IVAS_PI_AUDIO_FOCUS_INDCATION or IVAS_PI_AUDIO_FOCUS_REQUEST */ + bool availDirection; /* audio focus contains direction */ + bool availLevel; /* audio focus contains level */ + IVAS_QUATERNION direction; /* direction data expressed as quarternions */ + IVAS_FLVL flvl; /* audio focus level */ +} IVAS_PIDATA_AUDIO_FOCUS; + +/* Listener position */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_LISTENER_POSITION) */ + uint32_t piDataType; /* IVAS_PI_LISTENER_POSITION */ + IVAS_COORDINATE position; /* Position of audio objects in ISM(s) */ +} IVAS_PIDATA_LISTENER_POSITION; + + +/* Dynamic Audio Suppression describes receiver’s preference with respect to the + * type of audio content that should be enhanced and the amount of suppression to + * be applied to the background noise + */ +typedef enum +{ + IVAS_SLI_NO_SUPPRESSION = 0, /* Apply no suppression */ + IVAS_SLI_SUPPRESSION_LEVEL_1, /* Suppression level 1 */ + IVAS_SLI_SUPPRESSION_LEVEL_2, /* Suppression level 2 */ + IVAS_SLI_SUPPRESSION_LEVEL_3, /* Suppression level 3 */ + IVAS_SLI_SUPPRESSION_LEVEL_4, /* Suppression level 4 */ + IVAS_SLI_SUPPRESSION_LEVEL_5, /* Suppression level 5 */ + IVAS_SLI_SUPPRESSION_LEVEL_6, /* Suppression level 6 */ + IVAS_SLI_SUPPRESSION_LEVEL_7, /* Suppression level 7 */ + IVAS_SLI_SUPPRESSION_LEVEL_8, /* Suppression level 8 */ + IVAS_SLI_SUPPRESSION_LEVEL_9, /* Suppression level 9 */ + IVAS_SLI_SUPPRESSION_LEVEL_10, /* Suppression level 10 */ + IVAS_SLI_SUPPRESSION_LEVEL_11, /* Suppression level 11 */ + IVAS_SLI_SUPPRESSION_LEVEL_12, /* Suppression level 12 */ + IVAS_SLI_SUPPRESSION_LEVEL_13, /* Suppression level 13 */ + IVAS_SLI_SUPPRESSION_LEVEL_14, /* Suppression level 14 */ + IVAS_SLI_MAX_SUPPRESSION, /* Apply max suppression */ +} IVAS_SLI; + +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_DYNAMIC_SUPPRESSION) */ + uint32_t piDataType; /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ + bool speech; /* receiver's preference is voice/speech */ + bool music; /* receiver's preference is music */ + bool ambiance; /* receiver's preference is background ambiance */ + IVAS_SLI sli; /* suppression level indicator [0, 15] */ +} IVAS_PIDATA_DYNAMIC_SUPPRESSION; + +/* Reverse PI latency calculated as the elapsed time between the sent reverse PI data + * and received forward PI data. It is based on the receiving device experiencing the + * result of its sent data by receiving the corresponding data in forward direction as + * forward PI data + */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_REVERSE_PI_LATENCY) */ + uint32_t piDataType; /* IVAS_PI_PI_LATENCY */ + IVAS_PI_TYPE type; /* Reverse PI used for computation of Latency */ + int32_t latency; /* Latency as 27-bit int on RTP Clock @ 16KHz */ +} IVAS_PIDATA_REVERSE_PI_LATENCY; + +/* ISM specific PI data editing requests */ + +/* ISM ID in editing requests */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ID) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_ID */ + uint8_t id; /* 8-bit ISM id of object to edit */ +} IVAS_PIDATA_ISM_EDIT_ID; + +/* Editing request for ISM gain */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_GAIN) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_GAIN */ + int8_t dB; /* Preferred ISM gain in dB [-96, +3] */ +} IVAS_PIDATA_ISM_EDIT_GAIN; + +/* Editing request for ISM orientation */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_ORIENTATION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_ORIENTATION */ + IVAS_QUATERNION orientation; /* orientation editing request for received ISM */ +} IVAS_PIDATA_ISM_EDIT_ORIENTATION; + +/* Editing request for ISM position */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_POSITION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_POSITION */ + IVAS_COORDINATE position; /* Positional editing request for received ISM */ +} IVAS_PIDATA_ISM_EDIT_POSITION; + +/* Editing request for ISM direction */ +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_ISM_EDIT_DIRECTION) */ + uint32_t piDataType; /* IVAS_PI_R_ISM_DIRECTION */ + float azimuth; /* azimuth angle in degrees [-180, 180] */ + float elevation; /* elevation angle in degrees [-90°, 90°] */ +} IVAS_PIDATA_ISM_EDIT_DIRECTION; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ - typedef struct - { - size_t size; /* sizeof(IVAS_PIDATA_NO_DATA) */ - uint32_t piDataType; /* IVAS_PI_NO_DATA */ - } IVAS_PIDATA_NO_DATA; +typedef struct +{ + size_t size; /* sizeof(IVAS_PIDATA_NO_DATA) */ + uint32_t piDataType; /* IVAS_PI_NO_DATA */ +} IVAS_PIDATA_NO_DATA; - typedef union - { - IVAS_PIDATA_ORIENTATION scene; - IVAS_PIDATA_ORIENTATION deviceCompensated; - IVAS_PIDATA_ORIENTATION deviceUnCompensated; - IVAS_PIDATA_ACOUSTIC_ENV acousticEnv; +typedef union +{ + IVAS_PIDATA_ORIENTATION scene; + IVAS_PIDATA_ORIENTATION deviceCompensated; + IVAS_PIDATA_ORIENTATION deviceUnCompensated; + IVAS_PIDATA_ACOUSTIC_ENV acousticEnv; #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_PIDATA_AUDIO_DESC audioDesc; - IVAS_PIDATA_ISM_NUM ismNum; - IVAS_PIDATA_ISM_ID ismId; - IVAS_PIDATA_ISM_GAIN ismGain; - IVAS_PIDATA_ISM_ORIENTATION ismOrientation; - IVAS_PIDATA_ISM_POSITION ismPosition; - IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; - IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; - IVAS_PIDATA_DIEGETIC digeticIndicator; - IVAS_PIDATA_AUDIO_FOCUS focusIndication; - - IVAS_PIDATA_ORIENTATION playbackOrientation; - IVAS_PIDATA_ORIENTATION headOrientation; - IVAS_PIDATA_LISTENER_POSITION listnerPosition; - IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppression; - IVAS_PIDATA_AUDIO_FOCUS focusRequest; - IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; - IVAS_PIDATA_ISM_EDIT_ID ismEditId; - IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; - IVAS_PIDATA_ISM_EDIT_ORIENTATION ismEditOrientation; - IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; - IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; + IVAS_PIDATA_AUDIO_DESC audioDesc; + IVAS_PIDATA_ISM_NUM ismNum; + IVAS_PIDATA_ISM_ID ismId; + IVAS_PIDATA_ISM_GAIN ismGain; + IVAS_PIDATA_ISM_ORIENTATION ismOrientation; + IVAS_PIDATA_ISM_POSITION ismPosition; + IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; + IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; + IVAS_PIDATA_DIEGETIC digeticIndicator; + IVAS_PIDATA_AUDIO_FOCUS focusIndication; + + IVAS_PIDATA_ORIENTATION playbackOrientation; + IVAS_PIDATA_ORIENTATION headOrientation; + IVAS_PIDATA_LISTENER_POSITION listnerPosition; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppression; + IVAS_PIDATA_AUDIO_FOCUS focusRequest; + IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; + IVAS_PIDATA_ISM_EDIT_ID ismEditId; + IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; + IVAS_PIDATA_ISM_EDIT_ORIENTATION ismEditOrientation; + IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; + IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_PIDATA_NO_DATA noPiData; - } PIDATA; + IVAS_PIDATA_NO_DATA noPiData; +} PIDATA; - typedef struct - { - PIDATA data; - uint32_t timestamp; - } PIDATA_TS; +typedef struct +{ + PIDATA data; + uint32_t timestamp; +} PIDATA_TS; #endif /* IVAS_RTPDUMP */ -#ifdef __cplusplus -} -#endif - #endif /* IVAS_RTP_PI_DATA_H */ diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index 8d979a7e6d..bd13acaf56 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -44,7 +44,8 @@ import argparse from pathlib import Path from typing import cast, Optional -NO_REQ="NO_REQ" +NO_REQ = "NO_REQ" + class CAMODE(str, Enum): CA_LO_02 = "CA-LO-O2" @@ -56,23 +57,27 @@ class CAMODE(str, Enum): CA_HI_05 = "CA-HI-O5" CA_HI_07 = "CA-HI-O7" + class CODECS(str, Enum): AMRWB = "amrwb_io" EVS = "evs" IVAS = "ivas" + class SRCODEC(str, Enum): LCLD = "lcld" LC3PLUS = "lc3+" + class BANDWIDTH(str, Enum): NB = "narrowband" WB = "wideband" SWB = "super wideband" FB = "fullband" + class REQUESTS(str, Enum): - CODEC = "codec", + CODEC = ("codec",) BR = "bitrate" BW = "bandwidth" CA = "ca-mode" @@ -80,14 +85,16 @@ class REQUESTS(str, Enum): SUBFMT = "sub-format" SRCFG = "sr-config" + class FORMATS(str, Enum): - STEREO="Stereo" - SBA="SBA" - MASA="MASA" - ISM="ISM" - MC="MC" - OMASA="OMASA" - OSBA="OSBA" + STEREO = "Stereo" + SBA = "SBA" + MASA = "MASA" + ISM = "ISM" + MC = "MC" + OMASA = "OMASA" + OSBA = "OSBA" + class SUBFORMATS(str, Enum): FOA_PLANAR = "FOA planar" @@ -155,6 +162,7 @@ class SUBFORMATS(str, Enum): OSBA_ISM3_HOA3 = "OSBA ISM3 HOA3" OSBA_ISM4_HOA3 = "OSBA ISM4 HOA3" + class PIDATAS(str, Enum): SCENE_ORIENTATION = "SCENE_ORIENTATION" DEVICE_ORIENTATION_COMPENSATED = "DEVICE_ORIENTATION_COMPENSATED" @@ -189,6 +197,7 @@ class PIDATAS(str, Enum): RESERVED30 = "RESERVED30" NO_PI_DATA = "NO_PI_DATA" + class SUPPRESSION_LEVEL(int, Enum): SUPPRESSION_LEVEL_NONE = 0 SUPPRESSION_LEVEL_1 = 1 @@ -207,6 +216,7 @@ class SUPPRESSION_LEVEL(int, Enum): SUPPRESSION_LEVEL_14 = 14 SUPPRESSION_LEVEL_MAX = 15 + class AUDIO_FOCUS_LEVEL(int, Enum): AUDIO_FOCUS_LEVEL_NONE = 0 AUDIO_FOCUS_LEVEL_1 = 1 @@ -225,6 +235,7 @@ class AUDIO_FOCUS_LEVEL(int, Enum): AUDIO_FOCUS_LEVEL_DEFAULT = 14 AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 + @dataclass class RTPHDR: version: int = 2 @@ -246,24 +257,28 @@ class RTPHDR: self.timestamp += 320 * numFrames def pack(self, bitstrm: BitStream): - bitstrm.append(f'uint:2={self.version}') - bitstrm.append(f'bool={self.padding}') - bitstrm.append(f'bool={self.extension}') - bitstrm.append(f'uint:4={self.csrcCount}') - bitstrm.append(f'bool={self.marker}') - bitstrm.append(f'uint:7={self.payloadType}') - bitstrm.append(f'uintbe:16={self.sequenceNum}') - bitstrm.append(f'uintbe:32={self.timestamp}') - bitstrm.append(f'uintbe:32={self.ssrc}') - assert len(self.csrcList) == self.csrcCount, "csrcList must be of length csrcCount" + bitstrm.append(f"uint:2={self.version}") + bitstrm.append(f"bool={self.padding}") + bitstrm.append(f"bool={self.extension}") + bitstrm.append(f"uint:4={self.csrcCount}") + bitstrm.append(f"bool={self.marker}") + bitstrm.append(f"uint:7={self.payloadType}") + bitstrm.append(f"uintbe:16={self.sequenceNum}") + bitstrm.append(f"uintbe:32={self.timestamp}") + bitstrm.append(f"uintbe:32={self.ssrc}") + assert ( + len(self.csrcList) == self.csrcCount + ), "csrcList must be of length csrcCount" for csrc in self.csrcList: - bitstrm.append(f'uintbe:32={csrc}') + bitstrm.append(f"uintbe:32={csrc}") if self.extension: - bitstrm.append(f'uintbe:16={self.extensionType}') - bitstrm.append(f'uintbe:16={self.extensionLength}') - assert len(self.extensionWords) == self.extensionLength, "extensionWords must be of extensionLength csrcCount" + bitstrm.append(f"uintbe:16={self.extensionType}") + bitstrm.append(f"uintbe:16={self.extensionLength}") + assert ( + len(self.extensionWords) == self.extensionLength + ), "extensionWords must be of extensionLength csrcCount" for ext in self.extensionWords: - bitstrm.append(f'uintbe:32={ext}') + bitstrm.append(f"uintbe:32={ext}") @classmethod def unpack(cls, bitstrm: ConstBitStream): @@ -278,13 +293,16 @@ class RTPHDR: hdr.timestamp = bitstrm.read(32).uintbe hdr.ssrc = bitstrm.read(32).uintbe if hdr.csrcCount: - hdr.csrcList = [ bitstrm.read(32).uintbe for _ in range(hdr.csrcCount) ] + hdr.csrcList = [bitstrm.read(32).uintbe for _ in range(hdr.csrcCount)] if hdr.extension: hdr.extensionType = bitstrm.read(16).uintbe hdr.extensionLength = bitstrm.read(16).uintbe - hdr.extensionWords = [ bitstrm.read(32).uintbe for _ in range(hdr.extensionLength) ] + hdr.extensionWords = [ + bitstrm.read(32).uintbe for _ in range(hdr.extensionLength) + ] return hdr + @dataclass class SRCONFIG: diegetic: bool = False @@ -292,6 +310,7 @@ class SRCONFIG: pitch: bool = False roll: bool = False + @dataclass class CMR: bandwidth: BANDWIDTH @@ -300,12 +319,14 @@ class CMR: endIdx: int = 0 bitrates: list = field(default_factory=list) + @dataclass class SRINFO: bitrate: int = 0 diegetic: bool = False transportCodec: SRCODEC = SRCODEC.LCLD + @dataclass class FRAME: codec: CODECS = CODECS.IVAS @@ -316,7 +337,8 @@ class FRAME: timestamp: int = 0 au: bytes = field(default_factory=bytes) -#PI DATA STRUCTURES + +# PI DATA STRUCTURES @dataclass class ORIENTATION: w: float = 0.0 @@ -324,15 +346,18 @@ class ORIENTATION: y: float = 0.0 z: float = 0.0 + @dataclass class POSITION: x: float = 0.0 y: float = 0.0 z: float = 0.0 + class ISM_POSITIONS: positions: list[POSITION] + @dataclass class AUDIO_DESCRIPTION: isSpeech: bool = False @@ -341,6 +366,7 @@ class AUDIO_DESCRIPTION: isEditable: bool = False isBinaural: bool = False + @dataclass class DYNAMIC_AUDIO_SUPPRESSION: preferSpeech: bool = False @@ -348,10 +374,12 @@ class DYNAMIC_AUDIO_SUPPRESSION: preferAmbiance: bool = False level: SUPPRESSION_LEVEL = SUPPRESSION_LEVEL.SUPPRESSION_LEVEL_MAX + @dataclass class DIEGETIC_TYPE: isDigetic: list[bool] + @dataclass class ACOUSTIC_ENVIRONMENT: aeid: int = 0 @@ -360,70 +388,288 @@ class ACOUSTIC_ENVIRONMENT: dim: tuple[float, float, float] = () abscoeff: tuple[float, float, float, float, float, float] = () + @dataclass class AUDIO_FOCUS: direction: Optional[ORIENTATION] = None level: Optional[AUDIO_FOCUS_LEVEL] = None + @dataclass class PIDATA: timestamp: int = 0 type: str = "NO_PI_DATA" data: any = None + MAX_PACKED_PI_SIZE = 32 -ivasBitrates = [13200, 16400, 24400, 32000, 48000, 64000, 80000, 96000, 128000, 160000, 192000, 256000, 384000, 512000, -1, 5200] -evsBitrates = [5900, 7200, 8000, 9600, 13200, 16400, 24400, 32000, 48000, 64000, 96000, 128000, 2400, -1, -1, -1] -amrwbBitrates = [6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850, 1750, -1, -1, -1, -1, -1, -1] -requestBitratesForCodec = {CODECS.AMRWB: amrwbBitrates[0:9], CODECS.EVS: evsBitrates[0:12], CODECS.IVAS: ivasBitrates[0:14]} -rt60Value = [0.01, 0.0126, 0.0159, 0.02, 0.0252, 0.0317, 0.04, 0.0504, 0.0635, 0.08, 0.1008, 0.1270, 0.16, 0.2016, 0.2540, 0.32, 0.4032, - 0.5080, 0.64, 0.8063, 1.0159, 1.28, 1.6127, 2.0319, 2.56, 3.2254, 4.0637, 5.12, 6.4508, 8.1275, 10.24, 12.9016 ] -dsrValue = [ -20.0, -21.0, -22.0, -23.0, -24.0, -25.0, -26.0, -27.0, -28.0, -29.0, -30.0, -31.0, -32.0, -33.0, -34.0, -35.0, -36.0, -37.0, - -38.0, -39.0, -40.0, -41.0, -42.0, -43.0, -44.0, -45.0, -46.0, -47.0, -48.0, -49.0, -50.0, -51.0, -52.0, -53.0, -54.0, -55.0, - -56.0, -57.0, -58.0, -59.0, -60.0, -61.0, -62.0, -63.0, -64.0, -65.0, -66.0, -67.0, -68.0, -69.0, -70.0, -71.0, -72.0, -73.0, - -74.0, -75.0, -76.0, -77.0, -78.0, -79.0, -80.0, -81.0, -82.0, -83.0 ] -roomDimensionValue = [0.5, 0.707, 1.0, 1.4141, 2.0, 2.8282, 4.0, 5.6568, 8.0, 11.314, 16.0, 22.627, 32.0, 45.255, 64.0, 90.51] +ivasBitrates = [ + 13200, + 16400, + 24400, + 32000, + 48000, + 64000, + 80000, + 96000, + 128000, + 160000, + 192000, + 256000, + 384000, + 512000, + -1, + 5200, +] +evsBitrates = [ + 5900, + 7200, + 8000, + 9600, + 13200, + 16400, + 24400, + 32000, + 48000, + 64000, + 96000, + 128000, + 2400, + -1, + -1, + -1, +] +amrwbBitrates = [ + 6600, + 8850, + 12650, + 14250, + 15850, + 18250, + 19850, + 23050, + 23850, + 1750, + -1, + -1, + -1, + -1, + -1, + -1, +] +requestBitratesForCodec = { + CODECS.AMRWB: amrwbBitrates[0:9], + CODECS.EVS: evsBitrates[0:12], + CODECS.IVAS: ivasBitrates[0:14], +} +rt60Value = [ + 0.01, + 0.0126, + 0.0159, + 0.02, + 0.0252, + 0.0317, + 0.04, + 0.0504, + 0.0635, + 0.08, + 0.1008, + 0.1270, + 0.16, + 0.2016, + 0.2540, + 0.32, + 0.4032, + 0.5080, + 0.64, + 0.8063, + 1.0159, + 1.28, + 1.6127, + 2.0319, + 2.56, + 3.2254, + 4.0637, + 5.12, + 6.4508, + 8.1275, + 10.24, + 12.9016, +] +dsrValue = [ + -20.0, + -21.0, + -22.0, + -23.0, + -24.0, + -25.0, + -26.0, + -27.0, + -28.0, + -29.0, + -30.0, + -31.0, + -32.0, + -33.0, + -34.0, + -35.0, + -36.0, + -37.0, + -38.0, + -39.0, + -40.0, + -41.0, + -42.0, + -43.0, + -44.0, + -45.0, + -46.0, + -47.0, + -48.0, + -49.0, + -50.0, + -51.0, + -52.0, + -53.0, + -54.0, + -55.0, + -56.0, + -57.0, + -58.0, + -59.0, + -60.0, + -61.0, + -62.0, + -63.0, + -64.0, + -65.0, + -66.0, + -67.0, + -68.0, + -69.0, + -70.0, + -71.0, + -72.0, + -73.0, + -74.0, + -75.0, + -76.0, + -77.0, + -78.0, + -79.0, + -80.0, + -81.0, + -82.0, + -83.0, +] +roomDimensionValue = [ + 0.5, + 0.707, + 1.0, + 1.4141, + 2.0, + 2.8282, + 4.0, + 5.6568, + 8.0, + 11.314, + 16.0, + 22.627, + 32.0, + 45.255, + 64.0, + 90.51, +] absorptionCoeffValues = [0.0800, 0.1656, 0.3430, 0.7101] codedFormats = list(FORMATS) codedSubFormats = list(SUBFORMATS) PiTypeNames = list(PIDATAS) + def mapNearestIndex(table: list, val: float) -> int: for idx, entry in enumerate(table): if abs(entry) >= abs(val): return idx return len(table) - 1 + getListIndex = lambda mylist, val: mylist.index(val) if val in mylist else -1 cmrLookup = [ - CMR(bandwidth=BANDWIDTH.NB, codec=CODECS.EVS, startIdx=0, endIdx=7, bitrates = evsBitrates), #000 = NB-EVS - CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.AMRWB, startIdx=0, endIdx=9, bitrates = amrwbBitrates), #001 = AMRWB IO - CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=12, bitrates = evsBitrates), #010 = WB-EVS - CMR(bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=3, endIdx=12, bitrates = evsBitrates), #011 = SWB-EVS - CMR(bandwidth=BANDWIDTH.FB, codec=CODECS.EVS, startIdx=5, endIdx=12, bitrates = evsBitrates), #100 = FB-EVS - CMR(bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #101 = WB-CA - CMR(bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates = []), #110 = SWB-CA - CMR(bandwidth=NO_REQ, codec=CODECS.IVAS, startIdx=0, endIdx=14, bitrates = ivasBitrates), #111 = IVAS + CMR( + bandwidth=BANDWIDTH.NB, + codec=CODECS.EVS, + startIdx=0, + endIdx=7, + bitrates=evsBitrates, + ), # 000 = NB-EVS + CMR( + bandwidth=BANDWIDTH.WB, + codec=CODECS.AMRWB, + startIdx=0, + endIdx=9, + bitrates=amrwbBitrates, + ), # 001 = AMRWB IO + CMR( + bandwidth=BANDWIDTH.WB, + codec=CODECS.EVS, + startIdx=0, + endIdx=12, + bitrates=evsBitrates, + ), # 010 = WB-EVS + CMR( + bandwidth=BANDWIDTH.SWB, + codec=CODECS.EVS, + startIdx=3, + endIdx=12, + bitrates=evsBitrates, + ), # 011 = SWB-EVS + CMR( + bandwidth=BANDWIDTH.FB, + codec=CODECS.EVS, + startIdx=5, + endIdx=12, + bitrates=evsBitrates, + ), # 100 = FB-EVS + CMR( + bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] + ), # 101 = WB-CA + CMR( + bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] + ), # 110 = SWB-CA + CMR( + bandwidth=NO_REQ, + codec=CODECS.IVAS, + startIdx=0, + endIdx=14, + bitrates=ivasBitrates, + ), # 111 = IVAS ] -q15 = lambda x : int(min(32767.0, max(-32768.0, x * 32768.0))) +q15 = lambda x: int(min(32767.0, max(-32768.0, x * 32768.0))) + def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: - #assert False, "Unsupported PI Data" - return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode('utf-8') + # assert False, "Unsupported PI Data" + return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode("utf-8") + def packUnsupported(bitstrm: ConstBitStream, data: any) -> any: assert False, f"unsupported PI Data of type : {type(data)}" + def unpackNoPiData(bitstrm: ConstBitStream, piSize: int) -> None: assert piSize == 0, "NO_PI_DATA should be 0 size" + def packNoPiData(bitstrm: BitStream, data: any = None): pass + def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION]: - assert (piSize % 8) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" + assert ( + piSize % 8 + ) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" orientations = list() while piSize > 0: w = bitstrm.read(16).int / 32768.0 @@ -434,14 +680,18 @@ def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION piSize -= 8 return orientations + def packOrientations(bitstrm: BitStream, data: any): assert type(data) == list, "Orientation PI Data expects a data of type list" for orientation in cast(list, data): - assert type(orientation) == ORIENTATION, "Orientation PI Data expects a data of type list[ORIENTATION]" - bitstrm.append(f'intbe:16={q15(orientation.w)}') - bitstrm.append(f'intbe:16={q15(orientation.x)}') - bitstrm.append(f'intbe:16={q15(orientation.y)}') - bitstrm.append(f'intbe:16={q15(orientation.z)}') + assert ( + type(orientation) == ORIENTATION + ), "Orientation PI Data expects a data of type list[ORIENTATION]" + bitstrm.append(f"intbe:16={q15(orientation.w)}") + bitstrm.append(f"intbe:16={q15(orientation.x)}") + bitstrm.append(f"intbe:16={q15(orientation.y)}") + bitstrm.append(f"intbe:16={q15(orientation.z)}") + def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: assert piSize <= 24 and (piSize % 6) == 0, "Incorrect PI Data Size for Positions" @@ -454,15 +704,19 @@ def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: piSize -= 6 return positions + def packPositions(bitstrm: BitStream, data: any): assert type(data) == list, "Position PI Data expects a data of type list" positions = cast(list, data) assert len(positions) <= 4, "Max one position per ISM object" for position in positions: - assert type(position) == POSITION, "Position PI Data expects a data of type list[POSITIONS]" - bitstrm.append(f'intbe:16={q15(position.x / 327.68)}') - bitstrm.append(f'intbe:16={q15(position.y / 327.68)}') - bitstrm.append(f'intbe:16={q15(position.z / 327.68)}') + assert ( + type(position) == POSITION + ), "Position PI Data expects a data of type list[POSITIONS]" + bitstrm.append(f"intbe:16={q15(position.x / 327.68)}") + bitstrm.append(f"intbe:16={q15(position.y / 327.68)}") + bitstrm.append(f"intbe:16={q15(position.z / 327.68)}") + def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: assert piSize == 8, "Incorrect PI Data Size for ORIENTATION" @@ -470,23 +724,31 @@ def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: assert len(orientations) == 1 return orientations[0] + def packOrientation(bitstrm: BitStream, data: any): - assert type(data) == ORIENTATION, "Orientation PI Data expects a data of type ORIENTATION" + assert ( + type(data) == ORIENTATION + ), "Orientation PI Data expects a data of type ORIENTATION" orientation = cast(ORIENTATION, data) packOrientations(bitstrm, [orientation]) + def unpackPosition(bitstrm: ConstBitStream, piSize: int) -> POSITION: assert piSize == 6, "Incorrect PI Data Size for POSITION" positions = unpackPositions(bitstrm, piSize) assert len(positions) == 1 return positions[0] + def packPosition(bitstrm: BitStream, data: any): assert type(data) == POSITION, "Position PI Data expects a data of type POSITION" position = cast(POSITION, data) packPositions(bitstrm, [position]) -def unpackAudioDescription(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: + +def unpackAudioDescription( + bitstrm: ConstBitStream, piSize: int +) -> list[AUDIO_DESCRIPTION]: assert piSize <= 5, "Incorrect PI Data Size for AUDIO_DESCRIPTION" ad = list() for byte in range(piSize): @@ -496,20 +758,30 @@ def unpackAudioDescription(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_D E = bitstrm.read(1).bool B = bitstrm.read(1).bool _ = bitstrm.read(3) - ad.append(AUDIO_DESCRIPTION(isSpeech=V, isMusic=M, isAmbiance=A, isEditable=E, isBinaural=B)) + ad.append( + AUDIO_DESCRIPTION( + isSpeech=V, isMusic=M, isAmbiance=A, isEditable=E, isBinaural=B + ) + ) return ad + def packAudioDescription(bitstrm: BitStream, data: any): - assert type(data) == list, "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + assert ( + type(data) == list + ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" for desc in cast(list, data): - assert type(desc) == AUDIO_DESCRIPTION, "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + assert ( + type(desc) == AUDIO_DESCRIPTION + ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" ad = cast(AUDIO_DESCRIPTION, desc) - bitstrm.append(f'bool={ad.isSpeech}') - bitstrm.append(f'bool={ad.isMusic}') - bitstrm.append(f'bool={ad.isAmbiance}') - bitstrm.append(f'bool={ad.isEditable}') - bitstrm.append(f'bool={ad.isBinaural}') - bitstrm.append(f'uint:3=0') + bitstrm.append(f"bool={ad.isSpeech}") + bitstrm.append(f"bool={ad.isMusic}") + bitstrm.append(f"bool={ad.isAmbiance}") + bitstrm.append(f"bool={ad.isEditable}") + bitstrm.append(f"bool={ad.isBinaural}") + bitstrm.append(f"uint:3=0") + def unpackDAS(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: assert piSize == 2, "Incorrect PI Data Size for DYNAMIC_AUDIO_SUPPRESSION" @@ -519,38 +791,52 @@ def unpackDAS(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: _ = bitstrm.read(5) SLI = bitstrm.read(4).uint _ = bitstrm.read(4) - return DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=V, preferMusic=M, preferAmbiance=A, level=SLI) + return DYNAMIC_AUDIO_SUPPRESSION( + preferSpeech=V, preferMusic=M, preferAmbiance=A, level=SLI + ) + def packDAS(bitstrm: BitStream, data: any): - assert type(data) == DYNAMIC_AUDIO_SUPPRESSION, "Dynamic Audio Suppression PI Data expects a data of type DYNAMIC_AUDIO_SUPPRESSION" + assert ( + type(data) == DYNAMIC_AUDIO_SUPPRESSION + ), "Dynamic Audio Suppression PI Data expects a data of type DYNAMIC_AUDIO_SUPPRESSION" das = cast(DYNAMIC_AUDIO_SUPPRESSION, data) - bitstrm.append(f'bool={das.preferSpeech}') - bitstrm.append(f'bool={das.preferMusic}') - bitstrm.append(f'bool={das.preferAmbiance}') - bitstrm.append(f'uint:5=0') - bitstrm.append(f'uint:4={das.level}') - bitstrm.append(f'uint:4=0') + bitstrm.append(f"bool={das.preferSpeech}") + bitstrm.append(f"bool={das.preferMusic}") + bitstrm.append(f"bool={das.preferAmbiance}") + bitstrm.append(f"uint:5=0") + bitstrm.append(f"uint:4={das.level}") + bitstrm.append(f"uint:4=0") + def unpackDiegetic(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: assert piSize == 1, "Incorrect PI Data Size for DIEGETIC_TYPE" digType = list() - for _ in range(5): # no way to know how many bits are valid bits, so all 5 read + for _ in range(5): # no way to know how many bits are valid bits, so all 5 read digType.append(bitstrm.read(1).bool) bitstrm.bytealign() return DIEGETIC_TYPE(isDigetic=digType) + def packDiegetic(bitstrm: BitStream, data: any): - assert type(data) == DIEGETIC_TYPE, "Diegetic type PI Data expects a data of type DIEGETIC_TYPE" + assert ( + type(data) == DIEGETIC_TYPE + ), "Diegetic type PI Data expects a data of type DIEGETIC_TYPE" diegetic = cast(DIEGETIC_TYPE, data) - assert len(diegetic.isDigetic) <= 5, "Maximum 1 bit per object + 1 bit for SBA/MASA is required (max 5)" + assert ( + len(diegetic.isDigetic) <= 5 + ), "Maximum 1 bit per object + 1 bit for SBA/MASA is required (max 5)" for isDigetic in diegetic.isDigetic: - bitstrm.append(f'bool={isDigetic}') + bitstrm.append(f"bool={isDigetic}") nPad = 8 - (bitstrm.pos % 8) if nPad > 0: - bitstrm.append(f'uint:{nPad}=0') + bitstrm.append(f"uint:{nPad}=0") + def unpackAcousticEnv(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: - assert piSize == 1 or piSize == 5 or piSize == 8, "Incorrect PI Data Size for ACOUSTIC_ENVIRONMENT" + assert ( + piSize == 1 or piSize == 5 or piSize == 8 + ), "Incorrect PI Data Size for ACOUSTIC_ENVIRONMENT" rt60 = list() dsr = list() dim = list() @@ -571,32 +857,46 @@ def unpackAcousticEnv(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: for _ in range(6): absCoeff.append(absorptionCoeffValues[bitstrm.read(2).uint]) - return ACOUSTIC_ENVIRONMENT(aeid=aeid, rt60=tuple(rt60), dsr=tuple(dsr), dim=tuple(dim), abscoeff=tuple(absCoeff)) + return ACOUSTIC_ENVIRONMENT( + aeid=aeid, + rt60=tuple(rt60), + dsr=tuple(dsr), + dim=tuple(dim), + abscoeff=tuple(absCoeff), + ) + def packAcousticEnv(bitstrm: BitStream, data: any): - assert type(data) == ACOUSTIC_ENVIRONMENT, "Diegetic type PI Data expects a data of type ACOUSTIC_ENVIRONMENT" + assert ( + type(data) == ACOUSTIC_ENVIRONMENT + ), "Diegetic type PI Data expects a data of type ACOUSTIC_ENVIRONMENT" aenv = cast(ACOUSTIC_ENVIRONMENT, data) if not aenv.rt60 and not aenv.dsr: - bitstrm.append(f'uint:8={aenv.aeid % 128}') + bitstrm.append(f"uint:8={aenv.aeid % 128}") else: - assert len(aenv.rt60) == 3 and len(aenv.dsr) == 3, "Lo, Mi, Hi only required for RT60 and DSR values" - bitstrm.append(f'uint:7={aenv.aeid % 128}') + assert ( + len(aenv.rt60) == 3 and len(aenv.dsr) == 3 + ), "Lo, Mi, Hi only required for RT60 and DSR values" + bitstrm.append(f"uint:7={aenv.aeid % 128}") for n in range(3): rt60 = mapNearestIndex(rt60Value, aenv.rt60[n]) dsr = mapNearestIndex(dsrValue, aenv.dsr[n]) - bitstrm.append(f'uint:5={rt60}') - bitstrm.append(f'uint:6={dsr}') + bitstrm.append(f"uint:5={rt60}") + bitstrm.append(f"uint:6={dsr}") if aenv.abscoeff and aenv.dim: assert len(aenv.abscoeff) == 6 and len(aenv.dim) == 3 for n in range(3): dim = mapNearestIndex(roomDimensionValue, aenv.dim[n]) - bitstrm.append(f'uint:4={dim}') + bitstrm.append(f"uint:4={dim}") for n in range(6): absCoeff = mapNearestIndex(absorptionCoeffValues, aenv.abscoeff[n]) - bitstrm.append(f'uint:2={absCoeff}') + bitstrm.append(f"uint:2={absCoeff}") + def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: - assert piSize == 1 or piSize == 8 or piSize == 9, "Incorrect PI Data Size for AUDIO_FOCUS" + assert ( + piSize == 1 or piSize == 8 or piSize == 9 + ), "Incorrect PI Data Size for AUDIO_FOCUS" direction = None level = None if piSize == 1: @@ -610,87 +910,91 @@ def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: return AUDIO_FOCUS(direction=direction, level=level) + def packAudioFocus(bitstrm: BitStream, data: any): - assert type(data) == AUDIO_FOCUS, "Audio focus PI Data expects a data of type AUDIO_FOCUS" + assert ( + type(data) == AUDIO_FOCUS + ), "Audio focus PI Data expects a data of type AUDIO_FOCUS" auFocus = cast(AUDIO_FOCUS, data) if auFocus.direction is not None: packOrientations(bitstrm, [auFocus.direction]) if auFocus.level is not None: level = int(auFocus.level) - bitstrm.append(f'uint:4={level}') - bitstrm.append(f'uint:4=0') + bitstrm.append(f"uint:4={level}") + bitstrm.append(f"uint:4=0") PIDataUnpacker = [ - unpackOrientation, # SCENE_ORIENTATION, - unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, - unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED - unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT - unpackAudioDescription, # AUDIO_DESCRIPTION - unpackUnsupported, # ISM_NUM - unpackUnsupported, # ISM_ID - unpackUnsupported, # ISM_GAIN - unpackOrientations,# ISM_ORIENTATION - unpackPositions, # ISM_POSITION - unpackUnsupported, # ISM_DISTANCE_ATTENUATION - unpackUnsupported, # ISM_DIRECTIVITY - unpackDiegetic, # DIEGETIC_TYPE - unpackUnsupported, # RESERVED13 + unpackOrientation, # SCENE_ORIENTATION, + unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, + unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT + unpackAudioDescription, # AUDIO_DESCRIPTION + unpackUnsupported, # ISM_NUM + unpackUnsupported, # ISM_ID + unpackUnsupported, # ISM_GAIN + unpackOrientations, # ISM_ORIENTATION + unpackPositions, # ISM_POSITION + unpackUnsupported, # ISM_DISTANCE_ATTENUATION + unpackUnsupported, # ISM_DIRECTIVITY + unpackDiegetic, # DIEGETIC_TYPE + unpackUnsupported, # RESERVED13 unpackAudioFocus, # AUDIO_FOCUS_INDICATION - unpackUnsupported, # RESERVED15 - unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION - unpackOrientation, # HEAD_ORIENTATION - unpackPosition, # LISTENER_POSITION - unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION + unpackUnsupported, # RESERVED15 + unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION + unpackOrientation, # HEAD_ORIENTATION + unpackPosition, # LISTENER_POSITION + unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION unpackAudioFocus, # AUDIO_FOCUS_REQUEST - unpackUnsupported, # PI_LATENCY - unpackUnsupported, # R_ISM_ID - unpackUnsupported, # R_ISM_GAIN - unpackOrientation, # R_ISM_ORIENTATION - unpackPosition, # R_ISM_POSITION - unpackUnsupported, # R_ISM_DIRECTION - unpackUnsupported, # RESERVED27 - unpackUnsupported, # RESERVED28 - unpackUnsupported, # RESERVED29 - unpackUnsupported, # RESERVED30 - unpackNoPiData # NO_DATA + unpackUnsupported, # PI_LATENCY + unpackUnsupported, # R_ISM_ID + unpackUnsupported, # R_ISM_GAIN + unpackOrientation, # R_ISM_ORIENTATION + unpackPosition, # R_ISM_POSITION + unpackUnsupported, # R_ISM_DIRECTION + unpackUnsupported, # RESERVED27 + unpackUnsupported, # RESERVED28 + unpackUnsupported, # RESERVED29 + unpackUnsupported, # RESERVED30 + unpackNoPiData, # NO_DATA ] PIDataPacker = [ - packOrientation, # SCENE_ORIENTATION, - packOrientation, # DEVICE_ORIENTATION_COMPENSATED, - packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED - packAcousticEnv, # ACOUSTIC_ENVIRONMENT - packAudioDescription, # AUDIO_DESCRIPTION - packUnsupported, # ISM_NUM - packUnsupported, # ISM_ID - packUnsupported, # ISM_GAIN - packOrientations,# ISM_ORIENTATION - packPositions, # ISM_POSITION - packUnsupported, # ISM_DISTANCE_ATTENUATION - packUnsupported, # ISM_DIRECTIVITY - packDiegetic, # DIEGETIC_TYPE - packUnsupported, # RESERVED13 + packOrientation, # SCENE_ORIENTATION, + packOrientation, # DEVICE_ORIENTATION_COMPENSATED, + packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + packAcousticEnv, # ACOUSTIC_ENVIRONMENT + packAudioDescription, # AUDIO_DESCRIPTION + packUnsupported, # ISM_NUM + packUnsupported, # ISM_ID + packUnsupported, # ISM_GAIN + packOrientations, # ISM_ORIENTATION + packPositions, # ISM_POSITION + packUnsupported, # ISM_DISTANCE_ATTENUATION + packUnsupported, # ISM_DIRECTIVITY + packDiegetic, # DIEGETIC_TYPE + packUnsupported, # RESERVED13 packAudioFocus, # AUDIO_FOCUS_INDICATION - packUnsupported, # RESERVED15 - packOrientation, # PLAYBACK_DEVICE_ORIENTATION - packOrientation, # HEAD_ORIENTATION - packPosition, # LISTENER_POSITION - packDAS, # DYNAMIC_AUDIO_SUPPRESSION + packUnsupported, # RESERVED15 + packOrientation, # PLAYBACK_DEVICE_ORIENTATION + packOrientation, # HEAD_ORIENTATION + packPosition, # LISTENER_POSITION + packDAS, # DYNAMIC_AUDIO_SUPPRESSION packAudioFocus, # AUDIO_FOCUS_DIRECTION - packUnsupported, # PI_LATENCY - packUnsupported, # R_ISM_ID - packUnsupported, # R_ISM_GAIN - packOrientation, # R_ISM_ORIENTATION - packPosition, # R_ISM_POSITION - packUnsupported, # R_ISM_DIRECTION - packUnsupported, # RESERVED27 - packUnsupported, # RESERVED28 - packUnsupported, # RESERVED29 - packUnsupported, # RESERVED30 - packNoPiData # NO_DATA + packUnsupported, # PI_LATENCY + packUnsupported, # R_ISM_ID + packUnsupported, # R_ISM_GAIN + packOrientation, # R_ISM_ORIENTATION + packPosition, # R_ISM_POSITION + packUnsupported, # R_ISM_DIRECTION + packUnsupported, # RESERVED27 + packUnsupported, # RESERVED28 + packUnsupported, # RESERVED29 + packUnsupported, # RESERVED30 + packNoPiData, # NO_DATA ] + def ReadG192Bitstream(g192File: Path) -> list[bytes]: refPackets = list[bytes]() with open(g192File, "rb") as fd: @@ -706,6 +1010,7 @@ def ReadG192Bitstream(g192File: Path) -> list[bytes]: refPackets.append(writer.tobytes()) return refPackets + def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: piIndicated = False requests = dict() @@ -713,7 +1018,7 @@ def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: if bitstrm.read(1).bool: T = bitstrm.read(3).uint BR = bitstrm.read(4).uint - if T in [5, 6]:#CA MODES + if T in [5, 6]: # CA MODES if BR < 8: requests[REQUESTS.CODEC] = cmrLookup[T].codec requests[REQUESTS.BR] = 13200 @@ -721,12 +1026,12 @@ def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: requests[REQUESTS.BW] = cmrLookup[T].bandwidth else: raise Exception("Unsupported BR bits in CA Mode") - elif T == 7: #IVAS - if BR < 14 : + elif T == 7: # IVAS + if BR < 14: requests[REQUESTS.CODEC] = cmrLookup[T].codec requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] requests[REQUESTS.CA] = -1 - elif BR == 14 : + elif BR == 14: raise Exception("Reserved BR idx in IVAS EByte") else: if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: @@ -735,16 +1040,23 @@ def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: requests[REQUESTS.CA] = -1 requests[REQUESTS.BW] = cmrLookup[T].bandwidth else: - raise Exception("Reserved BR idx in {} EByte".format(cmrLookup[T].codec)) - #Try to get all subsequent E-bytes + raise Exception( + "Reserved BR idx in {} EByte".format(cmrLookup[T].codec) + ) + # Try to get all subsequent E-bytes while bitstrm.read(1).bool: ET = bitstrm.read(3).uint - if ET == 0 : - supportedBW = [BANDWIDTH.WB, BANDWIDTH.SWB, BANDWIDTH.FB, BANDWIDTH.NREQ] + if ET == 0: + supportedBW = [ + BANDWIDTH.WB, + BANDWIDTH.SWB, + BANDWIDTH.FB, + BANDWIDTH.NREQ, + ] reserved = bitstrm.read(2) BW = bitstrm.read(2).uint requests[REQUESTS.BW] = supportedBW[BW] - elif ET == 1 : + elif ET == 1: S = bitstrm.read(1).bool FMT = bitstrm.read(3).uint if not S: @@ -759,30 +1071,48 @@ def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: reserved = bitstrm.read(4) piIndicated = True elif ET == 3: - D = bitstrm.read(1).bool - Y = bitstrm.read(1).bool - P = bitstrm.read(1).bool - R = bitstrm.read(1).bool - requests[REQUESTS.SRCFG] = SRCONFIG(diegetic=D, yaw=Y, pitch=P, roll=R) + D = bitstrm.read(1).bool + Y = bitstrm.read(1).bool + P = bitstrm.read(1).bool + R = bitstrm.read(1).bool + requests[REQUESTS.SRCFG] = SRCONFIG( + diegetic=D, yaw=Y, pitch=P, roll=R + ) else: reserved = bitstrm.read(4) - raise Exception("Unsupported subsequent EByte with ET={}".format(ET)) + raise Exception( + "Unsupported subsequent EByte with ET={}".format(ET) + ) except ReadError as error: - print ("Underflow in E-Bytes parsing during unpacking, error = {}".format(error)) + print("Underflow in E-Bytes parsing during unpacking, error = {}".format(error)) return piIndicated, requests -def packEBytes(bitstrm: BitStream, requests: dict[str: any], piIndication: bool = False): + +def packEBytes(bitstrm: BitStream, requests: dict[str:any], piIndication: bool = False): codec = requests[REQUESTS.CODEC] if REQUESTS.CODEC in requests.keys() else NO_REQ bitrate = requests[REQUESTS.BR] if REQUESTS.BR in requests.keys() else 0 bandwidth = requests[REQUESTS.BW] if REQUESTS.BW in requests.keys() else NO_REQ camode = requests[REQUESTS.CA] if REQUESTS.CA in requests.keys() else NO_REQ format = requests[REQUESTS.FMT] if REQUESTS.FMT in requests.keys() else NO_REQ - subFormat = requests[REQUESTS.SUBFMT] if REQUESTS.SUBFMT in requests.keys() else NO_REQ - srcfg = SRCONFIG(requests[REQUESTS.SRCFG]) if REQUESTS.SRCFG in requests.keys() else None + subFormat = ( + requests[REQUESTS.SUBFMT] if REQUESTS.SUBFMT in requests.keys() else NO_REQ + ) + srcfg = ( + SRCONFIG(requests[REQUESTS.SRCFG]) + if REQUESTS.SRCFG in requests.keys() + else None + ) # Check if any request needs to be sent - isInitialEByteNeeded = piIndication or bitrate != 0 or bandwidth != NO_REQ or camode != NO_REQ or \ - format != NO_REQ or subFormat != NO_REQ or srcfg != None + isInitialEByteNeeded = ( + piIndication + or bitrate != 0 + or bandwidth != NO_REQ + or camode != NO_REQ + or format != NO_REQ + or subFormat != NO_REQ + or srcfg != None + ) if not isInitialEByteNeeded: return @@ -795,19 +1125,27 @@ def packEBytes(bitstrm: BitStream, requests: dict[str: any], piIndication: bool T = 7 BR = 15 else: - mapBandwidthToTBit = {BANDWIDTH.NB: 0, BANDWIDTH.WB: 2, BANDWIDTH.SWB: 3, BANDWIDTH.FB: 4, NO_REQ: 2}[bandwidth] - T = {CODECS.AMRWB: 1, CODECS.IVAS: 7, CODECS.EVS: mapBandwidthToTBit} [codec] + mapBandwidthToTBit = { + BANDWIDTH.NB: 0, + BANDWIDTH.WB: 2, + BANDWIDTH.SWB: 3, + BANDWIDTH.FB: 4, + NO_REQ: 2, + }[bandwidth] + T = {CODECS.AMRWB: 1, CODECS.IVAS: 7, CODECS.EVS: mapBandwidthToTBit}[codec] BR = getListIndex(requestBitratesForCodec[codec], bitrate) - assert BR >= cmrLookup [T].startIdx and BR < cmrLookup [T].endIdx, "EVS Bitrate Index and Bandwidth Combination cannot be requested" + assert ( + BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx + ), "EVS Bitrate Index and Bandwidth Combination cannot be requested" # Write the Initial E-Byte - bitstrm.append(f'bool={True}') #E-Byte H=1 - bitstrm.append(f'uint:3={T}') - bitstrm.append(f'uint:4={BR}') + bitstrm.append(f"bool={True}") # E-Byte H=1 + bitstrm.append(f"uint:3={T}") + bitstrm.append(f"uint:4={BR}") # Subsequent E-bytes follow if piIndication: - bitstrm.append('hex:8=A0') + bitstrm.append("hex:8=A0") if codec != CODECS.IVAS: return @@ -815,43 +1153,50 @@ def packEBytes(bitstrm: BitStream, requests: dict[str: any], piIndication: bool # Bandwidth E-Byte if bandwidth != NO_REQ: bw = {BANDWIDTH.WB: 0, BANDWIDTH.SWB: 1, BANDWIDTH.FB: 2}[bandwidth] - bitstrm.append('hex:4=8') - bitstrm.append(f'uint:4={bw}') + bitstrm.append("hex:4=8") + bitstrm.append(f"uint:4={bw}") # Coded Format/SubFormat Request E-Byte if subFormat in SUBFORMATS: subFmt = getListIndex(codedSubFormats, subFormat) - bitstrm.append('hex:8=9F') #S=0, FMT=111 - bitstrm.append(f'uint:8={subFmt}') + bitstrm.append("hex:8=9F") # S=0, FMT=111 + bitstrm.append(f"uint:8={subFmt}") elif format in FORMATS: fmt = getListIndex(codedFormats, format) - bitstrm.append('hex:4=9') - bitstrm.append(f'bool={False}') # S=0 - bitstrm.append(f'uint:3={fmt}') + bitstrm.append("hex:4=9") + bitstrm.append(f"bool={False}") # S=0 + bitstrm.append(f"uint:3={fmt}") # SR Config E-Byte with srcfg: - bitstrm.append('hex:4=B') - bitstrm.append(f'bool={srcfg.diegetic}') - bitstrm.append(f'bool={srcfg.yaw}') - bitstrm.append(f'bool={srcfg.pitch}') - bitstrm.append(f'bool={srcfg.roll}') + bitstrm.append("hex:4=B") + bitstrm.append(f"bool={srcfg.diegetic}") + bitstrm.append(f"bool={srcfg.yaw}") + bitstrm.append(f"bool={srcfg.pitch}") + bitstrm.append(f"bool={srcfg.roll}") + def unpackAUFrames(bitstrm: ConstBitStream, frameList: list[FRAME]): try: - #Unpack Frame AUs here - for (idx, frm) in enumerate(frameList): - auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode + # Unpack Frame AUs here + for idx, frm in enumerate(frameList): + auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode frm.au = bitstrm.read(auSize * 8).tobytes() except ReadError as error: - print ("Underflow in AU Frames parsing during unpacking, error = {}".format(error)) + print( + "Underflow in AU Frames parsing during unpacking, error = {}".format(error) + ) + def packAUFrames(bitstrm: BitStream, frameList: list[FRAME]): for frm in frameList: bitstrm.append(frm.au) bitstrm.bytealign() -def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> list[FRAME]: + +def unpackToCBytes( + bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS +) -> list[FRAME]: F = True frmList = list() @@ -861,9 +1206,9 @@ def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> FT = bitstrm.read(2).uint BR = bitstrm.read(4).uint frm = FRAME(timestamp=rtpTimestamp) - if FT == 1 : + if FT == 1: frm.codec = CODECS.IVAS - if BR == 14 : + if BR == 14: supportedBitrates = [-1, 256000, 384000, 512000] reserved = bitstrm.read(1) D = bitstrm.read(1).bool @@ -872,8 +1217,11 @@ def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> reserved = bitstrm.read(3) if SR_BR == 0: raise Exception("Reserved bitrate in SR Config ToC Indicated") - frm.srInfo = SRINFO(bitrate=supportedBitrates[SR_BR], diegetic=D, - transportCodec= SRCODEC.LC3PLUS if C else SRCODEC.LCLD) + frm.srInfo = SRINFO( + bitrate=supportedBitrates[SR_BR], + diegetic=D, + transportCodec=SRCODEC.LC3PLUS if C else SRCODEC.LCLD, + ) else: frm.bitrate = ivasBitrates[BR] elif FT == 0: @@ -881,7 +1229,7 @@ def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> frm.codec = CODECS.EVS if BR != 15 else Codec if BR == 13: raise Exception("Reserved bitrate in EVS ToC Indicated") - frm.speechLost = (BR == 14) + frm.speechLost = BR == 14 frm.bitrate = evsBitrates[BR] if BR < 13 else 0 else: frm.codec = CODECS.AMRWB @@ -893,25 +1241,28 @@ def unpackToCBytes(bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS) -> rtpTimestamp += 320 frmList.append(frm) if F: - #skip all frame specific E-bytes before next header - while (bitstrm.read(1).bool): + # skip all frame specific E-bytes before next header + while bitstrm.read(1).bool: print("Skipping unsupported frame specific subsequent Ebytes") reserved = bitstrm.read(7) except ReadError as error: - print ("Underflow in ToC parsing during unpacking, error = {}".format(error)) + print("Underflow in ToC parsing during unpacking, error = {}".format(error)) return frmList + def packToCBytes(bitstrm: BitStream, frameList: list[FRAME]): numFrames = len(frameList) for idx, frame in enumerate(frameList): - F = (idx != (numFrames - 1)) + F = idx != (numFrames - 1) FT = 0 BR = 0 if frame.frmSizeBits == 0: - FT = 3 if frame.codec == CODECS.AMRWB else 0 # Only AMRWB or EVS support 0 frame case - BR = 14 if frame.speechLost else 15 #SPEECH_LOST or NO_DATA + FT = ( + 3 if frame.codec == CODECS.AMRWB else 0 + ) # Only AMRWB or EVS support 0 frame case + BR = 14 if frame.speechLost else 15 # SPEECH_LOST or NO_DATA elif frame.codec == CODECS.AMRWB: FT = 3 BR = getListIndex(amrwbBitrates[0:10], frame.bitrate) @@ -924,17 +1275,18 @@ def packToCBytes(bitstrm: BitStream, frameList: list[FRAME]): assert BR >= 0, "Index for table not found" - bitstrm.append(f'bool={False}') #ToC 0 bit - bitstrm.append(f'bool={F}') #Frame follows bit - bitstrm.append(f'uint:2={FT}') #Frame Type - bitstrm.append(f'uint:4={BR}') #Frame Type + bitstrm.append(f"bool={False}") # ToC 0 bit + bitstrm.append(f"bool={F}") # Frame follows bit + bitstrm.append(f"uint:2={FT}") # Frame Type + bitstrm.append(f"uint:4={BR}") # Frame Type if frame.srInfo and frame.codec == CODECS.IVAS: - SRBR = (frame.srInfo.bitrate//128000) - 1 - bitstrm.append(f'bool={False}') #ToC 0 bit - bitstrm.append(f'bool={frame.srInfo.diegetic}') - bitstrm.append(f'bool={frame.srInfo.transportCodec == SRCODEC.LC3PLUS}') - bitstrm.append(f'uint:2={SRBR}') - bitstrm.append('uint:3=0') + SRBR = (frame.srInfo.bitrate // 128000) - 1 + bitstrm.append(f"bool={False}") # ToC 0 bit + bitstrm.append(f"bool={frame.srInfo.diegetic}") + bitstrm.append(f"bool={frame.srInfo.transportCodec == SRCODEC.LC3PLUS}") + bitstrm.append(f"uint:2={SRBR}") + bitstrm.append("uint:3=0") + def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: piDataList = list[PIDATA]() @@ -942,7 +1294,7 @@ def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: # PI Data if Indicated PF = True piTimeStamps = rtpTimestamp - while PF : + while PF: PF = bitstrm.read(1).bool PM = bitstrm.read(2).uint PiType = bitstrm.read(5).uint @@ -956,17 +1308,32 @@ def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: if PiTypeNames[PiType] != PIDATAS.NO_PI_DATA: piDataList.append( - PIDATA(timestamp=piTimeStamps if PM != 3 else rtpTimestamp, # Generic Pi has base timestamp + PIDATA( + timestamp=( + piTimeStamps if PM != 3 else rtpTimestamp + ), # Generic Pi has base timestamp type=PiTypeNames[PiType], - data=PiFrameData)) + data=PiFrameData, + ) + ) piTimeStamps += 320 if PM == 2 else 0 except ReadError as error: - print ("Underflow before completion of unpacking, error = {}".format(error)) + print("Underflow before completion of unpacking, error = {}".format(error)) return piDataList -def packPiData(bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataList: list[PIDATA]): + +def packPiData( + bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataList: list[PIDATA] +): # sort the piDataList by timestamp and eliminate data where timestamp is OOB for this packet - piDataList = [data for data in piDataList if (data.timestamp >= rtpTimestampBounds[0] and data.timestamp < rtpTimestampBounds[1])] + piDataList = [ + data + for data in piDataList + if ( + data.timestamp >= rtpTimestampBounds[0] + and data.timestamp < rtpTimestampBounds[1] + ) + ] sorted(piDataList, key=lambda data: data.timestamp) numPiData = len(piDataList) @@ -982,7 +1349,9 @@ def packPiData(bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataLi for ts, dataList in piDataDict.items(): while curTimestamp < ts: # Inset NO_PI_DATA till current Timestamp is reached - bitstrm.append('hex:16=DF00') #PI Frame follows, Last PI header for this frame, NO_PI_DATA, size=0 + bitstrm.append( + "hex:16=DF00" + ) # PI Frame follows, Last PI header for this frame, NO_PI_DATA, size=0 curTimestamp += 320 for idx, data in enumerate(dataList): @@ -993,12 +1362,14 @@ def packPiData(bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataLi PIDataPacker[TYPE](pack, data.data) assert (pack.pos % 8) == 0, "PI data must be byte aligned" SIZE = pack.pos // 8 - assert SIZE < MAX_PACKED_PI_SIZE, f"Packed PI Size should be less than MAX_PACKED_PI_SIZE ({MAX_PACKED_PI_SIZE})" - - bitstrm.append(f'uint:1={PF}') - bitstrm.append(f'uint:2={PM}') - bitstrm.append(f'uint:5={TYPE}') - bitstrm.append(f'uint:8={SIZE}') + assert ( + SIZE < MAX_PACKED_PI_SIZE + ), f"Packed PI Size should be less than MAX_PACKED_PI_SIZE ({MAX_PACKED_PI_SIZE})" + + bitstrm.append(f"uint:1={PF}") + bitstrm.append(f"uint:2={PM}") + bitstrm.append(f"uint:5={TYPE}") + bitstrm.append(f"uint:8={SIZE}") bitstrm.append(pack) numPiData -= 1 curTimestamp += 320 @@ -1009,7 +1380,7 @@ def packPiData(bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataLi class IvasPayload: frameList: list[FRAME] = field(default_factory=list) piDataList: list[PIDATA] = field(default_factory=list) - requests: dict [str, any] = field(default_factory=dict) + requests: dict[str, any] = field(default_factory=dict) def pack(self, bitstrm: BitStream) -> int: piIndication = len(self.piDataList) > 0 @@ -1018,7 +1389,11 @@ class IvasPayload: packToCBytes(bitstrm, self.frameList) packAUFrames(bitstrm, self.frameList) if piIndication: - packPiData(bitstrm, (self.frameList[0].timestamp, self.frameList[-1].timestamp + 320), self.piDataList) + packPiData( + bitstrm, + (self.frameList[0].timestamp, self.frameList[-1].timestamp + 320), + self.piDataList, + ) return numFrames @classmethod @@ -1039,21 +1414,23 @@ class IvasPayload: return cls(frameList=frameList, piDataList=piDataList, requests=requests) + @dataclass class IvasPacket: hdr: RTPHDR = field(default_factory=RTPHDR) payload: IvasPayload = field(default_factory=IvasPayload) - def pack(self, bitstrm: BitStream) : + def pack(self, bitstrm: BitStream): self.hdr.pack(bitstrm) numFrames = self.payload.pack(bitstrm) self.hdr.updateHeader(numFrames) + class IvasRtp: - def __init__(self, numFramesPerPacket = 4, codec: CODECS = CODECS.IVAS): + def __init__(self, numFramesPerPacket=4, codec: CODECS = CODECS.IVAS): self.numFramesPerPacket = numFramesPerPacket self.packets = list[IvasPacket]() - self.Codec: CODECS = codec# Track last frame's codec + self.Codec: CODECS = codec # Track last frame's codec self.requests = dict() self.piData = dict() @@ -1062,8 +1439,8 @@ class IvasRtp: packets = list() for packet in self.packets: packetDict = asdict(packet) - for frame in packetDict['payload']['frameList']: - frame['au'] = base64.b64encode(frame['au']).decode('utf-8') + for frame in packetDict["payload"]["frameList"]: + frame["au"] = base64.b64encode(frame["au"]).decode("utf-8") packets.append(packetDict) json_output = json.dumps(packets, indent=4) fd.write(json_output) @@ -1092,7 +1469,10 @@ class IvasRtp: data = list() for orientation in dataDict: data.append(ORIENTATION(**orientation)) - elif "ORIENTATION" in piTypes or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION: + elif ( + "ORIENTATION" in piTypes + or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION + ): data = ORIENTATION(**dataDict) elif piTypes == PIDATAS.ACOUSTIC_ENVIRONMENT: data = ACOUSTIC_ENVIRONMENT(**dataDict) @@ -1102,19 +1482,30 @@ class IvasRtp: data.append(AUDIO_DESCRIPTION(**desc)) elif piTypes == PIDATAS.DIEGETIC_TYPE: data = DIEGETIC_TYPE(**dataDict) - elif piTypes == PIDATAS.LISTENER_POSITION or piTypes == PIDATAS.R_ISM_POSITION: + elif ( + piTypes == PIDATAS.LISTENER_POSITION + or piTypes == PIDATAS.R_ISM_POSITION + ): data = POSITION(**dataDict) elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) else: assert False, "Unhandled PI Data" - piDataList.append(PIDATA(timestamp=startTimestamp, type=piTypes, data=data)) + piDataList.append( + PIDATA(timestamp=startTimestamp, type=piTypes, data=data) + ) startTimestamp += 320 return piDataList - def packG192File(self, g192File: Path, rtpDumpOut: Path, piData: dict = None, requestsData: dict = None): + def packG192File( + self, + g192File: Path, + rtpDumpOut: Path, + piData: dict = None, + requestsData: dict = None, + ): packet = IvasPacket() - packet.hdr.sequenceNum=int("0xFFFF", 16) + packet.hdr.sequenceNum = int("0xFFFF", 16) packet.hdr.timestamp = 0 packet.hdr.ssrc = int("0xDEADBEEF", 16) @@ -1130,61 +1521,115 @@ class IvasRtp: while refBitStrm.pos < refBitStrm.len: sync = hex(refBitStrm.read(16).intle) nBits = refBitStrm.read(16).intle - assert sync == "0x6b21", "G192 syncword not found at start of packet" + assert ( + sync == "0x6b21" + ), "G192 syncword not found at start of packet" writer = BitStream() for _ in range(nBits): bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" writer.append(bit) - frames.append(FRAME(codec=self.Codec, frmSizeBits=nBits, bitrate=nBits * 50, speechLost=False, timestamp=timestamp, au=writer.tobytes())) - if (len(frames) == self.numFramesPerPacket) or (refBitStrm.pos == refBitStrm.len): + frames.append( + FRAME( + codec=self.Codec, + frmSizeBits=nBits, + bitrate=nBits * 50, + speechLost=False, + timestamp=timestamp, + au=writer.tobytes(), + ) + ) + if (len(frames) == self.numFramesPerPacket) or ( + refBitStrm.pos == refBitStrm.len + ): rtpBitstrm = BitStream() numFrames = len(frames) - packet.payload = IvasPayload(frameList=frames, piDataList=self.piDataReader(piTimestamps, piTimestamps + (numFrames * 320)), requests=self.requestReader(piTimestamps)) + packet.payload = IvasPayload( + frameList=frames, + piDataList=self.piDataReader( + piTimestamps, piTimestamps + (numFrames * 320) + ), + requests=self.requestReader(piTimestamps), + ) packet.pack(bitstrm=rtpBitstrm) - fout.write(struct.pack('i', rtpBitstrm.bytepos)) + fout.write(struct.pack("i", rtpBitstrm.bytepos)) fout.write(rtpBitstrm.tobytes()) frames = list() piTimestamps += numFrames * 320 timestamp += 320 - def getPackets(self): return self.packets def unpackFile(self, rtpDumpFile): - with open (rtpDumpFile, mode="rb") as fd: + with open(rtpDumpFile, mode="rb") as fd: while True: size = fd.read(4) if not size: break - size = struct.unpack( 'i', size)[0] + size = struct.unpack("i", size)[0] packet = fd.read(size) if not packet: break self.packets.append(self.unpackPacket(packet)) - def unpackPacket(self, packet) -> IvasPacket : + def unpackPacket(self, packet) -> IvasPacket: bitStrm = ConstBitStream(packet) hdr = RTPHDR.unpack(bitStrm) - payload = IvasPayload.unpack(bitStrm, rtpTimestamp=hdr.timestamp, Codec=self.Codec) - self.Codec = payload.frameList[-1].codec #Last Frame's codec for next frame for NO_DATA case + payload = IvasPayload.unpack( + bitStrm, rtpTimestamp=hdr.timestamp, Codec=self.Codec + ) + self.Codec = payload.frameList[ + -1 + ].codec # Last Frame's codec for next frame for NO_DATA case return IvasPacket(hdr=hdr, payload=payload) + class ArgsParser: def __init__(self): self.parser = argparse.ArgumentParser() - self.parser.add_argument("-r", "--rtpdump", type=str, default=None, help="RTP Dump to unpack") - self.parser.add_argument("-j", "--json", type=str, default="unpack.json", help="Output unpacked RTP frames to JSON file") - self.parser.add_argument("-g", "--g192", type=str, default=None, help="G192 bitstream input for RTP Packing") - self.parser.add_argument("-f", "--framesPerPacket", type=int, default=1, help="Number of IVAS frames per RTP Packet") - self.parser.add_argument("-o", "--outrtpdump", type=str, default="output.rtpdump", help="Output RTP Dump file") - self.parser.add_argument("-p", "--piDataJson", type=str, default=None, help="piData to be packed") - self.parser.add_argument("-x", "--requestsJson", type=str, default=None, help="Requests to be packed") + self.parser.add_argument( + "-r", "--rtpdump", type=str, default=None, help="RTP Dump to unpack" + ) + self.parser.add_argument( + "-j", + "--json", + type=str, + default="unpack.json", + help="Output unpacked RTP frames to JSON file", + ) + self.parser.add_argument( + "-g", + "--g192", + type=str, + default=None, + help="G192 bitstream input for RTP Packing", + ) + self.parser.add_argument( + "-f", + "--framesPerPacket", + type=int, + default=1, + help="Number of IVAS frames per RTP Packet", + ) + self.parser.add_argument( + "-o", + "--outrtpdump", + type=str, + default="output.rtpdump", + help="Output RTP Dump file", + ) + self.parser.add_argument( + "-p", "--piDataJson", type=str, default=None, help="piData to be packed" + ) + self.parser.add_argument( + "-x", "--requestsJson", type=str, default=None, help="Requests to be packed" + ) def parse(self): args = self.parser.parse_args() return args + if __name__ == "__main__": args = ArgsParser().parse() rtp = IvasRtp(numFramesPerPacket=args.framesPerPacket) @@ -1201,4 +1646,9 @@ if __name__ == "__main__": if args.requestsJson: with open(args.requestsJson) as f: requestsData = json.load(f) - rtp.packG192File(g192File=args.g192, rtpDumpOut=args.outrtpdump, piData=piData, requestsData=requestsData) + rtp.packG192File( + g192File=args.g192, + rtpDumpOut=args.outrtpdump, + piData=piData, + requestsData=requestsData, + ) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index c1832202bb..69b85dc9af 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -68,18 +68,19 @@ sys.path.append(ROOT_DIR) from tests.conftest import EncoderFrontend, DecoderFrontend + @pytest.mark.parametrize("dtx", [False, True]) @pytest.mark.parametrize("bitrate", [6600, 12650, 23850]) @pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) -def test_rtp_bitstream_amrwb ( +def test_rtp_bitstream_amrwb( test_info, bitrate: int, dtx: bool, framesPerPacket: int, dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend + dut_decoder_frontend: DecoderFrontend, ): - run_rtp_bitstream_tests ( + run_rtp_bitstream_tests( CODECS.AMRWB, bitrate, "WB", @@ -88,15 +89,16 @@ def test_rtp_bitstream_amrwb ( dtx, framesPerPacket, dut_encoder_frontend, - dut_decoder_frontend + dut_decoder_frontend, ) + @pytest.mark.parametrize("dtx", [False, True]) @pytest.mark.parametrize("bitrate", [9600, 24400, 128000]) @pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) @pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) @pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) -def test_rtp_bitstream_evs ( +def test_rtp_bitstream_evs( test_info, bitrate: int, bandwidth: str, @@ -104,9 +106,9 @@ def test_rtp_bitstream_evs ( dtx: bool, framesPerPacket: int, dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend + dut_decoder_frontend: DecoderFrontend, ): - run_rtp_bitstream_tests ( + run_rtp_bitstream_tests( CODECS.EVS, bitrate, bandwidth, @@ -115,7 +117,7 @@ def test_rtp_bitstream_evs ( dtx, framesPerPacket, dut_encoder_frontend, - dut_decoder_frontend + dut_decoder_frontend, ) @@ -123,16 +125,16 @@ def test_rtp_bitstream_evs ( @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) @pytest.mark.parametrize("framesPerPacket", [8]) -def test_rtp_bitstream_ivas_nodtx ( +def test_rtp_bitstream_ivas_nodtx( test_info, bitrate: int, bandwidth: str, format: str, framesPerPacket: int, dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend + dut_decoder_frontend: DecoderFrontend, ): - run_rtp_bitstream_tests ( + run_rtp_bitstream_tests( CODECS.IVAS, bitrate, bandwidth, @@ -141,23 +143,24 @@ def test_rtp_bitstream_ivas_nodtx ( False, framesPerPacket, dut_encoder_frontend, - dut_decoder_frontend + dut_decoder_frontend, ) + @pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA"]) @pytest.mark.parametrize("framesPerPacket", [1, 3]) -def test_rtp_bitstream_ivas_dtx ( +def test_rtp_bitstream_ivas_dtx( test_info, bitrate: int, bandwidth: str, format: str, framesPerPacket: int, dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend + dut_decoder_frontend: DecoderFrontend, ): - run_rtp_bitstream_tests ( + run_rtp_bitstream_tests( CODECS.IVAS, bitrate, bandwidth, @@ -166,24 +169,63 @@ def test_rtp_bitstream_ivas_dtx ( True, framesPerPacket, dut_encoder_frontend, - dut_decoder_frontend + dut_decoder_frontend, ) + def generateRequests(startTs: int, endTs: int) -> dict: requests = dict() return requests + 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) - somePosition = lambda : POSITION( x=random.randint(-32788, 32767)/100.0, y=random.randint(-32788, 32767)/100.0, z=random.randint(-32788, 32767)/100.0) - someDesc = lambda : AUDIO_DESCRIPTION(isSpeech=bool(random.getrandbits(1)), isMusic=bool(random.getrandbits(1)), isAmbiance=bool(random.getrandbits(1)), isEditable=bool(random.getrandbits(1)), isBinaural=bool(random.getrandbits(1))) - someDAS = lambda : DYNAMIC_AUDIO_SUPPRESSION(preferSpeech=bool(random.getrandbits(1)), preferMusic=bool(random.getrandbits(1)), preferAmbiance=bool(random.getrandbits(1)), level=random.randint(0, 15)) - 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)) - someAuFocusLvl = lambda : AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) + 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, + ) + somePosition = lambda: POSITION( + x=random.randint(-32788, 32767) / 100.0, + y=random.randint(-32788, 32767) / 100.0, + z=random.randint(-32788, 32767) / 100.0, + ) + someDesc = lambda: AUDIO_DESCRIPTION( + isSpeech=bool(random.getrandbits(1)), + isMusic=bool(random.getrandbits(1)), + isAmbiance=bool(random.getrandbits(1)), + isEditable=bool(random.getrandbits(1)), + isBinaural=bool(random.getrandbits(1)), + ) + someDAS = lambda: DYNAMIC_AUDIO_SUPPRESSION( + preferSpeech=bool(random.getrandbits(1)), + preferMusic=bool(random.getrandbits(1)), + preferAmbiance=bool(random.getrandbits(1)), + level=random.randint(0, 15), + ) + 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, + ) + ) + someAuFocusLvl = lambda: AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] for ts in range(startTs, endTs, 320): @@ -198,7 +240,9 @@ def generatePiData(startTs: int, endTs: int) -> dict: pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] pidata["DIEGETIC_TYPE"] = someDIG() - pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT(aeid=random.randint(0, 127)) + pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT( + aeid=random.randint(0, 127) + ) data[str(ts)] = pidata return data @@ -206,7 +250,10 @@ def generatePiData(startTs: int, endTs: int) -> dict: def isEqualFrame(refFrame: bytes, dutFrame: bytes): assert len(refFrame) == len(dutFrame), "Encoded frame size is different" for refByte, dutByte in zip(refFrame, dutFrame): - assert refByte == dutByte, "Encoded frames should be bitexact between ref and rtpdump" + assert ( + refByte == dutByte + ), "Encoded frames should be bitexact between ref and rtpdump" + def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): assert abs(ref.w - dut.w) < 0.0001, "Scene Orientation PI Data mismatch in w" @@ -214,54 +261,85 @@ def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" + def isEqualPosition(ref: POSITION, dut: POSITION): assert abs(ref.x - dut.x) < 0.3, "Position PI Data mismatch in x" assert abs(ref.y - dut.y) < 0.3, "Position PI Data mismatch in y" assert abs(ref.z - dut.z) < 0.3, "Position PI Data mismatch in z" + def isEqualAD(ref: AUDIO_DESCRIPTION, dut: AUDIO_DESCRIPTION): - assert ref.isSpeech == dut.isSpeech, "Audio Description PI Data mismatch in isSpeech" + assert ( + ref.isSpeech == dut.isSpeech + ), "Audio Description PI Data mismatch in isSpeech" assert ref.isMusic == dut.isMusic, "Audio Description PI Data mismatch in isMusic" - assert ref.isAmbiance == dut.isAmbiance, "Audio Description PI Data mismatch in isAmbiance" - assert ref.isEditable == dut.isEditable, "Audio Description PI Data mismatch in isEditable" - assert ref.isBinaural == dut.isBinaural, "Audio Description PI Data mismatch in isBinaural" + assert ( + ref.isAmbiance == dut.isAmbiance + ), "Audio Description PI Data mismatch in isAmbiance" + assert ( + ref.isEditable == dut.isEditable + ), "Audio Description PI Data mismatch in isEditable" + assert ( + ref.isBinaural == dut.isBinaural + ), "Audio Description PI Data mismatch in isBinaural" + def isEqualDAS(ref: DYNAMIC_AUDIO_SUPPRESSION, dut: DYNAMIC_AUDIO_SUPPRESSION): - assert ref.preferSpeech == dut.preferSpeech, "Dynamic Audio Suppression PI Data mismatch in preferSpeech" - assert ref.preferMusic == dut.preferMusic, "Dynamic Audio Suppression PI Data mismatch in preferMusic" - assert ref.preferAmbiance == dut.preferAmbiance, "Dynamic Audio Suppression PI Data mismatch in preferAmbiance" + assert ( + ref.preferSpeech == dut.preferSpeech + ), "Dynamic Audio Suppression PI Data mismatch in preferSpeech" + assert ( + ref.preferMusic == dut.preferMusic + ), "Dynamic Audio Suppression PI Data mismatch in preferMusic" + assert ( + ref.preferAmbiance == dut.preferAmbiance + ), "Dynamic Audio Suppression PI Data mismatch in preferAmbiance" assert ref.level == dut.level, "Dynamic Audio Suppression PI Data mismatch in level" + def isEqualDiegetic(ref: DIEGETIC_TYPE, dut: DIEGETIC_TYPE): for r, d in zip(ref.isDigetic, dut.isDigetic): assert r == d, f"Diegetic PI Data mismatch {r} != {d}" + def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): assert ref.aeid == dut.aeid, "Acoustic Env PI Data mismatch in Acoustic Identifier" assert len(ref.rt60) == len(dut.rt60), "Acoustic Env PI Data mismatch in len(rt60)" assert len(ref.dsr) == len(dut.dsr), "Acoustic Env PI Data mismatch in len(dsr)" assert len(ref.dim) == len(dut.dim), "Acoustic Env PI Data mismatch in len(dim)" - assert len(ref.abscoeff) == len(dut.abscoeff), "Acoustic Env PI Data mismatch in len(abscoeff)" + assert len(ref.abscoeff) == len( + dut.abscoeff + ), "Acoustic Env PI Data mismatch in len(abscoeff)" for r, d in zip(ref.rt60, dut.rt60): assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" + def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): if ref.direction is not None or dut.direction is not None: assert ref.direction is not None, "Audio Focus PI Data missing direction" 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.0001, "Audio Focus PI Data mismatch in direction w" - assert abs(ref.direction['x'] - dut.direction.x) < 0.0001, "Audio Focus PI Data mismatch in direction x" - assert abs(ref.direction['y'] - dut.direction.y) < 0.0001, "Audio Focus PI Data mismatch in direction y" - assert abs(ref.direction['z'] - dut.direction.z) < 0.0001, "Audio Focus PI Data mismatch in direction z" + assert ( + abs(ref.direction["w"] - dut.direction.w) < 0.0001 + ), "Audio Focus PI Data mismatch in direction w" + assert ( + abs(ref.direction["x"] - dut.direction.x) < 0.0001 + ), "Audio Focus PI Data mismatch in direction x" + assert ( + abs(ref.direction["y"] - dut.direction.y) < 0.0001 + ), "Audio Focus PI Data mismatch in direction y" + assert ( + abs(ref.direction["z"] - dut.direction.z) < 0.0001 + ), "Audio Focus PI Data mismatch in direction z" assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" + class CSVREADER: def __init__(self, csvFile: Path): self.rIdx = 0 self.rows = [] - with open(csvFile, 'r') as fd: - self.rows = [ row for row in csv.reader(fd) ] + with open(csvFile, "r") as fd: + self.rows = [row for row in csv.reader(fd)] self.count = len(self.rows) def next(self) -> list[float]: @@ -269,14 +347,14 @@ class CSVREADER: self.rIdx += 1 if self.rIdx == self.count: self.rIdx = 0 - return [ float(x) for x in row ] + return [float(x) for x in row] -class RTPVALIDATE: - DTX_BITRATES = { CODECS.IVAS: 5200, CODECS.EVS: 2400, CODECS.AMRWB: 1750 } +class RTPVALIDATE: + DTX_BITRATES = {CODECS.IVAS: 5200, CODECS.EVS: 2400, CODECS.AMRWB: 1750} - def __init__(self, codec = CODECS.IVAS, bitrate = 24400, framesPerPacket = 1, dtx = False): + def __init__(self, codec=CODECS.IVAS, bitrate=24400, framesPerPacket=1, dtx=False): self.framesPerPacket = framesPerPacket self.dtx = dtx self.codec = codec @@ -288,8 +366,8 @@ class RTPVALIDATE: self.validatePiData = False self.g192File = None self.frameIdx = 0 - #PI DATA - self.readers: dict[str : CSVREADER] = dict() + # PI DATA + self.readers: dict[str:CSVREADER] = dict() def setPiDataFiles(self, piFiles: tuple[Path]): self.validatePiData = True @@ -316,12 +394,18 @@ class RTPVALIDATE: def payload(self, payload: IvasPayload): self.numFrames = len(payload.frameList) - assert self.numFrames >= 1 and self.numFrames <= self.framesPerPacket, f"Packet must have atleast 1 frame and atmost {self.framesPerPacket} frames" + assert ( + self.numFrames >= 1 and self.numFrames <= self.framesPerPacket + ), f"Packet must have atleast 1 frame and atmost {self.framesPerPacket} frames" for frame in payload.frameList: assert self.codec == frame.codec, "Codec mismatch in RTP Payload" if self.dtx: - assert frame.bitrate in (self.bitrate, RTPVALIDATE.DTX_BITRATES[self.codec], 0), "Bitrate mismatch in RTP Payload in DTX mode" + assert frame.bitrate in ( + self.bitrate, + RTPVALIDATE.DTX_BITRATES[self.codec], + 0, + ), "Bitrate mismatch in RTP Payload in DTX mode" else: assert frame.bitrate == self.bitrate, "Bitrate mismatch in RTP Payload" @@ -335,21 +419,38 @@ class RTPVALIDATE: def piData(self, piDataList: list[PIDATA]): for piData in piDataList: - assert piData.timestamp >= self.timestamp and piData.timestamp < self.timestamp + (self.numFrames * 320), "PI Data Time stamp is OOB" - assert piData.type == PiTypeNames[0] or piData.type == PiTypeNames[1], "PI Data is neither Scene nor Device Orientation" + assert ( + piData.timestamp >= self.timestamp + and piData.timestamp < self.timestamp + (self.numFrames * 320) + ), "PI Data Time stamp is OOB" + assert ( + piData.type == PiTypeNames[0] or piData.type == PiTypeNames[1] + ), "PI Data is neither Scene nor Device Orientation" assert type(piData.data) == ORIENTATION, "Orientation type data expected" - #validate the PI Data provided is the PI data in the packet + # validate the PI Data provided is the PI data in the packet refData = self.readers[piData.type].next() - isEqualOrientation(ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), piData.data) + isEqualOrientation( + ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), + piData.data, + ) + + @dataclass class TVARGS: TVROOT = Path(ROOT_DIR).joinpath("scripts/testv") + def __init__(self): self.tvDict = dict() - self.sceneFile = Path(ROOT_DIR).joinpath("scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv").absolute() - self.deviceFile = Path(ROOT_DIR).joinpath("scripts/trajectories/headrot-1.5s.csv").absolute() + self.sceneFile = ( + Path(ROOT_DIR) + .joinpath("scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv") + .absolute() + ) + self.deviceFile = ( + Path(ROOT_DIR).joinpath("scripts/trajectories/headrot-1.5s.csv").absolute() + ) - def add(self, fmt:str, inputFile:str, args:list[str] = []): + def add(self, fmt: str, inputFile: str, args: list[str] = []): inputFile = str(TVARGS.TVROOT.joinpath(inputFile).absolute()) if fmt == "MASA": args[2] = str(TVARGS.TVROOT.joinpath(args[2]).absolute()) @@ -359,15 +460,21 @@ class TVARGS: return self.tvDict[fmt][0] def args(self, fmt, addPI=False) -> list[str]: - args = [ x for x in self.tvDict[fmt][1] ] + args = [x for x in self.tvDict[fmt][1]] if addPI and fmt != "MONO": - args += [ "-scene_orientation", str(self.sceneFile), "-device_orientation", str(self.deviceFile) ] + args += [ + "-scene_orientation", + str(self.sceneFile), + "-device_orientation", + str(self.deviceFile), + ] return args def piFiles(self) -> tuple[Path]: return (self.sceneFile, self.deviceFile) -def run_rtp_bitstream_tests ( + +def run_rtp_bitstream_tests( codec: CODECS, bitrate: int, bandwidth: str, @@ -376,12 +483,12 @@ def run_rtp_bitstream_tests ( dtx: bool, framesPerPacket: int, dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend + dut_decoder_frontend: DecoderFrontend, ): tvArgs = TVARGS() tvArgs.add("MONO", "stv48n.wav") - if dtx: #use bigger file for dtx stereo + if dtx: # use bigger file for dtx stereo tvArgs.add("STEREO", "stvST48n.wav", ["-stereo"]) else: tvArgs.add("STEREO", "stv2MASA2TC48c.wav", ["-stereo"]) @@ -390,25 +497,52 @@ def run_rtp_bitstream_tests ( tvArgs.add("MASA", "stv2MASA2TC48c.wav", ["-masa", "2", "stv2MASA2TC48c.met"]) tvArgs.add("SBA", "stvFOA48c.wav", ["-sba", "+1"]) - if (bitrate > 24400 and bandwidth == "NB") or (format == "STEREO" and bitrate > 256000): + if (bitrate > 24400 and bandwidth == "NB") or ( + format == "STEREO" and bitrate > 256000 + ): pytest.skip() - print("Test: dut_encoder_frontend={}, dtx={}, codec:={}, bitrate={}, bandwidth={}, caMode={}, format={},".format(dut_encoder_frontend._path, dtx, codec, bitrate, bandwidth, caMode, format)) + print( + "Test: dut_encoder_frontend={}, dtx={}, codec:={}, bitrate={}, bandwidth={}, caMode={}, format={},".format( + dut_encoder_frontend._path, dtx, codec, bitrate, bandwidth, caMode, format + ) + ) validate = RTPVALIDATE( - codec=codec, - bitrate=bitrate, - framesPerPacket=framesPerPacket, - dtx=dtx + codec=codec, bitrate=bitrate, framesPerPacket=framesPerPacket, dtx=dtx ) with TemporaryDirectory() as tmp_dir: - g192Out = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192").absolute() - rtpdumpOut = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump").absolute() - rtpdumpIn = Path(tmp_dir).joinpath(f"input-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump").absolute() - pcmOut = Path(tmp_dir).joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav").absolute() - pcmOutG192 = Path(tmp_dir).joinpath(f"output_g192-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav").absolute() - piDataOutJson = Path(tmp_dir).joinpath(f"piData-{codec}-{bitrate}-{caMode}-{format}-{dtx}.json").absolute() + g192Out = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192") + .absolute() + ) + rtpdumpOut = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") + .absolute() + ) + rtpdumpIn = ( + Path(tmp_dir) + .joinpath(f"input-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") + .absolute() + ) + pcmOut = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") + .absolute() + ) + pcmOutG192 = ( + Path(tmp_dir) + .joinpath(f"output_g192-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") + .absolute() + ) + piDataOutJson = ( + Path(tmp_dir) + .joinpath(f"piData-{codec}-{bitrate}-{caMode}-{format}-{dtx}.json") + .absolute() + ) # Run WITHOUT rtpdump first to generate reference bitstream dut_encoder_frontend.run( @@ -419,7 +553,7 @@ def run_rtp_bitstream_tests ( sba_order=None, dtx_mode=dtx, max_band=bandwidth, - add_option_list=tvArgs.args(format) + add_option_list=tvArgs.args(format), ) validate.setRefG192Bitstream(g192File=g192Out) @@ -432,14 +566,19 @@ def run_rtp_bitstream_tests ( outMode = "" generatedPIData = dict() - packer.packG192File(g192File=g192Out, rtpDumpOut=rtpdumpIn, piData=generatedPIData, requestsData=generateRequests(0, 1600)) + packer.packG192File( + g192File=g192Out, + rtpDumpOut=rtpdumpIn, + piData=generatedPIData, + requestsData=generateRequests(0, 1600), + ) dut_decoder_frontend.run( output_config=outMode, output_sampling_rate=48, input_bitstream_path=g192Out, output_path=pcmOutG192, - add_option_list= [] + add_option_list=[], ) dut_decoder_frontend.run( @@ -447,26 +586,36 @@ def run_rtp_bitstream_tests ( output_sampling_rate=48, input_bitstream_path=rtpdumpIn, output_path=pcmOut, - add_option_list= ["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)] + add_option_list=["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)], ) decAudio, fs = readfile(pcmOut) g192Audio, Fs = readfile(pcmOutG192) - decAudio /= 32768.0 # readfile reuturns 16 bit int - g192Audio /= 32768.0 # readfile reuturns 16 bit int - decAudio = decAudio[4*960:] - assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= (4 * 960), "Decoded PCM Audio is not same length as input" + decAudio /= 32768.0 # readfile reuturns 16 bit int + g192Audio /= 32768.0 # readfile reuturns 16 bit int + decAudio = decAudio[4 * 960 :] + assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= ( + 4 * 960 + ), "Decoded PCM Audio is not same length as input" minSamples = min(decAudio.shape[0], g192Audio.shape[0]) - rmsdB = 10.0 * np.log10(np.finfo(float).eps + np.sum(np.abs(g192Audio[:minSamples] - decAudio[:minSamples])**2)/minSamples) + rmsdB = 10.0 * np.log10( + np.finfo(float).eps + + np.sum(np.abs(g192Audio[:minSamples] - decAudio[:minSamples]) ** 2) + / minSamples + ) if dtx: - assert rmsdB < -60.0, "Bitdiff in the RTP unpacked and G192 streams for DTX stream" + assert ( + rmsdB < -60.0 + ), "Bitdiff in the RTP unpacked and G192 streams for DTX stream" else: assert rmsdB < -96.0, "Bitdiff in the RTP unpacked and G192 streams" with open(piDataOutJson, "r") as fd: decodedPiData = json.load(fd) - assert decodedPiData.keys() == generatedPIData.keys(), f"Timestamp of PI data {generatedPIData.keys()} not found in Decoded PI Data {decodedPiData.keys()}" + assert ( + decodedPiData.keys() == generatedPIData.keys() + ), f"Timestamp of PI data {generatedPIData.keys()} not found in Decoded PI Data {decodedPiData.keys()}" for ts in generatedPIData.keys(): for pitype in generatedPIData[ts]: data = generatedPIData[ts][pitype] @@ -484,7 +633,9 @@ def run_rtp_bitstream_tests ( elif type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) elif type(generatedPIData[ts][pitype]) == list: - for r, d in zip(generatedPIData[ts][pitype], decodedPiData[ts][pitype]): + for r, d in zip( + generatedPIData[ts][pitype], decodedPiData[ts][pitype] + ): isEqualAD(AUDIO_DESCRIPTION(**d), r) else: assert False, "Unsupported PI data found" @@ -505,7 +656,7 @@ def run_rtp_bitstream_tests ( sba_order=None, dtx_mode=dtx, max_band=bandwidth, - add_option_list=extra_args + add_option_list=extra_args, ) unpacker = IvasRtp() -- GitLab From 6880621b6dacbda2ce5d29b7bf3cbf3183da83e0 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 9 Oct 2025 11:17:02 +1100 Subject: [PATCH 223/340] Fix for crash in decoder on free Signed-off-by: Ripinder Singh --- lib_dec/lib_dec.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index e0fc232dc2..4f6c1f75ab 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -346,11 +346,17 @@ void IVAS_DEC_Close( ( *phIvasDec )->hVoIP = NULL; } +#ifndef IVAS_RTPDUMP /* destroy Split binaural renderer (ISAR) handle */ ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); +#endif if ( ( *phIvasDec )->st_ivas ) { +#ifdef IVAS_RTPDUMP + /* destroy Split binaural renderer (ISAR) handle */ + ivas_destroy_handle_isar( &( *phIvasDec )->st_ivas->hSplitBinRend ); +#endif ivas_destroy_dec( ( *phIvasDec )->st_ivas ); ( *phIvasDec )->st_ivas = NULL; } -- GitLab From ae6af7da4011d99f1c2eed17027362ee2d97edda Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 9 Oct 2025 11:34:09 +0300 Subject: [PATCH 224/340] Remove rtpdump test --- .gitlab-ci.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef260a2b99..0b735a6df4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -707,28 +707,6 @@ split-rendering-voip-be-to-binaural: junit: - report-junit.xml -# test rtpdump -rtpdump-test: - extends: - - .test-job-linux - - .rules-merge-request-to-main - needs: ["build-codec-linux-make"] - stage: test - script: - - make -j - - testcase_timeout=30 - - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout - artifacts: - name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" - expire_in: 1 week - when: always - paths: - - report-junit.xml - expose_as: "rtpdump pytest results" - reports: - junit: - - report-junit.xml - lc3-wrapper-unit-test: extends: - .test-job-linux -- GitLab From 600054fc0decabd26bce6905d7e2b705cb105ece Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 9 Oct 2025 12:04:11 +0300 Subject: [PATCH 225/340] Remove rtpdump python tests and requirement --- tests/requirements.txt | 1 - tests/rtp/ivasrtp.py | 1654 ---------------------------------------- tests/rtp/test_rtp.py | 665 ---------------- 3 files changed, 2320 deletions(-) delete mode 100644 tests/rtp/ivasrtp.py delete mode 100644 tests/rtp/test_rtp.py diff --git a/tests/requirements.txt b/tests/requirements.txt index 00100fae88..2eb090f4fb 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,4 +2,3 @@ pytest>=5.3.5 pytest-xdist>=1.31.0 scipy>=1.5.2 numpy>=1.19.2 -bitstring>=4.3.1 diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py deleted file mode 100644 index bd13acaf56..0000000000 --- a/tests/rtp/ivasrtp.py +++ /dev/null @@ -1,1654 +0,0 @@ -#!/usr/bin/env python3 - -__copyright__ = """ -(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository. All Rights Reserved. - -This software is protected by copyright law and by international treaties. -The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository retain full ownership rights in their respective contributions in -the software. This notice grants no license of any kind, including but not limited to patent -license, nor is any license granted by implication, estoppel or otherwise. - -Contributors are required to enter into the IVAS codec Public Collaboration agreement before making -contributions. - -This software is provided "AS IS", without any express or implied warranties. The software is in the -development stage. It is intended exclusively for experts who have experience with such software and -solely for the purpose of inspection. All implied warranties of non-infringement, merchantability -and fitness for a particular purpose are hereby disclaimed and excluded. - -Any dispute, controversy or claim arising under or in relation to providing this software shall be -submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in -accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and -the United Nations Convention on Contracts on the International Sales of Goods. -""" - -__doc__ = """ -To configure test modules. -""" - -import struct -from enum import Enum -from dataclasses import dataclass, field, asdict -from bitstring import ConstBitStream, BitStream, ReadError -import json -import base64 -import argparse -from pathlib import Path -from typing import cast, Optional - -NO_REQ = "NO_REQ" - - -class CAMODE(str, Enum): - CA_LO_02 = "CA-LO-O2" - CA_LO_03 = "CA-LO-O3" - CA_LO_05 = "CA-LO-O5" - CA_LO_07 = "CA-LO-O7" - CA_HI_02 = "CA-HI-O2" - CA_HI_03 = "CA-HI-O3" - CA_HI_05 = "CA-HI-O5" - CA_HI_07 = "CA-HI-O7" - - -class CODECS(str, Enum): - AMRWB = "amrwb_io" - EVS = "evs" - IVAS = "ivas" - - -class SRCODEC(str, Enum): - LCLD = "lcld" - LC3PLUS = "lc3+" - - -class BANDWIDTH(str, Enum): - NB = "narrowband" - WB = "wideband" - SWB = "super wideband" - FB = "fullband" - - -class REQUESTS(str, Enum): - CODEC = ("codec",) - BR = "bitrate" - BW = "bandwidth" - CA = "ca-mode" - FMT = "format" - SUBFMT = "sub-format" - SRCFG = "sr-config" - - -class FORMATS(str, Enum): - STEREO = "Stereo" - SBA = "SBA" - MASA = "MASA" - ISM = "ISM" - MC = "MC" - OMASA = "OMASA" - OSBA = "OSBA" - - -class SUBFORMATS(str, Enum): - FOA_PLANAR = "FOA planar" - HOA2_PLANAR = "HOA2 planar" - HOA3_PLANAR = "HOA3 planar" - FOA = "FOA" - HOA2 = "HOA2" - HOA3 = "HOA3" - MASA1 = "MASA1" - MASA2 = "MASA2" - ISM1 = "ISM1" - ISM2 = "ISM2" - ISM3 = "ISM3" - ISM4 = "ISM4" - ISM1_EXTENDED_METADATA = "ISM1 extended metadata" - ISM2_EXTENDED_METADATA = "ISM2 extended metadata" - ISM3_EXTENDED_METADATA = "ISM3 extended metadata" - ISM4_EXTENDED_METADATA = "ISM4 extended metadata" - MC_5_1 = "MC 5.1" - MC_7_1 = "MC 7.1" - MC_5_1_2 = "MC 5.1.2" - MC_5_1_4 = "MC 5.1.4" - MC_7_1_4 = "MC 7.1.4" - Reserved22 = "Reserved22" - Reserved23 = "Reserved23" - Reserved24 = "Reserved24" - Reserved25 = "Reserved25" - Reserved26 = "Reserved26" - Reserved27 = "Reserved27" - Reserved28 = "Reserved28" - Reserved29 = "Reserved29" - Reserved30 = "Reserved30" - Reserved31 = "Reserved31" - Reserved32 = "Reserved32" - OMASA_ISM1_1TC = "OMASA ISM1 1TC" - OMASA_ISM2_1TC = "OMASA ISM2 1TC" - OMASA_ISM3_1TC = "OMASA ISM3 1TC" - OMASA_ISM4_1TC = "OMASA ISM4 1TC" - OMASA_ISM1_2TC = "OMASA ISM1 2TC" - OMASA_ISM2_2TC = "OMASA ISM2 2TC" - OMASA_ISM3_2TC = "OMASA ISM3 2TC" - OMASA_ISM4_2TC = "OMASA ISM4 2TC" - OSBA_ISM1_FOA_PLANAR = "OSBA ISM1 FOA planar" - OSBA_ISM2_FOA_PLANAR = "OSBA ISM2 FOA planar" - OSBA_ISM3_FOA_PLANAR = "OSBA ISM3 FOA planar" - OSBA_ISM4_FOA_PLANAR = "OSBA ISM4 FOA planar" - OSBA_ISM1_FOA = "OSBA ISM1 FOA" - OSBA_ISM2_FOA = "OSBA ISM2 FOA" - OSBA_ISM3_FOA = "OSBA ISM3 FOA" - OSBA_ISM4_FOA = "OSBA ISM4 FOA" - OSBA_ISM1_HOA2_PLANAR = "OSBA ISM1 HOA2 planar" - OSBA_ISM2_HOA2_PLANAR = "OSBA ISM2 HOA2 planar" - OSBA_ISM3_HOA2_PLANAR = "OSBA ISM3 HOA2 planar" - OSBA_ISM4_HOA2_PLANAR = "OSBA ISM4 HOA2 planar" - OSBA_ISM1_HOA2 = "OSBA ISM1 HOA2" - OSBA_ISM2_HOA2 = "OSBA ISM2 HOA2" - OSBA_ISM3_HOA2 = "OSBA ISM3 HOA2" - OSBA_ISM4_HOA2 = "OSBA ISM4 HOA2" - OSBA_ISM1_HOA3_PLANAR = "OSBA ISM1 HOA3 planar" - OSBA_ISM2_HOA3_PLANAR = "OSBA ISM2 HOA3 planar" - OSBA_ISM3_HOA3_PLANAR = "OSBA ISM3 HOA3 planar" - OSBA_ISM4_HOA3_PLANAR = "OSBA ISM4 HOA3 planar" - OSBA_ISM1_HOA3 = "OSBA ISM1 HOA3" - OSBA_ISM2_HOA3 = "OSBA ISM2 HOA3" - OSBA_ISM3_HOA3 = "OSBA ISM3 HOA3" - OSBA_ISM4_HOA3 = "OSBA ISM4 HOA3" - - -class PIDATAS(str, Enum): - SCENE_ORIENTATION = "SCENE_ORIENTATION" - DEVICE_ORIENTATION_COMPENSATED = "DEVICE_ORIENTATION_COMPENSATED" - DEVICE_ORIENTATION_UNCOMPENSATED = "DEVICE_ORIENTATION_UNCOMPENSATED" - ACOUSTIC_ENVIRONMENT = "ACOUSTIC_ENVIRONMENT" - AUDIO_DESCRIPTION = "AUDIO_DESCRIPTION" - ISM_NUM = "ISM_NUM" - ISM_ID = "ISM_ID" - ISM_GAIN = "ISM_GAIN" - ISM_ORIENTATION = "ISM_ORIENTATION" - ISM_POSITION = "ISM_POSITION" - ISM_DISTANCE_ATTENUATION = "ISM_DISTANCE_ATTENUATION" - ISM_DIRECTIVITY = "ISM_DIRECTIVITY" - DIEGETIC_TYPE = "DIEGETIC_TYPE" - RESERVED13 = "RESERVED13" - AUDIO_FOCUS_INDICATION = "AUDIO_FOCUS_INDICATION" - RESERVED15 = "RESERVED15" - PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" - HEAD_ORIENTATION = "HEAD_ORIENTATION" - LISTENER_POSITION = "LISTENER_POSITION" - DYNAMIC_AUDIO_SUPPRESSION = "DYNAMIC_AUDIO_SUPPRESSION" - AUDIO_FOCUS_REQUEST = "AUDIO_FOCUS_REQUEST" - PI_LATENCY = "PI_LATENCY" - R_ISM_ID = "R_ISM_ID" - R_ISM_GAIN = "R_ISM_GAIN" - R_ISM_ORIENTATION = "R_ISM_ORIENTATION" - R_ISM_POSITION = "R_ISM_POSITION" - R_ISM_DIRECTION = "R_ISM_DIRECTION" - RESERVED27 = "RESERVED27" - RESERVED28 = "RESERVED28" - RESERVED29 = "RESERVED29" - RESERVED30 = "RESERVED30" - NO_PI_DATA = "NO_PI_DATA" - - -class SUPPRESSION_LEVEL(int, Enum): - SUPPRESSION_LEVEL_NONE = 0 - SUPPRESSION_LEVEL_1 = 1 - SUPPRESSION_LEVEL_2 = 2 - SUPPRESSION_LEVEL_3 = 3 - SUPPRESSION_LEVEL_4 = 4 - SUPPRESSION_LEVEL_5 = 5 - SUPPRESSION_LEVEL_6 = 6 - SUPPRESSION_LEVEL_7 = 7 - SUPPRESSION_LEVEL_8 = 8 - SUPPRESSION_LEVEL_9 = 9 - SUPPRESSION_LEVEL_10 = 10 - SUPPRESSION_LEVEL_11 = 11 - SUPPRESSION_LEVEL_12 = 12 - SUPPRESSION_LEVEL_13 = 13 - SUPPRESSION_LEVEL_14 = 14 - SUPPRESSION_LEVEL_MAX = 15 - - -class AUDIO_FOCUS_LEVEL(int, Enum): - AUDIO_FOCUS_LEVEL_NONE = 0 - AUDIO_FOCUS_LEVEL_1 = 1 - AUDIO_FOCUS_LEVEL_2 = 2 - AUDIO_FOCUS_LEVEL_3 = 3 - AUDIO_FOCUS_LEVEL_4 = 4 - AUDIO_FOCUS_LEVEL_5 = 5 - AUDIO_FOCUS_LEVEL_6 = 6 - AUDIO_FOCUS_LEVEL_7 = 7 - AUDIO_FOCUS_LEVEL_8 = 8 - AUDIO_FOCUS_LEVEL_9 = 9 - AUDIO_FOCUS_LEVEL_10 = 10 - AUDIO_FOCUS_LEVEL_11 = 11 - AUDIO_FOCUS_LEVEL_12 = 12 - AUDIO_FOCUS_LEVEL_13 = 13 - AUDIO_FOCUS_LEVEL_DEFAULT = 14 - AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 - - -@dataclass -class RTPHDR: - version: int = 2 - padding: bool = False - extension: bool = False - csrcCount: int = 0 - marker: bool = False - payloadType: int = 0 - sequenceNum: int = 0 - timestamp: int = 0 - ssrc: int = 0 - extensionType: int = 0 - extensionLength: int = 0 - csrcList: list = field(default_factory=list) - extensionWords: list = field(default_factory=list) - - def updateHeader(self, numFrames: int): - self.sequenceNum = (self.sequenceNum + 1) % 65536 - self.timestamp += 320 * numFrames - - def pack(self, bitstrm: BitStream): - bitstrm.append(f"uint:2={self.version}") - bitstrm.append(f"bool={self.padding}") - bitstrm.append(f"bool={self.extension}") - bitstrm.append(f"uint:4={self.csrcCount}") - bitstrm.append(f"bool={self.marker}") - bitstrm.append(f"uint:7={self.payloadType}") - bitstrm.append(f"uintbe:16={self.sequenceNum}") - bitstrm.append(f"uintbe:32={self.timestamp}") - bitstrm.append(f"uintbe:32={self.ssrc}") - assert ( - len(self.csrcList) == self.csrcCount - ), "csrcList must be of length csrcCount" - for csrc in self.csrcList: - bitstrm.append(f"uintbe:32={csrc}") - if self.extension: - bitstrm.append(f"uintbe:16={self.extensionType}") - bitstrm.append(f"uintbe:16={self.extensionLength}") - assert ( - len(self.extensionWords) == self.extensionLength - ), "extensionWords must be of extensionLength csrcCount" - for ext in self.extensionWords: - bitstrm.append(f"uintbe:32={ext}") - - @classmethod - def unpack(cls, bitstrm: ConstBitStream): - hdr = cls() - hdr.version = bitstrm.read(2).uint - hdr.padding = bitstrm.read(1).bool - hdr.extension = bitstrm.read(1).bool - hdr.csrcCount = bitstrm.read(4).int - hdr.marker = bitstrm.read(1).bool - hdr.payloadType = bitstrm.read(7).int - hdr.sequenceNum = bitstrm.read(16).uintbe - hdr.timestamp = bitstrm.read(32).uintbe - hdr.ssrc = bitstrm.read(32).uintbe - if hdr.csrcCount: - hdr.csrcList = [bitstrm.read(32).uintbe for _ in range(hdr.csrcCount)] - if hdr.extension: - hdr.extensionType = bitstrm.read(16).uintbe - hdr.extensionLength = bitstrm.read(16).uintbe - hdr.extensionWords = [ - bitstrm.read(32).uintbe for _ in range(hdr.extensionLength) - ] - return hdr - - -@dataclass -class SRCONFIG: - diegetic: bool = False - yaw: bool = False - pitch: bool = False - roll: bool = False - - -@dataclass -class CMR: - bandwidth: BANDWIDTH - codec: CODECS = CODECS.IVAS - startIdx: int = 0 - endIdx: int = 0 - bitrates: list = field(default_factory=list) - - -@dataclass -class SRINFO: - bitrate: int = 0 - diegetic: bool = False - transportCodec: SRCODEC = SRCODEC.LCLD - - -@dataclass -class FRAME: - codec: CODECS = CODECS.IVAS - frmSizeBits: int = 0 - bitrate: int = 0 - speechLost: bool = False - srInfo: SRINFO = None - timestamp: int = 0 - au: bytes = field(default_factory=bytes) - - -# PI DATA STRUCTURES -@dataclass -class ORIENTATION: - w: float = 0.0 - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 - - -@dataclass -class POSITION: - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 - - -class ISM_POSITIONS: - positions: list[POSITION] - - -@dataclass -class AUDIO_DESCRIPTION: - isSpeech: bool = False - isMusic: bool = False - isAmbiance: bool = False - isEditable: bool = False - isBinaural: bool = False - - -@dataclass -class DYNAMIC_AUDIO_SUPPRESSION: - preferSpeech: bool = False - preferMusic: bool = False - preferAmbiance: bool = False - level: SUPPRESSION_LEVEL = SUPPRESSION_LEVEL.SUPPRESSION_LEVEL_MAX - - -@dataclass -class DIEGETIC_TYPE: - isDigetic: list[bool] - - -@dataclass -class ACOUSTIC_ENVIRONMENT: - aeid: int = 0 - rt60: tuple[float, float, float] = () - dsr: tuple[float, float, float] = () - dim: tuple[float, float, float] = () - abscoeff: tuple[float, float, float, float, float, float] = () - - -@dataclass -class AUDIO_FOCUS: - direction: Optional[ORIENTATION] = None - level: Optional[AUDIO_FOCUS_LEVEL] = None - - -@dataclass -class PIDATA: - timestamp: int = 0 - type: str = "NO_PI_DATA" - data: any = None - - -MAX_PACKED_PI_SIZE = 32 -ivasBitrates = [ - 13200, - 16400, - 24400, - 32000, - 48000, - 64000, - 80000, - 96000, - 128000, - 160000, - 192000, - 256000, - 384000, - 512000, - -1, - 5200, -] -evsBitrates = [ - 5900, - 7200, - 8000, - 9600, - 13200, - 16400, - 24400, - 32000, - 48000, - 64000, - 96000, - 128000, - 2400, - -1, - -1, - -1, -] -amrwbBitrates = [ - 6600, - 8850, - 12650, - 14250, - 15850, - 18250, - 19850, - 23050, - 23850, - 1750, - -1, - -1, - -1, - -1, - -1, - -1, -] -requestBitratesForCodec = { - CODECS.AMRWB: amrwbBitrates[0:9], - CODECS.EVS: evsBitrates[0:12], - CODECS.IVAS: ivasBitrates[0:14], -} -rt60Value = [ - 0.01, - 0.0126, - 0.0159, - 0.02, - 0.0252, - 0.0317, - 0.04, - 0.0504, - 0.0635, - 0.08, - 0.1008, - 0.1270, - 0.16, - 0.2016, - 0.2540, - 0.32, - 0.4032, - 0.5080, - 0.64, - 0.8063, - 1.0159, - 1.28, - 1.6127, - 2.0319, - 2.56, - 3.2254, - 4.0637, - 5.12, - 6.4508, - 8.1275, - 10.24, - 12.9016, -] -dsrValue = [ - -20.0, - -21.0, - -22.0, - -23.0, - -24.0, - -25.0, - -26.0, - -27.0, - -28.0, - -29.0, - -30.0, - -31.0, - -32.0, - -33.0, - -34.0, - -35.0, - -36.0, - -37.0, - -38.0, - -39.0, - -40.0, - -41.0, - -42.0, - -43.0, - -44.0, - -45.0, - -46.0, - -47.0, - -48.0, - -49.0, - -50.0, - -51.0, - -52.0, - -53.0, - -54.0, - -55.0, - -56.0, - -57.0, - -58.0, - -59.0, - -60.0, - -61.0, - -62.0, - -63.0, - -64.0, - -65.0, - -66.0, - -67.0, - -68.0, - -69.0, - -70.0, - -71.0, - -72.0, - -73.0, - -74.0, - -75.0, - -76.0, - -77.0, - -78.0, - -79.0, - -80.0, - -81.0, - -82.0, - -83.0, -] -roomDimensionValue = [ - 0.5, - 0.707, - 1.0, - 1.4141, - 2.0, - 2.8282, - 4.0, - 5.6568, - 8.0, - 11.314, - 16.0, - 22.627, - 32.0, - 45.255, - 64.0, - 90.51, -] -absorptionCoeffValues = [0.0800, 0.1656, 0.3430, 0.7101] -codedFormats = list(FORMATS) -codedSubFormats = list(SUBFORMATS) -PiTypeNames = list(PIDATAS) - - -def mapNearestIndex(table: list, val: float) -> int: - for idx, entry in enumerate(table): - if abs(entry) >= abs(val): - return idx - return len(table) - 1 - - -getListIndex = lambda mylist, val: mylist.index(val) if val in mylist else -1 - -cmrLookup = [ - CMR( - bandwidth=BANDWIDTH.NB, - codec=CODECS.EVS, - startIdx=0, - endIdx=7, - bitrates=evsBitrates, - ), # 000 = NB-EVS - CMR( - bandwidth=BANDWIDTH.WB, - codec=CODECS.AMRWB, - startIdx=0, - endIdx=9, - bitrates=amrwbBitrates, - ), # 001 = AMRWB IO - CMR( - bandwidth=BANDWIDTH.WB, - codec=CODECS.EVS, - startIdx=0, - endIdx=12, - bitrates=evsBitrates, - ), # 010 = WB-EVS - CMR( - bandwidth=BANDWIDTH.SWB, - codec=CODECS.EVS, - startIdx=3, - endIdx=12, - bitrates=evsBitrates, - ), # 011 = SWB-EVS - CMR( - bandwidth=BANDWIDTH.FB, - codec=CODECS.EVS, - startIdx=5, - endIdx=12, - bitrates=evsBitrates, - ), # 100 = FB-EVS - CMR( - bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] - ), # 101 = WB-CA - CMR( - bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] - ), # 110 = SWB-CA - CMR( - bandwidth=NO_REQ, - codec=CODECS.IVAS, - startIdx=0, - endIdx=14, - bitrates=ivasBitrates, - ), # 111 = IVAS -] - -q15 = lambda x: int(min(32767.0, max(-32768.0, x * 32768.0))) - - -def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: - # assert False, "Unsupported PI Data" - return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode("utf-8") - - -def packUnsupported(bitstrm: ConstBitStream, data: any) -> any: - assert False, f"unsupported PI Data of type : {type(data)}" - - -def unpackNoPiData(bitstrm: ConstBitStream, piSize: int) -> None: - assert piSize == 0, "NO_PI_DATA should be 0 size" - - -def packNoPiData(bitstrm: BitStream, data: any = None): - pass - - -def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION]: - assert ( - piSize % 8 - ) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" - orientations = list() - while piSize > 0: - w = bitstrm.read(16).int / 32768.0 - x = bitstrm.read(16).int / 32768.0 - y = bitstrm.read(16).int / 32768.0 - z = bitstrm.read(16).int / 32768.0 - orientations.append(ORIENTATION(w, x, y, z)) - piSize -= 8 - return orientations - - -def packOrientations(bitstrm: BitStream, data: any): - assert type(data) == list, "Orientation PI Data expects a data of type list" - for orientation in cast(list, data): - assert ( - type(orientation) == ORIENTATION - ), "Orientation PI Data expects a data of type list[ORIENTATION]" - bitstrm.append(f"intbe:16={q15(orientation.w)}") - bitstrm.append(f"intbe:16={q15(orientation.x)}") - bitstrm.append(f"intbe:16={q15(orientation.y)}") - bitstrm.append(f"intbe:16={q15(orientation.z)}") - - -def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: - assert piSize <= 24 and (piSize % 6) == 0, "Incorrect PI Data Size for Positions" - positions = list() - while piSize > 0: - x = bitstrm.read(16).int / 100.0 - y = bitstrm.read(16).int / 100.0 - z = bitstrm.read(16).int / 100.0 - positions.append(POSITION(x, y, z)) - piSize -= 6 - return positions - - -def packPositions(bitstrm: BitStream, data: any): - assert type(data) == list, "Position PI Data expects a data of type list" - positions = cast(list, data) - assert len(positions) <= 4, "Max one position per ISM object" - for position in positions: - assert ( - type(position) == POSITION - ), "Position PI Data expects a data of type list[POSITIONS]" - bitstrm.append(f"intbe:16={q15(position.x / 327.68)}") - bitstrm.append(f"intbe:16={q15(position.y / 327.68)}") - bitstrm.append(f"intbe:16={q15(position.z / 327.68)}") - - -def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: - assert piSize == 8, "Incorrect PI Data Size for ORIENTATION" - orientations = unpackOrientations(bitstrm, piSize) - assert len(orientations) == 1 - return orientations[0] - - -def packOrientation(bitstrm: BitStream, data: any): - assert ( - type(data) == ORIENTATION - ), "Orientation PI Data expects a data of type ORIENTATION" - orientation = cast(ORIENTATION, data) - packOrientations(bitstrm, [orientation]) - - -def unpackPosition(bitstrm: ConstBitStream, piSize: int) -> POSITION: - assert piSize == 6, "Incorrect PI Data Size for POSITION" - positions = unpackPositions(bitstrm, piSize) - assert len(positions) == 1 - return positions[0] - - -def packPosition(bitstrm: BitStream, data: any): - assert type(data) == POSITION, "Position PI Data expects a data of type POSITION" - position = cast(POSITION, data) - packPositions(bitstrm, [position]) - - -def unpackAudioDescription( - bitstrm: ConstBitStream, piSize: int -) -> list[AUDIO_DESCRIPTION]: - assert piSize <= 5, "Incorrect PI Data Size for AUDIO_DESCRIPTION" - ad = list() - for byte in range(piSize): - V = bitstrm.read(1).bool - M = bitstrm.read(1).bool - A = bitstrm.read(1).bool - E = bitstrm.read(1).bool - B = bitstrm.read(1).bool - _ = bitstrm.read(3) - ad.append( - AUDIO_DESCRIPTION( - isSpeech=V, isMusic=M, isAmbiance=A, isEditable=E, isBinaural=B - ) - ) - return ad - - -def packAudioDescription(bitstrm: BitStream, data: any): - assert ( - type(data) == list - ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" - for desc in cast(list, data): - assert ( - type(desc) == AUDIO_DESCRIPTION - ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" - ad = cast(AUDIO_DESCRIPTION, desc) - bitstrm.append(f"bool={ad.isSpeech}") - bitstrm.append(f"bool={ad.isMusic}") - bitstrm.append(f"bool={ad.isAmbiance}") - bitstrm.append(f"bool={ad.isEditable}") - bitstrm.append(f"bool={ad.isBinaural}") - bitstrm.append(f"uint:3=0") - - -def unpackDAS(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: - assert piSize == 2, "Incorrect PI Data Size for DYNAMIC_AUDIO_SUPPRESSION" - V = bitstrm.read(1).bool - M = bitstrm.read(1).bool - A = bitstrm.read(1).bool - _ = bitstrm.read(5) - SLI = bitstrm.read(4).uint - _ = bitstrm.read(4) - return DYNAMIC_AUDIO_SUPPRESSION( - preferSpeech=V, preferMusic=M, preferAmbiance=A, level=SLI - ) - - -def packDAS(bitstrm: BitStream, data: any): - assert ( - type(data) == DYNAMIC_AUDIO_SUPPRESSION - ), "Dynamic Audio Suppression PI Data expects a data of type DYNAMIC_AUDIO_SUPPRESSION" - das = cast(DYNAMIC_AUDIO_SUPPRESSION, data) - bitstrm.append(f"bool={das.preferSpeech}") - bitstrm.append(f"bool={das.preferMusic}") - bitstrm.append(f"bool={das.preferAmbiance}") - bitstrm.append(f"uint:5=0") - bitstrm.append(f"uint:4={das.level}") - bitstrm.append(f"uint:4=0") - - -def unpackDiegetic(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: - assert piSize == 1, "Incorrect PI Data Size for DIEGETIC_TYPE" - digType = list() - for _ in range(5): # no way to know how many bits are valid bits, so all 5 read - digType.append(bitstrm.read(1).bool) - bitstrm.bytealign() - return DIEGETIC_TYPE(isDigetic=digType) - - -def packDiegetic(bitstrm: BitStream, data: any): - assert ( - type(data) == DIEGETIC_TYPE - ), "Diegetic type PI Data expects a data of type DIEGETIC_TYPE" - diegetic = cast(DIEGETIC_TYPE, data) - assert ( - len(diegetic.isDigetic) <= 5 - ), "Maximum 1 bit per object + 1 bit for SBA/MASA is required (max 5)" - for isDigetic in diegetic.isDigetic: - bitstrm.append(f"bool={isDigetic}") - nPad = 8 - (bitstrm.pos % 8) - if nPad > 0: - bitstrm.append(f"uint:{nPad}=0") - - -def unpackAcousticEnv(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: - assert ( - piSize == 1 or piSize == 5 or piSize == 8 - ), "Incorrect PI Data Size for ACOUSTIC_ENVIRONMENT" - rt60 = list() - dsr = list() - dim = list() - absCoeff = list() - - if piSize == 1: - bitstrm.read(1) - - aeid = bitstrm.read(7).uint - - if piSize >= 5: - for _ in range(3): - rt60.append(rt60Value[bitstrm.read(5).uint]) - dsr.append(dsrValue[bitstrm.read(6).uint]) - if piSize == 8: - for _ in range(3): - dim.append(roomDimensionValue[bitstrm.read(4).uint]) - for _ in range(6): - absCoeff.append(absorptionCoeffValues[bitstrm.read(2).uint]) - - return ACOUSTIC_ENVIRONMENT( - aeid=aeid, - rt60=tuple(rt60), - dsr=tuple(dsr), - dim=tuple(dim), - abscoeff=tuple(absCoeff), - ) - - -def packAcousticEnv(bitstrm: BitStream, data: any): - assert ( - type(data) == ACOUSTIC_ENVIRONMENT - ), "Diegetic type PI Data expects a data of type ACOUSTIC_ENVIRONMENT" - aenv = cast(ACOUSTIC_ENVIRONMENT, data) - if not aenv.rt60 and not aenv.dsr: - bitstrm.append(f"uint:8={aenv.aeid % 128}") - else: - assert ( - len(aenv.rt60) == 3 and len(aenv.dsr) == 3 - ), "Lo, Mi, Hi only required for RT60 and DSR values" - bitstrm.append(f"uint:7={aenv.aeid % 128}") - for n in range(3): - rt60 = mapNearestIndex(rt60Value, aenv.rt60[n]) - dsr = mapNearestIndex(dsrValue, aenv.dsr[n]) - bitstrm.append(f"uint:5={rt60}") - bitstrm.append(f"uint:6={dsr}") - if aenv.abscoeff and aenv.dim: - assert len(aenv.abscoeff) == 6 and len(aenv.dim) == 3 - for n in range(3): - dim = mapNearestIndex(roomDimensionValue, aenv.dim[n]) - bitstrm.append(f"uint:4={dim}") - for n in range(6): - absCoeff = mapNearestIndex(absorptionCoeffValues, aenv.abscoeff[n]) - bitstrm.append(f"uint:2={absCoeff}") - - -def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: - assert ( - piSize == 1 or piSize == 8 or piSize == 9 - ), "Incorrect PI Data Size for AUDIO_FOCUS" - direction = None - level = None - if piSize == 1: - level = bitstrm.read(4).uint - _ = bitstrm.read(4) - else: - direction = unpackOrientation(bitstrm, 8) - if piSize == 9: - level = bitstrm.read(4).uint - _ = bitstrm.read(4) - - return AUDIO_FOCUS(direction=direction, level=level) - - -def packAudioFocus(bitstrm: BitStream, data: any): - assert ( - type(data) == AUDIO_FOCUS - ), "Audio focus PI Data expects a data of type AUDIO_FOCUS" - auFocus = cast(AUDIO_FOCUS, data) - if auFocus.direction is not None: - packOrientations(bitstrm, [auFocus.direction]) - if auFocus.level is not None: - level = int(auFocus.level) - bitstrm.append(f"uint:4={level}") - bitstrm.append(f"uint:4=0") - - -PIDataUnpacker = [ - unpackOrientation, # SCENE_ORIENTATION, - unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, - unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED - unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT - unpackAudioDescription, # AUDIO_DESCRIPTION - unpackUnsupported, # ISM_NUM - unpackUnsupported, # ISM_ID - unpackUnsupported, # ISM_GAIN - unpackOrientations, # ISM_ORIENTATION - unpackPositions, # ISM_POSITION - unpackUnsupported, # ISM_DISTANCE_ATTENUATION - unpackUnsupported, # ISM_DIRECTIVITY - unpackDiegetic, # DIEGETIC_TYPE - unpackUnsupported, # RESERVED13 - unpackAudioFocus, # AUDIO_FOCUS_INDICATION - unpackUnsupported, # RESERVED15 - unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION - unpackOrientation, # HEAD_ORIENTATION - unpackPosition, # LISTENER_POSITION - unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION - unpackAudioFocus, # AUDIO_FOCUS_REQUEST - unpackUnsupported, # PI_LATENCY - unpackUnsupported, # R_ISM_ID - unpackUnsupported, # R_ISM_GAIN - unpackOrientation, # R_ISM_ORIENTATION - unpackPosition, # R_ISM_POSITION - unpackUnsupported, # R_ISM_DIRECTION - unpackUnsupported, # RESERVED27 - unpackUnsupported, # RESERVED28 - unpackUnsupported, # RESERVED29 - unpackUnsupported, # RESERVED30 - unpackNoPiData, # NO_DATA -] - -PIDataPacker = [ - packOrientation, # SCENE_ORIENTATION, - packOrientation, # DEVICE_ORIENTATION_COMPENSATED, - packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED - packAcousticEnv, # ACOUSTIC_ENVIRONMENT - packAudioDescription, # AUDIO_DESCRIPTION - packUnsupported, # ISM_NUM - packUnsupported, # ISM_ID - packUnsupported, # ISM_GAIN - packOrientations, # ISM_ORIENTATION - packPositions, # ISM_POSITION - packUnsupported, # ISM_DISTANCE_ATTENUATION - packUnsupported, # ISM_DIRECTIVITY - packDiegetic, # DIEGETIC_TYPE - packUnsupported, # RESERVED13 - packAudioFocus, # AUDIO_FOCUS_INDICATION - packUnsupported, # RESERVED15 - packOrientation, # PLAYBACK_DEVICE_ORIENTATION - packOrientation, # HEAD_ORIENTATION - packPosition, # LISTENER_POSITION - packDAS, # DYNAMIC_AUDIO_SUPPRESSION - packAudioFocus, # AUDIO_FOCUS_DIRECTION - packUnsupported, # PI_LATENCY - packUnsupported, # R_ISM_ID - packUnsupported, # R_ISM_GAIN - packOrientation, # R_ISM_ORIENTATION - packPosition, # R_ISM_POSITION - packUnsupported, # R_ISM_DIRECTION - packUnsupported, # RESERVED27 - packUnsupported, # RESERVED28 - packUnsupported, # RESERVED29 - packUnsupported, # RESERVED30 - packNoPiData, # NO_DATA -] - - -def ReadG192Bitstream(g192File: Path) -> list[bytes]: - refPackets = list[bytes]() - with open(g192File, "rb") as fd: - refBitStrm = ConstBitStream(fd.read()) - while refBitStrm.pos < refBitStrm.len: - sync = hex(refBitStrm.read(16).intle) - nBits = refBitStrm.read(16).intle - assert sync == "0x6b21", "G192 syncword not found at start of packet" - writer = BitStream() - for _ in range(nBits): - bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" - writer.append(bit) - refPackets.append(writer.tobytes()) - return refPackets - - -def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: - piIndicated = False - requests = dict() - try: - if bitstrm.read(1).bool: - T = bitstrm.read(3).uint - BR = bitstrm.read(4).uint - if T in [5, 6]: # CA MODES - if BR < 8: - requests[REQUESTS.CODEC] = cmrLookup[T].codec - requests[REQUESTS.BR] = 13200 - requests[REQUESTS.CA] = BR - requests[REQUESTS.BW] = cmrLookup[T].bandwidth - else: - raise Exception("Unsupported BR bits in CA Mode") - elif T == 7: # IVAS - if BR < 14: - requests[REQUESTS.CODEC] = cmrLookup[T].codec - requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] - requests[REQUESTS.CA] = -1 - elif BR == 14: - raise Exception("Reserved BR idx in IVAS EByte") - else: - if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: - requests[REQUESTS.CODEC] = cmrLookup[T].codec - requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] - requests[REQUESTS.CA] = -1 - requests[REQUESTS.BW] = cmrLookup[T].bandwidth - else: - raise Exception( - "Reserved BR idx in {} EByte".format(cmrLookup[T].codec) - ) - # Try to get all subsequent E-bytes - while bitstrm.read(1).bool: - ET = bitstrm.read(3).uint - if ET == 0: - supportedBW = [ - BANDWIDTH.WB, - BANDWIDTH.SWB, - BANDWIDTH.FB, - BANDWIDTH.NREQ, - ] - reserved = bitstrm.read(2) - BW = bitstrm.read(2).uint - requests[REQUESTS.BW] = supportedBW[BW] - elif ET == 1: - S = bitstrm.read(1).bool - FMT = bitstrm.read(3).uint - if not S: - requests[REQUESTS.FMT] = codedFormats[FMT] - requests[REQUESTS.SUBFMT] = NO_REQ - else: - reserved = bitstrm.read(2) - subFMT = bitstrm.read(6).uint - requests[REQUESTS.FMT] = NO_REQ - requests[REQUESTS.SUBFMT] = codedSubFormats[subFMT] - elif ET == 2: - reserved = bitstrm.read(4) - piIndicated = True - elif ET == 3: - D = bitstrm.read(1).bool - Y = bitstrm.read(1).bool - P = bitstrm.read(1).bool - R = bitstrm.read(1).bool - requests[REQUESTS.SRCFG] = SRCONFIG( - diegetic=D, yaw=Y, pitch=P, roll=R - ) - else: - reserved = bitstrm.read(4) - raise Exception( - "Unsupported subsequent EByte with ET={}".format(ET) - ) - except ReadError as error: - print("Underflow in E-Bytes parsing during unpacking, error = {}".format(error)) - return piIndicated, requests - - -def packEBytes(bitstrm: BitStream, requests: dict[str:any], piIndication: bool = False): - codec = requests[REQUESTS.CODEC] if REQUESTS.CODEC in requests.keys() else NO_REQ - bitrate = requests[REQUESTS.BR] if REQUESTS.BR in requests.keys() else 0 - bandwidth = requests[REQUESTS.BW] if REQUESTS.BW in requests.keys() else NO_REQ - camode = requests[REQUESTS.CA] if REQUESTS.CA in requests.keys() else NO_REQ - format = requests[REQUESTS.FMT] if REQUESTS.FMT in requests.keys() else NO_REQ - subFormat = ( - requests[REQUESTS.SUBFMT] if REQUESTS.SUBFMT in requests.keys() else NO_REQ - ) - srcfg = ( - SRCONFIG(requests[REQUESTS.SRCFG]) - if REQUESTS.SRCFG in requests.keys() - else None - ) - - # Check if any request needs to be sent - isInitialEByteNeeded = ( - piIndication - or bitrate != 0 - or bandwidth != NO_REQ - or camode != NO_REQ - or format != NO_REQ - or subFormat != NO_REQ - or srcfg != None - ) - - if not isInitialEByteNeeded: - return - - if camode != NO_REQ: - T = 6 if bandwidth == BANDWIDTH.SWB else 5 - BR = getListIndex(list(CAMODE), camode) - assert BR > 0, "Channel Aware Mode not supported" - elif bitrate == 0: - T = 7 - BR = 15 - else: - mapBandwidthToTBit = { - BANDWIDTH.NB: 0, - BANDWIDTH.WB: 2, - BANDWIDTH.SWB: 3, - BANDWIDTH.FB: 4, - NO_REQ: 2, - }[bandwidth] - T = {CODECS.AMRWB: 1, CODECS.IVAS: 7, CODECS.EVS: mapBandwidthToTBit}[codec] - BR = getListIndex(requestBitratesForCodec[codec], bitrate) - assert ( - BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx - ), "EVS Bitrate Index and Bandwidth Combination cannot be requested" - - # Write the Initial E-Byte - bitstrm.append(f"bool={True}") # E-Byte H=1 - bitstrm.append(f"uint:3={T}") - bitstrm.append(f"uint:4={BR}") - - # Subsequent E-bytes follow - if piIndication: - bitstrm.append("hex:8=A0") - - if codec != CODECS.IVAS: - return - - # Bandwidth E-Byte - if bandwidth != NO_REQ: - bw = {BANDWIDTH.WB: 0, BANDWIDTH.SWB: 1, BANDWIDTH.FB: 2}[bandwidth] - bitstrm.append("hex:4=8") - bitstrm.append(f"uint:4={bw}") - - # Coded Format/SubFormat Request E-Byte - if subFormat in SUBFORMATS: - subFmt = getListIndex(codedSubFormats, subFormat) - bitstrm.append("hex:8=9F") # S=0, FMT=111 - bitstrm.append(f"uint:8={subFmt}") - elif format in FORMATS: - fmt = getListIndex(codedFormats, format) - bitstrm.append("hex:4=9") - bitstrm.append(f"bool={False}") # S=0 - bitstrm.append(f"uint:3={fmt}") - - # SR Config E-Byte - with srcfg: - bitstrm.append("hex:4=B") - bitstrm.append(f"bool={srcfg.diegetic}") - bitstrm.append(f"bool={srcfg.yaw}") - bitstrm.append(f"bool={srcfg.pitch}") - bitstrm.append(f"bool={srcfg.roll}") - - -def unpackAUFrames(bitstrm: ConstBitStream, frameList: list[FRAME]): - try: - # Unpack Frame AUs here - for idx, frm in enumerate(frameList): - auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode - frm.au = bitstrm.read(auSize * 8).tobytes() - except ReadError as error: - print( - "Underflow in AU Frames parsing during unpacking, error = {}".format(error) - ) - - -def packAUFrames(bitstrm: BitStream, frameList: list[FRAME]): - for frm in frameList: - bitstrm.append(frm.au) - bitstrm.bytealign() - - -def unpackToCBytes( - bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS -) -> list[FRAME]: - F = True - frmList = list() - - try: - while F: - F = bitstrm.read(1).bool - FT = bitstrm.read(2).uint - BR = bitstrm.read(4).uint - frm = FRAME(timestamp=rtpTimestamp) - if FT == 1: - frm.codec = CODECS.IVAS - if BR == 14: - supportedBitrates = [-1, 256000, 384000, 512000] - reserved = bitstrm.read(1) - D = bitstrm.read(1).bool - C = bitstrm.read(1).bool - SR_BR = bitstrm.read(2).uint - reserved = bitstrm.read(3) - if SR_BR == 0: - raise Exception("Reserved bitrate in SR Config ToC Indicated") - frm.srInfo = SRINFO( - bitrate=supportedBitrates[SR_BR], - diegetic=D, - transportCodec=SRCODEC.LC3PLUS if C else SRCODEC.LCLD, - ) - else: - frm.bitrate = ivasBitrates[BR] - elif FT == 0: - # Codec switch only if not NO_DATA_FRAME, as IVAS/EVS signal using this - frm.codec = CODECS.EVS if BR != 15 else Codec - if BR == 13: - raise Exception("Reserved bitrate in EVS ToC Indicated") - frm.speechLost = BR == 14 - frm.bitrate = evsBitrates[BR] if BR < 13 else 0 - else: - frm.codec = CODECS.AMRWB - if BR >= 10 and BR <= 13: - raise Exception("Reserved bitrate in AMRWB-IO ToC Indicated") - frm.speechLost = (BR == 14) or (FT == 2) - frm.bitrate = amrwbBitrates[BR] if BR < 10 else 0 - frm.frmSizeBits = frm.bitrate // 50 - rtpTimestamp += 320 - frmList.append(frm) - if F: - # skip all frame specific E-bytes before next header - while bitstrm.read(1).bool: - print("Skipping unsupported frame specific subsequent Ebytes") - reserved = bitstrm.read(7) - except ReadError as error: - print("Underflow in ToC parsing during unpacking, error = {}".format(error)) - - return frmList - - -def packToCBytes(bitstrm: BitStream, frameList: list[FRAME]): - numFrames = len(frameList) - - for idx, frame in enumerate(frameList): - F = idx != (numFrames - 1) - FT = 0 - BR = 0 - if frame.frmSizeBits == 0: - FT = ( - 3 if frame.codec == CODECS.AMRWB else 0 - ) # Only AMRWB or EVS support 0 frame case - BR = 14 if frame.speechLost else 15 # SPEECH_LOST or NO_DATA - elif frame.codec == CODECS.AMRWB: - FT = 3 - BR = getListIndex(amrwbBitrates[0:10], frame.bitrate) - elif frame.codec == CODECS.IVAS: - FT = 1 - BR = 14 if frame.srInfo else getListIndex(ivasBitrates, frame.bitrate) - else: - FT = 0 - BR = getListIndex(evsBitrates[0:13], frame.bitrate) - - assert BR >= 0, "Index for table not found" - - bitstrm.append(f"bool={False}") # ToC 0 bit - bitstrm.append(f"bool={F}") # Frame follows bit - bitstrm.append(f"uint:2={FT}") # Frame Type - bitstrm.append(f"uint:4={BR}") # Frame Type - if frame.srInfo and frame.codec == CODECS.IVAS: - SRBR = (frame.srInfo.bitrate // 128000) - 1 - bitstrm.append(f"bool={False}") # ToC 0 bit - bitstrm.append(f"bool={frame.srInfo.diegetic}") - bitstrm.append(f"bool={frame.srInfo.transportCodec == SRCODEC.LC3PLUS}") - bitstrm.append(f"uint:2={SRBR}") - bitstrm.append("uint:3=0") - - -def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: - piDataList = list[PIDATA]() - try: - # PI Data if Indicated - PF = True - piTimeStamps = rtpTimestamp - while PF: - PF = bitstrm.read(1).bool - PM = bitstrm.read(2).uint - PiType = bitstrm.read(5).uint - PiSize = 0 - byte = 255 - while byte == 255: - byte = bitstrm.read(8).uint - PiSize += byte - - PiFrameData = PIDataUnpacker[PiType](bitstrm, PiSize) - - if PiTypeNames[PiType] != PIDATAS.NO_PI_DATA: - piDataList.append( - PIDATA( - timestamp=( - piTimeStamps if PM != 3 else rtpTimestamp - ), # Generic Pi has base timestamp - type=PiTypeNames[PiType], - data=PiFrameData, - ) - ) - piTimeStamps += 320 if PM == 2 else 0 - except ReadError as error: - print("Underflow before completion of unpacking, error = {}".format(error)) - return piDataList - - -def packPiData( - bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataList: list[PIDATA] -): - # sort the piDataList by timestamp and eliminate data where timestamp is OOB for this packet - piDataList = [ - data - for data in piDataList - if ( - data.timestamp >= rtpTimestampBounds[0] - and data.timestamp < rtpTimestampBounds[1] - ) - ] - sorted(piDataList, key=lambda data: data.timestamp) - numPiData = len(piDataList) - - # Group PI data by timestamps - piDataDict = dict[int, list[PIDATA]]() - for data in piDataList: - ts = (data.timestamp // 320) * 320 - if ts not in piDataDict.keys(): - piDataDict[ts] = list() - piDataDict[ts].append(data) - - curTimestamp = rtpTimestampBounds[0] - for ts, dataList in piDataDict.items(): - while curTimestamp < ts: - # Inset NO_PI_DATA till current Timestamp is reached - bitstrm.append( - "hex:16=DF00" - ) # PI Frame follows, Last PI header for this frame, NO_PI_DATA, size=0 - curTimestamp += 320 - - for idx, data in enumerate(dataList): - pack = BitStream() - PM = 2 if idx == len(dataList) - 1 else 1 - PF = 0 if numPiData == 1 else 1 - TYPE = PiTypeNames.index(data.type) - PIDataPacker[TYPE](pack, data.data) - assert (pack.pos % 8) == 0, "PI data must be byte aligned" - SIZE = pack.pos // 8 - assert ( - SIZE < MAX_PACKED_PI_SIZE - ), f"Packed PI Size should be less than MAX_PACKED_PI_SIZE ({MAX_PACKED_PI_SIZE})" - - bitstrm.append(f"uint:1={PF}") - bitstrm.append(f"uint:2={PM}") - bitstrm.append(f"uint:5={TYPE}") - bitstrm.append(f"uint:8={SIZE}") - bitstrm.append(pack) - numPiData -= 1 - curTimestamp += 320 - assert numPiData == 0, "Not all PI data was packed due to internal error" - - -@dataclass -class IvasPayload: - frameList: list[FRAME] = field(default_factory=list) - piDataList: list[PIDATA] = field(default_factory=list) - requests: dict[str, any] = field(default_factory=dict) - - def pack(self, bitstrm: BitStream) -> int: - piIndication = len(self.piDataList) > 0 - numFrames = len(self.frameList) - packEBytes(bitstrm, self.requests, piIndication) - packToCBytes(bitstrm, self.frameList) - packAUFrames(bitstrm, self.frameList) - if piIndication: - packPiData( - bitstrm, - (self.frameList[0].timestamp, self.frameList[-1].timestamp + 320), - self.piDataList, - ) - return numFrames - - @classmethod - def unpack(cls, bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS): - # Unpack the E-bytes - piIndicated, requests = unpackEBytes(bitstrm) - - # ToC Byte parsing starts with 'F' bit as H bit is already read for E-byte parsing - frameList = unpackToCBytes(bitstrm, rtpTimestamp, Codec) - - # Extract packed AU - unpackAUFrames(bitstrm, frameList) - - if piIndicated: - piDataList = unpackPiData(bitstrm, rtpTimestamp) - else: - piDataList = list() - - return cls(frameList=frameList, piDataList=piDataList, requests=requests) - - -@dataclass -class IvasPacket: - hdr: RTPHDR = field(default_factory=RTPHDR) - payload: IvasPayload = field(default_factory=IvasPayload) - - def pack(self, bitstrm: BitStream): - self.hdr.pack(bitstrm) - numFrames = self.payload.pack(bitstrm) - self.hdr.updateHeader(numFrames) - - -class IvasRtp: - def __init__(self, numFramesPerPacket=4, codec: CODECS = CODECS.IVAS): - self.numFramesPerPacket = numFramesPerPacket - self.packets = list[IvasPacket]() - self.Codec: CODECS = codec # Track last frame's codec - self.requests = dict() - self.piData = dict() - - def dumpToJSON(self, jsonFileName): - with open(jsonFileName, "w") as fd: - packets = list() - for packet in self.packets: - packetDict = asdict(packet) - for frame in packetDict["payload"]["frameList"]: - frame["au"] = base64.b64encode(frame["au"]).decode("utf-8") - packets.append(packetDict) - json_output = json.dumps(packets, indent=4) - fd.write(json_output) - - def requestReader(self, timestamp: int) -> dict[str, any]: - tsList = sorted(self.requests.keys()) - if len(tsList) > 0: - lastTs = int(tsList[0]) - for ts in tsList: - if timestamp >= lastTs and timestamp < int(ts): - return self.requests[str(lastTs)] - lastTs = int(ts) - return dict() - - def piDataReader(self, startTimestamp: int, endTimestamp: int) -> list[PIDATA]: - - piDataList = list() - while startTimestamp < endTimestamp: - ts = str(startTimestamp) - if ts in self.piData.keys(): - for piTypes in self.piData[ts].keys(): - dataDict = self.piData[ts][piTypes] - if type(dataDict) != dict: - data = dataDict - elif piTypes == PIDATAS.ISM_ORIENTATION: - data = list() - for orientation in dataDict: - data.append(ORIENTATION(**orientation)) - elif ( - "ORIENTATION" in piTypes - or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION - ): - data = ORIENTATION(**dataDict) - elif piTypes == PIDATAS.ACOUSTIC_ENVIRONMENT: - data = ACOUSTIC_ENVIRONMENT(**dataDict) - elif piTypes == PIDATAS.AUDIO_DESCRIPTION: - data = list() - for desc in dataDict: - data.append(AUDIO_DESCRIPTION(**desc)) - elif piTypes == PIDATAS.DIEGETIC_TYPE: - data = DIEGETIC_TYPE(**dataDict) - elif ( - piTypes == PIDATAS.LISTENER_POSITION - or piTypes == PIDATAS.R_ISM_POSITION - ): - data = POSITION(**dataDict) - elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: - data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) - else: - assert False, "Unhandled PI Data" - piDataList.append( - PIDATA(timestamp=startTimestamp, type=piTypes, data=data) - ) - startTimestamp += 320 - return piDataList - - def packG192File( - self, - g192File: Path, - rtpDumpOut: Path, - piData: dict = None, - requestsData: dict = None, - ): - packet = IvasPacket() - packet.hdr.sequenceNum = int("0xFFFF", 16) - packet.hdr.timestamp = 0 - packet.hdr.ssrc = int("0xDEADBEEF", 16) - - self.piData = piData - self.requests = requestsData - - timestamp = packet.hdr.timestamp - piTimestamps = 0 - with open(g192File, "rb") as fin: - with open(rtpDumpOut, "wb") as fout: - refBitStrm = ConstBitStream(fin.read()) - frames = list[FRAME]() - while refBitStrm.pos < refBitStrm.len: - sync = hex(refBitStrm.read(16).intle) - nBits = refBitStrm.read(16).intle - assert ( - sync == "0x6b21" - ), "G192 syncword not found at start of packet" - writer = BitStream() - for _ in range(nBits): - bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" - writer.append(bit) - frames.append( - FRAME( - codec=self.Codec, - frmSizeBits=nBits, - bitrate=nBits * 50, - speechLost=False, - timestamp=timestamp, - au=writer.tobytes(), - ) - ) - if (len(frames) == self.numFramesPerPacket) or ( - refBitStrm.pos == refBitStrm.len - ): - rtpBitstrm = BitStream() - numFrames = len(frames) - packet.payload = IvasPayload( - frameList=frames, - piDataList=self.piDataReader( - piTimestamps, piTimestamps + (numFrames * 320) - ), - requests=self.requestReader(piTimestamps), - ) - packet.pack(bitstrm=rtpBitstrm) - fout.write(struct.pack("i", rtpBitstrm.bytepos)) - fout.write(rtpBitstrm.tobytes()) - frames = list() - piTimestamps += numFrames * 320 - timestamp += 320 - - def getPackets(self): - return self.packets - - def unpackFile(self, rtpDumpFile): - with open(rtpDumpFile, mode="rb") as fd: - while True: - size = fd.read(4) - if not size: - break - size = struct.unpack("i", size)[0] - packet = fd.read(size) - if not packet: - break - self.packets.append(self.unpackPacket(packet)) - - def unpackPacket(self, packet) -> IvasPacket: - bitStrm = ConstBitStream(packet) - hdr = RTPHDR.unpack(bitStrm) - payload = IvasPayload.unpack( - bitStrm, rtpTimestamp=hdr.timestamp, Codec=self.Codec - ) - self.Codec = payload.frameList[ - -1 - ].codec # Last Frame's codec for next frame for NO_DATA case - return IvasPacket(hdr=hdr, payload=payload) - - -class ArgsParser: - def __init__(self): - self.parser = argparse.ArgumentParser() - self.parser.add_argument( - "-r", "--rtpdump", type=str, default=None, help="RTP Dump to unpack" - ) - self.parser.add_argument( - "-j", - "--json", - type=str, - default="unpack.json", - help="Output unpacked RTP frames to JSON file", - ) - self.parser.add_argument( - "-g", - "--g192", - type=str, - default=None, - help="G192 bitstream input for RTP Packing", - ) - self.parser.add_argument( - "-f", - "--framesPerPacket", - type=int, - default=1, - help="Number of IVAS frames per RTP Packet", - ) - self.parser.add_argument( - "-o", - "--outrtpdump", - type=str, - default="output.rtpdump", - help="Output RTP Dump file", - ) - self.parser.add_argument( - "-p", "--piDataJson", type=str, default=None, help="piData to be packed" - ) - self.parser.add_argument( - "-x", "--requestsJson", type=str, default=None, help="Requests to be packed" - ) - - def parse(self): - args = self.parser.parse_args() - return args - - -if __name__ == "__main__": - args = ArgsParser().parse() - rtp = IvasRtp(numFramesPerPacket=args.framesPerPacket) - - if args.rtpdump: - rtp.unpackFile(args.rtpdump) - rtp.dumpToJSON(args.json) - elif args.g192: - piData = dict() - requestsData = dict() - if args.piDataJson: - with open(args.piDataJson) as f: - piData = json.load(f) - if args.requestsJson: - with open(args.requestsJson) as f: - requestsData = json.load(f) - rtp.packG192File( - g192File=args.g192, - rtpDumpOut=args.outrtpdump, - piData=piData, - requestsData=requestsData, - ) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py deleted file mode 100644 index 69b85dc9af..0000000000 --- a/tests/rtp/test_rtp.py +++ /dev/null @@ -1,665 +0,0 @@ -#!/usr/bin/env python3 - -__copyright__ = """ -(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository. All Rights Reserved. - -This software is protected by copyright law and by international treaties. -The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, -Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., -Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, -Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other -contributors to this repository retain full ownership rights in their respective contributions in -the software. This notice grants no license of any kind, including but not limited to patent -license, nor is any license granted by implication, estoppel or otherwise. - -Contributors are required to enter into the IVAS codec Public Collaboration agreement before making -contributions. - -This software is provided "AS IS", without any express or implied warranties. The software is in the -development stage. It is intended exclusively for experts who have experience with such software and -solely for the purpose of inspection. All implied warranties of non-infringement, merchantability -and fitness for a particular purpose are hereby disclaimed and excluded. - -Any dispute, controversy or claim arising under or in relation to providing this software shall be -submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in -accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and -the United Nations Convention on Contracts on the International Sales of Goods. -""" - -__doc__ = """ -This test does the following procedure:- - -DECODER TEST -============ -* Encode a input for given format, bitrate and dtx info using IVAS Encoder to g192 -* Generate random PI data to be used for RTP packaging -* Use g192 and random PI data to pack a stream into RTPDump stream using the reference rtp packer implementation in python -* Provide the rtpdump stream to IVAS decoder to decode the stream and generate a PI data dump in a JSON file -* Decode G192 stream using IVAS decoder and compare against output of RTPdump for bit-exactness -* Validate dumped JSON agains original random PI data to check for similar data after deocde - -ENCODER TEST -============ -* Encode the input for given format, bitrate and dtx info using IVAS Encoder to rtpdump directly -* Validate the generated rtp dump using reference python implementation of RTP depacker for following:- - * RTP Header consistency (Timestamp, seq number, etc) - * IVAS Payload (no of frames in packet, bitrate indicated, bitexactness of frames in packet) - * PI Data verification (Timestamp, Pidata Type, similarity of PI data) -""" - -import pytest -import csv -import os -import sys -import random - -from tempfile import TemporaryDirectory -from pathlib import Path -from ivasrtp import * -import numpy as np -from pyaudio3dtools.audiofile import readfile - -ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) -sys.path.append(ROOT_DIR) - -from tests.conftest import EncoderFrontend, DecoderFrontend - - -@pytest.mark.parametrize("dtx", [False, True]) -@pytest.mark.parametrize("bitrate", [6600, 12650, 23850]) -@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) -def test_rtp_bitstream_amrwb( - test_info, - bitrate: int, - dtx: bool, - framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend, -): - run_rtp_bitstream_tests( - CODECS.AMRWB, - bitrate, - "WB", - "OFF", - "MONO", - dtx, - framesPerPacket, - dut_encoder_frontend, - dut_decoder_frontend, - ) - - -@pytest.mark.parametrize("dtx", [False, True]) -@pytest.mark.parametrize("bitrate", [9600, 24400, 128000]) -@pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) -@pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) -@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) -def test_rtp_bitstream_evs( - test_info, - bitrate: int, - bandwidth: str, - caMode: str, - dtx: bool, - framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend, -): - run_rtp_bitstream_tests( - CODECS.EVS, - bitrate, - bandwidth, - caMode, - "MONO", - dtx, - framesPerPacket, - dut_encoder_frontend, - dut_decoder_frontend, - ) - - -@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) -@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) -@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) -@pytest.mark.parametrize("framesPerPacket", [8]) -def test_rtp_bitstream_ivas_nodtx( - test_info, - bitrate: int, - bandwidth: str, - format: str, - framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend, -): - run_rtp_bitstream_tests( - CODECS.IVAS, - bitrate, - bandwidth, - "OFF", - format, - False, - framesPerPacket, - dut_encoder_frontend, - dut_decoder_frontend, - ) - - -@pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) -@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) -@pytest.mark.parametrize("format", ["STEREO", "SBA"]) -@pytest.mark.parametrize("framesPerPacket", [1, 3]) -def test_rtp_bitstream_ivas_dtx( - test_info, - bitrate: int, - bandwidth: str, - format: str, - framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend, -): - run_rtp_bitstream_tests( - CODECS.IVAS, - bitrate, - bandwidth, - "OFF", - format, - True, - framesPerPacket, - dut_encoder_frontend, - dut_decoder_frontend, - ) - - -def generateRequests(startTs: int, endTs: int) -> dict: - requests = dict() - return requests - - -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, - ) - somePosition = lambda: POSITION( - x=random.randint(-32788, 32767) / 100.0, - y=random.randint(-32788, 32767) / 100.0, - z=random.randint(-32788, 32767) / 100.0, - ) - someDesc = lambda: AUDIO_DESCRIPTION( - isSpeech=bool(random.getrandbits(1)), - isMusic=bool(random.getrandbits(1)), - isAmbiance=bool(random.getrandbits(1)), - isEditable=bool(random.getrandbits(1)), - isBinaural=bool(random.getrandbits(1)), - ) - someDAS = lambda: DYNAMIC_AUDIO_SUPPRESSION( - preferSpeech=bool(random.getrandbits(1)), - preferMusic=bool(random.getrandbits(1)), - preferAmbiance=bool(random.getrandbits(1)), - level=random.randint(0, 15), - ) - 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, - ) - ) - someAuFocusLvl = lambda: AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) - someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] - - for ts in range(startTs, endTs, 320): - pidata = dict() - pidata["SCENE_ORIENTATION"] = someOrientation() - pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() - pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() - pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() - pidata["HEAD_ORIENTATION"] = someOrientation() - pidata["AUDIO_FOCUS_REQUEST"] = random.choice(someAuFocusList)() - pidata["LISTENER_POSITION"] = somePosition() - pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() - pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] - pidata["DIEGETIC_TYPE"] = someDIG() - pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT( - aeid=random.randint(0, 127) - ) - data[str(ts)] = pidata - return data - - -def isEqualFrame(refFrame: bytes, dutFrame: bytes): - assert len(refFrame) == len(dutFrame), "Encoded frame size is different" - for refByte, dutByte in zip(refFrame, dutFrame): - assert ( - refByte == dutByte - ), "Encoded frames should be bitexact between ref and rtpdump" - - -def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): - assert abs(ref.w - dut.w) < 0.0001, "Scene Orientation PI Data mismatch in w" - assert abs(ref.x - dut.x) < 0.0001, "Scene Orientation PI Data mismatch in x" - assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" - assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" - - -def isEqualPosition(ref: POSITION, dut: POSITION): - assert abs(ref.x - dut.x) < 0.3, "Position PI Data mismatch in x" - assert abs(ref.y - dut.y) < 0.3, "Position PI Data mismatch in y" - assert abs(ref.z - dut.z) < 0.3, "Position PI Data mismatch in z" - - -def isEqualAD(ref: AUDIO_DESCRIPTION, dut: AUDIO_DESCRIPTION): - assert ( - ref.isSpeech == dut.isSpeech - ), "Audio Description PI Data mismatch in isSpeech" - assert ref.isMusic == dut.isMusic, "Audio Description PI Data mismatch in isMusic" - assert ( - ref.isAmbiance == dut.isAmbiance - ), "Audio Description PI Data mismatch in isAmbiance" - assert ( - ref.isEditable == dut.isEditable - ), "Audio Description PI Data mismatch in isEditable" - assert ( - ref.isBinaural == dut.isBinaural - ), "Audio Description PI Data mismatch in isBinaural" - - -def isEqualDAS(ref: DYNAMIC_AUDIO_SUPPRESSION, dut: DYNAMIC_AUDIO_SUPPRESSION): - assert ( - ref.preferSpeech == dut.preferSpeech - ), "Dynamic Audio Suppression PI Data mismatch in preferSpeech" - assert ( - ref.preferMusic == dut.preferMusic - ), "Dynamic Audio Suppression PI Data mismatch in preferMusic" - assert ( - ref.preferAmbiance == dut.preferAmbiance - ), "Dynamic Audio Suppression PI Data mismatch in preferAmbiance" - assert ref.level == dut.level, "Dynamic Audio Suppression PI Data mismatch in level" - - -def isEqualDiegetic(ref: DIEGETIC_TYPE, dut: DIEGETIC_TYPE): - for r, d in zip(ref.isDigetic, dut.isDigetic): - assert r == d, f"Diegetic PI Data mismatch {r} != {d}" - - -def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): - assert ref.aeid == dut.aeid, "Acoustic Env PI Data mismatch in Acoustic Identifier" - assert len(ref.rt60) == len(dut.rt60), "Acoustic Env PI Data mismatch in len(rt60)" - assert len(ref.dsr) == len(dut.dsr), "Acoustic Env PI Data mismatch in len(dsr)" - assert len(ref.dim) == len(dut.dim), "Acoustic Env PI Data mismatch in len(dim)" - assert len(ref.abscoeff) == len( - dut.abscoeff - ), "Acoustic Env PI Data mismatch in len(abscoeff)" - for r, d in zip(ref.rt60, dut.rt60): - assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" - - -def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): - if ref.direction is not None or dut.direction is not None: - assert ref.direction is not None, "Audio Focus PI Data missing direction" - 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.0001 - ), "Audio Focus PI Data mismatch in direction w" - assert ( - abs(ref.direction["x"] - dut.direction.x) < 0.0001 - ), "Audio Focus PI Data mismatch in direction x" - assert ( - abs(ref.direction["y"] - dut.direction.y) < 0.0001 - ), "Audio Focus PI Data mismatch in direction y" - assert ( - abs(ref.direction["z"] - dut.direction.z) < 0.0001 - ), "Audio Focus PI Data mismatch in direction z" - assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" - - -class CSVREADER: - def __init__(self, csvFile: Path): - self.rIdx = 0 - self.rows = [] - with open(csvFile, "r") as fd: - self.rows = [row for row in csv.reader(fd)] - self.count = len(self.rows) - - def next(self) -> list[float]: - row = self.rows[self.rIdx] - self.rIdx += 1 - if self.rIdx == self.count: - self.rIdx = 0 - return [float(x) for x in row] - - -class RTPVALIDATE: - - DTX_BITRATES = {CODECS.IVAS: 5200, CODECS.EVS: 2400, CODECS.AMRWB: 1750} - - def __init__(self, codec=CODECS.IVAS, bitrate=24400, framesPerPacket=1, dtx=False): - self.framesPerPacket = framesPerPacket - self.dtx = dtx - self.codec = codec - self.bitrate = bitrate - self.timestamp = 0 - self.seqnum = -1 - self.ssrc = -1 - self.numFrames = 0 - self.validatePiData = False - self.g192File = None - self.frameIdx = 0 - # PI DATA - self.readers: dict[str:CSVREADER] = dict() - - def setPiDataFiles(self, piFiles: tuple[Path]): - self.validatePiData = True - self.readers[PiTypeNames[0]] = CSVREADER(piFiles[0]) - self.readers[PiTypeNames[1]] = CSVREADER(piFiles[1]) - - def setRefG192Bitstream(self, g192File: Path): - self.refPackets = ReadG192Bitstream(g192File) - - def packet(self, packet: IvasPacket): - self.header(packet.hdr) - self.payload(packet.payload) - self.seqnum = (self.seqnum + 1) % 65536 - self.timestamp += 320 * self.numFrames - - def header(self, hdr: RTPHDR): - if self.timestamp == 0: - self.seqnum = hdr.sequenceNum - self.ssrc = hdr.ssrc - assert hdr.version == 2, "RTP Header Version must be 2" - assert self.ssrc == hdr.ssrc, "SSRC changed mid-stream in RTP Header" - assert self.timestamp == hdr.timestamp, "Timestamp mismatch in RTP Header" - assert self.seqnum == hdr.sequenceNum, "Sequence number mismatch in RTP Header" - - def payload(self, payload: IvasPayload): - self.numFrames = len(payload.frameList) - assert ( - self.numFrames >= 1 and self.numFrames <= self.framesPerPacket - ), f"Packet must have atleast 1 frame and atmost {self.framesPerPacket} frames" - - for frame in payload.frameList: - assert self.codec == frame.codec, "Codec mismatch in RTP Payload" - if self.dtx: - assert frame.bitrate in ( - self.bitrate, - RTPVALIDATE.DTX_BITRATES[self.codec], - 0, - ), "Bitrate mismatch in RTP Payload in DTX mode" - else: - assert frame.bitrate == self.bitrate, "Bitrate mismatch in RTP Payload" - - assert self.frameIdx < len(self.refPackets), "No. of frames mismatch" - isEqualFrame(frame.au, self.refPackets[self.frameIdx]) - self.frameIdx += 1 - - # Vallidate the PI Data - if self.validatePiData: - self.piData(payload.piDataList) - - def piData(self, piDataList: list[PIDATA]): - for piData in piDataList: - assert ( - piData.timestamp >= self.timestamp - and piData.timestamp < self.timestamp + (self.numFrames * 320) - ), "PI Data Time stamp is OOB" - assert ( - piData.type == PiTypeNames[0] or piData.type == PiTypeNames[1] - ), "PI Data is neither Scene nor Device Orientation" - assert type(piData.data) == ORIENTATION, "Orientation type data expected" - # validate the PI Data provided is the PI data in the packet - refData = self.readers[piData.type].next() - isEqualOrientation( - ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), - piData.data, - ) - - -@dataclass -class TVARGS: - TVROOT = Path(ROOT_DIR).joinpath("scripts/testv") - - def __init__(self): - self.tvDict = dict() - self.sceneFile = ( - Path(ROOT_DIR) - .joinpath("scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv") - .absolute() - ) - self.deviceFile = ( - Path(ROOT_DIR).joinpath("scripts/trajectories/headrot-1.5s.csv").absolute() - ) - - def add(self, fmt: str, inputFile: str, args: list[str] = []): - inputFile = str(TVARGS.TVROOT.joinpath(inputFile).absolute()) - if fmt == "MASA": - args[2] = str(TVARGS.TVROOT.joinpath(args[2]).absolute()) - self.tvDict[fmt] = (inputFile, args) - - def input(self, fmt): - return self.tvDict[fmt][0] - - def args(self, fmt, addPI=False) -> list[str]: - args = [x for x in self.tvDict[fmt][1]] - if addPI and fmt != "MONO": - args += [ - "-scene_orientation", - str(self.sceneFile), - "-device_orientation", - str(self.deviceFile), - ] - return args - - def piFiles(self) -> tuple[Path]: - return (self.sceneFile, self.deviceFile) - - -def run_rtp_bitstream_tests( - codec: CODECS, - bitrate: int, - bandwidth: str, - caMode: str, - format: str, - dtx: bool, - framesPerPacket: int, - dut_encoder_frontend: EncoderFrontend, - dut_decoder_frontend: DecoderFrontend, -): - tvArgs = TVARGS() - tvArgs.add("MONO", "stv48n.wav") - - if dtx: # use bigger file for dtx stereo - tvArgs.add("STEREO", "stvST48n.wav", ["-stereo"]) - else: - tvArgs.add("STEREO", "stv2MASA2TC48c.wav", ["-stereo"]) - - tvArgs.add("MC", "stv51MC48c.wav", ["-mc", "5_1"]) - tvArgs.add("MASA", "stv2MASA2TC48c.wav", ["-masa", "2", "stv2MASA2TC48c.met"]) - tvArgs.add("SBA", "stvFOA48c.wav", ["-sba", "+1"]) - - if (bitrate > 24400 and bandwidth == "NB") or ( - format == "STEREO" and bitrate > 256000 - ): - pytest.skip() - - print( - "Test: dut_encoder_frontend={}, dtx={}, codec:={}, bitrate={}, bandwidth={}, caMode={}, format={},".format( - dut_encoder_frontend._path, dtx, codec, bitrate, bandwidth, caMode, format - ) - ) - - validate = RTPVALIDATE( - codec=codec, bitrate=bitrate, framesPerPacket=framesPerPacket, dtx=dtx - ) - - with TemporaryDirectory() as tmp_dir: - g192Out = ( - Path(tmp_dir) - .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192") - .absolute() - ) - rtpdumpOut = ( - Path(tmp_dir) - .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") - .absolute() - ) - rtpdumpIn = ( - Path(tmp_dir) - .joinpath(f"input-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") - .absolute() - ) - pcmOut = ( - Path(tmp_dir) - .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") - .absolute() - ) - pcmOutG192 = ( - Path(tmp_dir) - .joinpath(f"output_g192-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") - .absolute() - ) - piDataOutJson = ( - Path(tmp_dir) - .joinpath(f"piData-{codec}-{bitrate}-{caMode}-{format}-{dtx}.json") - .absolute() - ) - - # Run WITHOUT rtpdump first to generate reference bitstream - dut_encoder_frontend.run( - bitrate=bitrate, - input_sampling_rate=48, - input_path=tvArgs.input(format), - output_bitstream_path=g192Out, - sba_order=None, - dtx_mode=dtx, - max_band=bandwidth, - add_option_list=tvArgs.args(format), - ) - validate.setRefG192Bitstream(g192File=g192Out) - - packer = IvasRtp(numFramesPerPacket=framesPerPacket, codec=codec) - - if codec == CODECS.IVAS: - outMode = "STEREO" if format == "STEREO" else "BINAURAL" - generatedPIData = generatePiData(0, 16000) - else: - outMode = "" - generatedPIData = dict() - - packer.packG192File( - g192File=g192Out, - rtpDumpOut=rtpdumpIn, - piData=generatedPIData, - requestsData=generateRequests(0, 1600), - ) - - dut_decoder_frontend.run( - output_config=outMode, - output_sampling_rate=48, - input_bitstream_path=g192Out, - output_path=pcmOutG192, - add_option_list=[], - ) - - dut_decoder_frontend.run( - output_config=outMode, - output_sampling_rate=48, - input_bitstream_path=rtpdumpIn, - output_path=pcmOut, - add_option_list=["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)], - ) - - decAudio, fs = readfile(pcmOut) - g192Audio, Fs = readfile(pcmOutG192) - decAudio /= 32768.0 # readfile reuturns 16 bit int - g192Audio /= 32768.0 # readfile reuturns 16 bit int - decAudio = decAudio[4 * 960 :] - assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= ( - 4 * 960 - ), "Decoded PCM Audio is not same length as input" - minSamples = min(decAudio.shape[0], g192Audio.shape[0]) - rmsdB = 10.0 * np.log10( - np.finfo(float).eps - + np.sum(np.abs(g192Audio[:minSamples] - decAudio[:minSamples]) ** 2) - / minSamples - ) - - if dtx: - assert ( - rmsdB < -60.0 - ), "Bitdiff in the RTP unpacked and G192 streams for DTX stream" - else: - assert rmsdB < -96.0, "Bitdiff in the RTP unpacked and G192 streams" - - with open(piDataOutJson, "r") as fd: - decodedPiData = json.load(fd) - assert ( - decodedPiData.keys() == generatedPIData.keys() - ), f"Timestamp of PI data {generatedPIData.keys()} not found in Decoded PI Data {decodedPiData.keys()}" - for ts in generatedPIData.keys(): - for pitype in generatedPIData[ts]: - data = generatedPIData[ts][pitype] - decoded = decodedPiData[ts][pitype] - if type(generatedPIData[ts][pitype]) == ORIENTATION: - isEqualOrientation(ORIENTATION(**decoded), data) - elif type(generatedPIData[ts][pitype]) == POSITION: - isEqualPosition(POSITION(**decoded), data) - elif type(generatedPIData[ts][pitype]) == DYNAMIC_AUDIO_SUPPRESSION: - isEqualDAS(DYNAMIC_AUDIO_SUPPRESSION(**decoded), data) - elif type(generatedPIData[ts][pitype]) == DIEGETIC_TYPE: - isEqualDiegetic(DIEGETIC_TYPE(**decoded), data) - elif type(generatedPIData[ts][pitype]) == ACOUSTIC_ENVIRONMENT: - isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) - elif type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: - isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) - elif type(generatedPIData[ts][pitype]) == list: - for r, d in zip( - generatedPIData[ts][pitype], decodedPiData[ts][pitype] - ): - isEqualAD(AUDIO_DESCRIPTION(**d), r) - else: - assert False, "Unsupported PI data found" - - # Generate RTPDUMP - addPI = False if format == "MONO" else True - if addPI: - # Add PI Data to Pack in RTP - validate.setPiDataFiles(tvArgs.piFiles()) - - extra_args = tvArgs.args(format, addPI) - extra_args += ["-rtpdump", str(framesPerPacket)] - dut_encoder_frontend.run( - bitrate=bitrate, - input_sampling_rate=48, - input_path=tvArgs.input(format), - output_bitstream_path=rtpdumpOut, - sba_order=None, - dtx_mode=dtx, - max_band=bandwidth, - add_option_list=extra_args, - ) - - unpacker = IvasRtp() - unpacker.unpackFile(rtpdumpOut) - for packet in unpacker.getPackets(): - validate.packet(packet) -- GitLab From 5cd304bcdc7d8f3597e7c9e794597a015d6c4e54 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 9 Oct 2025 14:22:36 +0300 Subject: [PATCH 226/340] Add rtpdump python tests and requirement --- tests/requirements.txt | 1 + tests/rtp/ivasrtp.py | 1654 ++++++++++++++++++++++++++++++++++++++++ tests/rtp/test_rtp.py | 665 ++++++++++++++++ 3 files changed, 2320 insertions(+) create mode 100644 tests/rtp/ivasrtp.py create mode 100644 tests/rtp/test_rtp.py diff --git a/tests/requirements.txt b/tests/requirements.txt index 2eb090f4fb..00100fae88 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,3 +2,4 @@ pytest>=5.3.5 pytest-xdist>=1.31.0 scipy>=1.5.2 numpy>=1.19.2 +bitstring>=4.3.1 diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py new file mode 100644 index 0000000000..bd13acaf56 --- /dev/null +++ b/tests/rtp/ivasrtp.py @@ -0,0 +1,1654 @@ +#!/usr/bin/env python3 + +__copyright__ = """ +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +__doc__ = """ +To configure test modules. +""" + +import struct +from enum import Enum +from dataclasses import dataclass, field, asdict +from bitstring import ConstBitStream, BitStream, ReadError +import json +import base64 +import argparse +from pathlib import Path +from typing import cast, Optional + +NO_REQ = "NO_REQ" + + +class CAMODE(str, Enum): + CA_LO_02 = "CA-LO-O2" + CA_LO_03 = "CA-LO-O3" + CA_LO_05 = "CA-LO-O5" + CA_LO_07 = "CA-LO-O7" + CA_HI_02 = "CA-HI-O2" + CA_HI_03 = "CA-HI-O3" + CA_HI_05 = "CA-HI-O5" + CA_HI_07 = "CA-HI-O7" + + +class CODECS(str, Enum): + AMRWB = "amrwb_io" + EVS = "evs" + IVAS = "ivas" + + +class SRCODEC(str, Enum): + LCLD = "lcld" + LC3PLUS = "lc3+" + + +class BANDWIDTH(str, Enum): + NB = "narrowband" + WB = "wideband" + SWB = "super wideband" + FB = "fullband" + + +class REQUESTS(str, Enum): + CODEC = ("codec",) + BR = "bitrate" + BW = "bandwidth" + CA = "ca-mode" + FMT = "format" + SUBFMT = "sub-format" + SRCFG = "sr-config" + + +class FORMATS(str, Enum): + STEREO = "Stereo" + SBA = "SBA" + MASA = "MASA" + ISM = "ISM" + MC = "MC" + OMASA = "OMASA" + OSBA = "OSBA" + + +class SUBFORMATS(str, Enum): + FOA_PLANAR = "FOA planar" + HOA2_PLANAR = "HOA2 planar" + HOA3_PLANAR = "HOA3 planar" + FOA = "FOA" + HOA2 = "HOA2" + HOA3 = "HOA3" + MASA1 = "MASA1" + MASA2 = "MASA2" + ISM1 = "ISM1" + ISM2 = "ISM2" + ISM3 = "ISM3" + ISM4 = "ISM4" + ISM1_EXTENDED_METADATA = "ISM1 extended metadata" + ISM2_EXTENDED_METADATA = "ISM2 extended metadata" + ISM3_EXTENDED_METADATA = "ISM3 extended metadata" + ISM4_EXTENDED_METADATA = "ISM4 extended metadata" + MC_5_1 = "MC 5.1" + MC_7_1 = "MC 7.1" + MC_5_1_2 = "MC 5.1.2" + MC_5_1_4 = "MC 5.1.4" + MC_7_1_4 = "MC 7.1.4" + Reserved22 = "Reserved22" + Reserved23 = "Reserved23" + Reserved24 = "Reserved24" + Reserved25 = "Reserved25" + Reserved26 = "Reserved26" + Reserved27 = "Reserved27" + Reserved28 = "Reserved28" + Reserved29 = "Reserved29" + Reserved30 = "Reserved30" + Reserved31 = "Reserved31" + Reserved32 = "Reserved32" + OMASA_ISM1_1TC = "OMASA ISM1 1TC" + OMASA_ISM2_1TC = "OMASA ISM2 1TC" + OMASA_ISM3_1TC = "OMASA ISM3 1TC" + OMASA_ISM4_1TC = "OMASA ISM4 1TC" + OMASA_ISM1_2TC = "OMASA ISM1 2TC" + OMASA_ISM2_2TC = "OMASA ISM2 2TC" + OMASA_ISM3_2TC = "OMASA ISM3 2TC" + OMASA_ISM4_2TC = "OMASA ISM4 2TC" + OSBA_ISM1_FOA_PLANAR = "OSBA ISM1 FOA planar" + OSBA_ISM2_FOA_PLANAR = "OSBA ISM2 FOA planar" + OSBA_ISM3_FOA_PLANAR = "OSBA ISM3 FOA planar" + OSBA_ISM4_FOA_PLANAR = "OSBA ISM4 FOA planar" + OSBA_ISM1_FOA = "OSBA ISM1 FOA" + OSBA_ISM2_FOA = "OSBA ISM2 FOA" + OSBA_ISM3_FOA = "OSBA ISM3 FOA" + OSBA_ISM4_FOA = "OSBA ISM4 FOA" + OSBA_ISM1_HOA2_PLANAR = "OSBA ISM1 HOA2 planar" + OSBA_ISM2_HOA2_PLANAR = "OSBA ISM2 HOA2 planar" + OSBA_ISM3_HOA2_PLANAR = "OSBA ISM3 HOA2 planar" + OSBA_ISM4_HOA2_PLANAR = "OSBA ISM4 HOA2 planar" + OSBA_ISM1_HOA2 = "OSBA ISM1 HOA2" + OSBA_ISM2_HOA2 = "OSBA ISM2 HOA2" + OSBA_ISM3_HOA2 = "OSBA ISM3 HOA2" + OSBA_ISM4_HOA2 = "OSBA ISM4 HOA2" + OSBA_ISM1_HOA3_PLANAR = "OSBA ISM1 HOA3 planar" + OSBA_ISM2_HOA3_PLANAR = "OSBA ISM2 HOA3 planar" + OSBA_ISM3_HOA3_PLANAR = "OSBA ISM3 HOA3 planar" + OSBA_ISM4_HOA3_PLANAR = "OSBA ISM4 HOA3 planar" + OSBA_ISM1_HOA3 = "OSBA ISM1 HOA3" + OSBA_ISM2_HOA3 = "OSBA ISM2 HOA3" + OSBA_ISM3_HOA3 = "OSBA ISM3 HOA3" + OSBA_ISM4_HOA3 = "OSBA ISM4 HOA3" + + +class PIDATAS(str, Enum): + SCENE_ORIENTATION = "SCENE_ORIENTATION" + DEVICE_ORIENTATION_COMPENSATED = "DEVICE_ORIENTATION_COMPENSATED" + DEVICE_ORIENTATION_UNCOMPENSATED = "DEVICE_ORIENTATION_UNCOMPENSATED" + ACOUSTIC_ENVIRONMENT = "ACOUSTIC_ENVIRONMENT" + AUDIO_DESCRIPTION = "AUDIO_DESCRIPTION" + ISM_NUM = "ISM_NUM" + ISM_ID = "ISM_ID" + ISM_GAIN = "ISM_GAIN" + ISM_ORIENTATION = "ISM_ORIENTATION" + ISM_POSITION = "ISM_POSITION" + ISM_DISTANCE_ATTENUATION = "ISM_DISTANCE_ATTENUATION" + ISM_DIRECTIVITY = "ISM_DIRECTIVITY" + DIEGETIC_TYPE = "DIEGETIC_TYPE" + RESERVED13 = "RESERVED13" + AUDIO_FOCUS_INDICATION = "AUDIO_FOCUS_INDICATION" + RESERVED15 = "RESERVED15" + PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" + HEAD_ORIENTATION = "HEAD_ORIENTATION" + LISTENER_POSITION = "LISTENER_POSITION" + DYNAMIC_AUDIO_SUPPRESSION = "DYNAMIC_AUDIO_SUPPRESSION" + AUDIO_FOCUS_REQUEST = "AUDIO_FOCUS_REQUEST" + PI_LATENCY = "PI_LATENCY" + R_ISM_ID = "R_ISM_ID" + R_ISM_GAIN = "R_ISM_GAIN" + R_ISM_ORIENTATION = "R_ISM_ORIENTATION" + R_ISM_POSITION = "R_ISM_POSITION" + R_ISM_DIRECTION = "R_ISM_DIRECTION" + RESERVED27 = "RESERVED27" + RESERVED28 = "RESERVED28" + RESERVED29 = "RESERVED29" + RESERVED30 = "RESERVED30" + NO_PI_DATA = "NO_PI_DATA" + + +class SUPPRESSION_LEVEL(int, Enum): + SUPPRESSION_LEVEL_NONE = 0 + SUPPRESSION_LEVEL_1 = 1 + SUPPRESSION_LEVEL_2 = 2 + SUPPRESSION_LEVEL_3 = 3 + SUPPRESSION_LEVEL_4 = 4 + SUPPRESSION_LEVEL_5 = 5 + SUPPRESSION_LEVEL_6 = 6 + SUPPRESSION_LEVEL_7 = 7 + SUPPRESSION_LEVEL_8 = 8 + SUPPRESSION_LEVEL_9 = 9 + SUPPRESSION_LEVEL_10 = 10 + SUPPRESSION_LEVEL_11 = 11 + SUPPRESSION_LEVEL_12 = 12 + SUPPRESSION_LEVEL_13 = 13 + SUPPRESSION_LEVEL_14 = 14 + SUPPRESSION_LEVEL_MAX = 15 + + +class AUDIO_FOCUS_LEVEL(int, Enum): + AUDIO_FOCUS_LEVEL_NONE = 0 + AUDIO_FOCUS_LEVEL_1 = 1 + AUDIO_FOCUS_LEVEL_2 = 2 + AUDIO_FOCUS_LEVEL_3 = 3 + AUDIO_FOCUS_LEVEL_4 = 4 + AUDIO_FOCUS_LEVEL_5 = 5 + AUDIO_FOCUS_LEVEL_6 = 6 + AUDIO_FOCUS_LEVEL_7 = 7 + AUDIO_FOCUS_LEVEL_8 = 8 + AUDIO_FOCUS_LEVEL_9 = 9 + AUDIO_FOCUS_LEVEL_10 = 10 + AUDIO_FOCUS_LEVEL_11 = 11 + AUDIO_FOCUS_LEVEL_12 = 12 + AUDIO_FOCUS_LEVEL_13 = 13 + AUDIO_FOCUS_LEVEL_DEFAULT = 14 + AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 + + +@dataclass +class RTPHDR: + version: int = 2 + padding: bool = False + extension: bool = False + csrcCount: int = 0 + marker: bool = False + payloadType: int = 0 + sequenceNum: int = 0 + timestamp: int = 0 + ssrc: int = 0 + extensionType: int = 0 + extensionLength: int = 0 + csrcList: list = field(default_factory=list) + extensionWords: list = field(default_factory=list) + + def updateHeader(self, numFrames: int): + self.sequenceNum = (self.sequenceNum + 1) % 65536 + self.timestamp += 320 * numFrames + + def pack(self, bitstrm: BitStream): + bitstrm.append(f"uint:2={self.version}") + bitstrm.append(f"bool={self.padding}") + bitstrm.append(f"bool={self.extension}") + bitstrm.append(f"uint:4={self.csrcCount}") + bitstrm.append(f"bool={self.marker}") + bitstrm.append(f"uint:7={self.payloadType}") + bitstrm.append(f"uintbe:16={self.sequenceNum}") + bitstrm.append(f"uintbe:32={self.timestamp}") + bitstrm.append(f"uintbe:32={self.ssrc}") + assert ( + len(self.csrcList) == self.csrcCount + ), "csrcList must be of length csrcCount" + for csrc in self.csrcList: + bitstrm.append(f"uintbe:32={csrc}") + if self.extension: + bitstrm.append(f"uintbe:16={self.extensionType}") + bitstrm.append(f"uintbe:16={self.extensionLength}") + assert ( + len(self.extensionWords) == self.extensionLength + ), "extensionWords must be of extensionLength csrcCount" + for ext in self.extensionWords: + bitstrm.append(f"uintbe:32={ext}") + + @classmethod + def unpack(cls, bitstrm: ConstBitStream): + hdr = cls() + hdr.version = bitstrm.read(2).uint + hdr.padding = bitstrm.read(1).bool + hdr.extension = bitstrm.read(1).bool + hdr.csrcCount = bitstrm.read(4).int + hdr.marker = bitstrm.read(1).bool + hdr.payloadType = bitstrm.read(7).int + hdr.sequenceNum = bitstrm.read(16).uintbe + hdr.timestamp = bitstrm.read(32).uintbe + hdr.ssrc = bitstrm.read(32).uintbe + if hdr.csrcCount: + hdr.csrcList = [bitstrm.read(32).uintbe for _ in range(hdr.csrcCount)] + if hdr.extension: + hdr.extensionType = bitstrm.read(16).uintbe + hdr.extensionLength = bitstrm.read(16).uintbe + hdr.extensionWords = [ + bitstrm.read(32).uintbe for _ in range(hdr.extensionLength) + ] + return hdr + + +@dataclass +class SRCONFIG: + diegetic: bool = False + yaw: bool = False + pitch: bool = False + roll: bool = False + + +@dataclass +class CMR: + bandwidth: BANDWIDTH + codec: CODECS = CODECS.IVAS + startIdx: int = 0 + endIdx: int = 0 + bitrates: list = field(default_factory=list) + + +@dataclass +class SRINFO: + bitrate: int = 0 + diegetic: bool = False + transportCodec: SRCODEC = SRCODEC.LCLD + + +@dataclass +class FRAME: + codec: CODECS = CODECS.IVAS + frmSizeBits: int = 0 + bitrate: int = 0 + speechLost: bool = False + srInfo: SRINFO = None + timestamp: int = 0 + au: bytes = field(default_factory=bytes) + + +# PI DATA STRUCTURES +@dataclass +class ORIENTATION: + w: float = 0.0 + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + + +@dataclass +class POSITION: + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + + +class ISM_POSITIONS: + positions: list[POSITION] + + +@dataclass +class AUDIO_DESCRIPTION: + isSpeech: bool = False + isMusic: bool = False + isAmbiance: bool = False + isEditable: bool = False + isBinaural: bool = False + + +@dataclass +class DYNAMIC_AUDIO_SUPPRESSION: + preferSpeech: bool = False + preferMusic: bool = False + preferAmbiance: bool = False + level: SUPPRESSION_LEVEL = SUPPRESSION_LEVEL.SUPPRESSION_LEVEL_MAX + + +@dataclass +class DIEGETIC_TYPE: + isDigetic: list[bool] + + +@dataclass +class ACOUSTIC_ENVIRONMENT: + aeid: int = 0 + rt60: tuple[float, float, float] = () + dsr: tuple[float, float, float] = () + dim: tuple[float, float, float] = () + abscoeff: tuple[float, float, float, float, float, float] = () + + +@dataclass +class AUDIO_FOCUS: + direction: Optional[ORIENTATION] = None + level: Optional[AUDIO_FOCUS_LEVEL] = None + + +@dataclass +class PIDATA: + timestamp: int = 0 + type: str = "NO_PI_DATA" + data: any = None + + +MAX_PACKED_PI_SIZE = 32 +ivasBitrates = [ + 13200, + 16400, + 24400, + 32000, + 48000, + 64000, + 80000, + 96000, + 128000, + 160000, + 192000, + 256000, + 384000, + 512000, + -1, + 5200, +] +evsBitrates = [ + 5900, + 7200, + 8000, + 9600, + 13200, + 16400, + 24400, + 32000, + 48000, + 64000, + 96000, + 128000, + 2400, + -1, + -1, + -1, +] +amrwbBitrates = [ + 6600, + 8850, + 12650, + 14250, + 15850, + 18250, + 19850, + 23050, + 23850, + 1750, + -1, + -1, + -1, + -1, + -1, + -1, +] +requestBitratesForCodec = { + CODECS.AMRWB: amrwbBitrates[0:9], + CODECS.EVS: evsBitrates[0:12], + CODECS.IVAS: ivasBitrates[0:14], +} +rt60Value = [ + 0.01, + 0.0126, + 0.0159, + 0.02, + 0.0252, + 0.0317, + 0.04, + 0.0504, + 0.0635, + 0.08, + 0.1008, + 0.1270, + 0.16, + 0.2016, + 0.2540, + 0.32, + 0.4032, + 0.5080, + 0.64, + 0.8063, + 1.0159, + 1.28, + 1.6127, + 2.0319, + 2.56, + 3.2254, + 4.0637, + 5.12, + 6.4508, + 8.1275, + 10.24, + 12.9016, +] +dsrValue = [ + -20.0, + -21.0, + -22.0, + -23.0, + -24.0, + -25.0, + -26.0, + -27.0, + -28.0, + -29.0, + -30.0, + -31.0, + -32.0, + -33.0, + -34.0, + -35.0, + -36.0, + -37.0, + -38.0, + -39.0, + -40.0, + -41.0, + -42.0, + -43.0, + -44.0, + -45.0, + -46.0, + -47.0, + -48.0, + -49.0, + -50.0, + -51.0, + -52.0, + -53.0, + -54.0, + -55.0, + -56.0, + -57.0, + -58.0, + -59.0, + -60.0, + -61.0, + -62.0, + -63.0, + -64.0, + -65.0, + -66.0, + -67.0, + -68.0, + -69.0, + -70.0, + -71.0, + -72.0, + -73.0, + -74.0, + -75.0, + -76.0, + -77.0, + -78.0, + -79.0, + -80.0, + -81.0, + -82.0, + -83.0, +] +roomDimensionValue = [ + 0.5, + 0.707, + 1.0, + 1.4141, + 2.0, + 2.8282, + 4.0, + 5.6568, + 8.0, + 11.314, + 16.0, + 22.627, + 32.0, + 45.255, + 64.0, + 90.51, +] +absorptionCoeffValues = [0.0800, 0.1656, 0.3430, 0.7101] +codedFormats = list(FORMATS) +codedSubFormats = list(SUBFORMATS) +PiTypeNames = list(PIDATAS) + + +def mapNearestIndex(table: list, val: float) -> int: + for idx, entry in enumerate(table): + if abs(entry) >= abs(val): + return idx + return len(table) - 1 + + +getListIndex = lambda mylist, val: mylist.index(val) if val in mylist else -1 + +cmrLookup = [ + CMR( + bandwidth=BANDWIDTH.NB, + codec=CODECS.EVS, + startIdx=0, + endIdx=7, + bitrates=evsBitrates, + ), # 000 = NB-EVS + CMR( + bandwidth=BANDWIDTH.WB, + codec=CODECS.AMRWB, + startIdx=0, + endIdx=9, + bitrates=amrwbBitrates, + ), # 001 = AMRWB IO + CMR( + bandwidth=BANDWIDTH.WB, + codec=CODECS.EVS, + startIdx=0, + endIdx=12, + bitrates=evsBitrates, + ), # 010 = WB-EVS + CMR( + bandwidth=BANDWIDTH.SWB, + codec=CODECS.EVS, + startIdx=3, + endIdx=12, + bitrates=evsBitrates, + ), # 011 = SWB-EVS + CMR( + bandwidth=BANDWIDTH.FB, + codec=CODECS.EVS, + startIdx=5, + endIdx=12, + bitrates=evsBitrates, + ), # 100 = FB-EVS + CMR( + bandwidth=BANDWIDTH.WB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] + ), # 101 = WB-CA + CMR( + bandwidth=BANDWIDTH.SWB, codec=CODECS.EVS, startIdx=0, endIdx=0, bitrates=[] + ), # 110 = SWB-CA + CMR( + bandwidth=NO_REQ, + codec=CODECS.IVAS, + startIdx=0, + endIdx=14, + bitrates=ivasBitrates, + ), # 111 = IVAS +] + +q15 = lambda x: int(min(32767.0, max(-32768.0, x * 32768.0))) + + +def unpackUnsupported(bitstrm: ConstBitStream, piSize: int) -> any: + # assert False, "Unsupported PI Data" + return base64.b64encode(bitstrm.read(piSize * 8).tobytes()).decode("utf-8") + + +def packUnsupported(bitstrm: ConstBitStream, data: any) -> any: + assert False, f"unsupported PI Data of type : {type(data)}" + + +def unpackNoPiData(bitstrm: ConstBitStream, piSize: int) -> None: + assert piSize == 0, "NO_PI_DATA should be 0 size" + + +def packNoPiData(bitstrm: BitStream, data: any = None): + pass + + +def unpackOrientations(bitstrm: ConstBitStream, piSize: int) -> list[ORIENTATION]: + assert ( + piSize % 8 + ) == 0 and piSize <= 32, "Incorrect PI Data Size for list[ORIENTATION]" + orientations = list() + while piSize > 0: + w = bitstrm.read(16).int / 32768.0 + x = bitstrm.read(16).int / 32768.0 + y = bitstrm.read(16).int / 32768.0 + z = bitstrm.read(16).int / 32768.0 + orientations.append(ORIENTATION(w, x, y, z)) + piSize -= 8 + return orientations + + +def packOrientations(bitstrm: BitStream, data: any): + assert type(data) == list, "Orientation PI Data expects a data of type list" + for orientation in cast(list, data): + assert ( + type(orientation) == ORIENTATION + ), "Orientation PI Data expects a data of type list[ORIENTATION]" + bitstrm.append(f"intbe:16={q15(orientation.w)}") + bitstrm.append(f"intbe:16={q15(orientation.x)}") + bitstrm.append(f"intbe:16={q15(orientation.y)}") + bitstrm.append(f"intbe:16={q15(orientation.z)}") + + +def unpackPositions(bitstrm: ConstBitStream, piSize: int) -> list[POSITION]: + assert piSize <= 24 and (piSize % 6) == 0, "Incorrect PI Data Size for Positions" + positions = list() + while piSize > 0: + x = bitstrm.read(16).int / 100.0 + y = bitstrm.read(16).int / 100.0 + z = bitstrm.read(16).int / 100.0 + positions.append(POSITION(x, y, z)) + piSize -= 6 + return positions + + +def packPositions(bitstrm: BitStream, data: any): + assert type(data) == list, "Position PI Data expects a data of type list" + positions = cast(list, data) + assert len(positions) <= 4, "Max one position per ISM object" + for position in positions: + assert ( + type(position) == POSITION + ), "Position PI Data expects a data of type list[POSITIONS]" + bitstrm.append(f"intbe:16={q15(position.x / 327.68)}") + bitstrm.append(f"intbe:16={q15(position.y / 327.68)}") + bitstrm.append(f"intbe:16={q15(position.z / 327.68)}") + + +def unpackOrientation(bitstrm: ConstBitStream, piSize: int) -> ORIENTATION: + assert piSize == 8, "Incorrect PI Data Size for ORIENTATION" + orientations = unpackOrientations(bitstrm, piSize) + assert len(orientations) == 1 + return orientations[0] + + +def packOrientation(bitstrm: BitStream, data: any): + assert ( + type(data) == ORIENTATION + ), "Orientation PI Data expects a data of type ORIENTATION" + orientation = cast(ORIENTATION, data) + packOrientations(bitstrm, [orientation]) + + +def unpackPosition(bitstrm: ConstBitStream, piSize: int) -> POSITION: + assert piSize == 6, "Incorrect PI Data Size for POSITION" + positions = unpackPositions(bitstrm, piSize) + assert len(positions) == 1 + return positions[0] + + +def packPosition(bitstrm: BitStream, data: any): + assert type(data) == POSITION, "Position PI Data expects a data of type POSITION" + position = cast(POSITION, data) + packPositions(bitstrm, [position]) + + +def unpackAudioDescription( + bitstrm: ConstBitStream, piSize: int +) -> list[AUDIO_DESCRIPTION]: + assert piSize <= 5, "Incorrect PI Data Size for AUDIO_DESCRIPTION" + ad = list() + for byte in range(piSize): + V = bitstrm.read(1).bool + M = bitstrm.read(1).bool + A = bitstrm.read(1).bool + E = bitstrm.read(1).bool + B = bitstrm.read(1).bool + _ = bitstrm.read(3) + ad.append( + AUDIO_DESCRIPTION( + isSpeech=V, isMusic=M, isAmbiance=A, isEditable=E, isBinaural=B + ) + ) + return ad + + +def packAudioDescription(bitstrm: BitStream, data: any): + assert ( + type(data) == list + ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + for desc in cast(list, data): + assert ( + type(desc) == AUDIO_DESCRIPTION + ), "Audio Description PI Data expects a data of type list[AUDIO_DESCRIPTION]" + ad = cast(AUDIO_DESCRIPTION, desc) + bitstrm.append(f"bool={ad.isSpeech}") + bitstrm.append(f"bool={ad.isMusic}") + bitstrm.append(f"bool={ad.isAmbiance}") + bitstrm.append(f"bool={ad.isEditable}") + bitstrm.append(f"bool={ad.isBinaural}") + bitstrm.append(f"uint:3=0") + + +def unpackDAS(bitstrm: ConstBitStream, piSize: int) -> list[AUDIO_DESCRIPTION]: + assert piSize == 2, "Incorrect PI Data Size for DYNAMIC_AUDIO_SUPPRESSION" + V = bitstrm.read(1).bool + M = bitstrm.read(1).bool + A = bitstrm.read(1).bool + _ = bitstrm.read(5) + SLI = bitstrm.read(4).uint + _ = bitstrm.read(4) + return DYNAMIC_AUDIO_SUPPRESSION( + preferSpeech=V, preferMusic=M, preferAmbiance=A, level=SLI + ) + + +def packDAS(bitstrm: BitStream, data: any): + assert ( + type(data) == DYNAMIC_AUDIO_SUPPRESSION + ), "Dynamic Audio Suppression PI Data expects a data of type DYNAMIC_AUDIO_SUPPRESSION" + das = cast(DYNAMIC_AUDIO_SUPPRESSION, data) + bitstrm.append(f"bool={das.preferSpeech}") + bitstrm.append(f"bool={das.preferMusic}") + bitstrm.append(f"bool={das.preferAmbiance}") + bitstrm.append(f"uint:5=0") + bitstrm.append(f"uint:4={das.level}") + bitstrm.append(f"uint:4=0") + + +def unpackDiegetic(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: + assert piSize == 1, "Incorrect PI Data Size for DIEGETIC_TYPE" + digType = list() + for _ in range(5): # no way to know how many bits are valid bits, so all 5 read + digType.append(bitstrm.read(1).bool) + bitstrm.bytealign() + return DIEGETIC_TYPE(isDigetic=digType) + + +def packDiegetic(bitstrm: BitStream, data: any): + assert ( + type(data) == DIEGETIC_TYPE + ), "Diegetic type PI Data expects a data of type DIEGETIC_TYPE" + diegetic = cast(DIEGETIC_TYPE, data) + assert ( + len(diegetic.isDigetic) <= 5 + ), "Maximum 1 bit per object + 1 bit for SBA/MASA is required (max 5)" + for isDigetic in diegetic.isDigetic: + bitstrm.append(f"bool={isDigetic}") + nPad = 8 - (bitstrm.pos % 8) + if nPad > 0: + bitstrm.append(f"uint:{nPad}=0") + + +def unpackAcousticEnv(bitstrm: ConstBitStream, piSize: int) -> DIEGETIC_TYPE: + assert ( + piSize == 1 or piSize == 5 or piSize == 8 + ), "Incorrect PI Data Size for ACOUSTIC_ENVIRONMENT" + rt60 = list() + dsr = list() + dim = list() + absCoeff = list() + + if piSize == 1: + bitstrm.read(1) + + aeid = bitstrm.read(7).uint + + if piSize >= 5: + for _ in range(3): + rt60.append(rt60Value[bitstrm.read(5).uint]) + dsr.append(dsrValue[bitstrm.read(6).uint]) + if piSize == 8: + for _ in range(3): + dim.append(roomDimensionValue[bitstrm.read(4).uint]) + for _ in range(6): + absCoeff.append(absorptionCoeffValues[bitstrm.read(2).uint]) + + return ACOUSTIC_ENVIRONMENT( + aeid=aeid, + rt60=tuple(rt60), + dsr=tuple(dsr), + dim=tuple(dim), + abscoeff=tuple(absCoeff), + ) + + +def packAcousticEnv(bitstrm: BitStream, data: any): + assert ( + type(data) == ACOUSTIC_ENVIRONMENT + ), "Diegetic type PI Data expects a data of type ACOUSTIC_ENVIRONMENT" + aenv = cast(ACOUSTIC_ENVIRONMENT, data) + if not aenv.rt60 and not aenv.dsr: + bitstrm.append(f"uint:8={aenv.aeid % 128}") + else: + assert ( + len(aenv.rt60) == 3 and len(aenv.dsr) == 3 + ), "Lo, Mi, Hi only required for RT60 and DSR values" + bitstrm.append(f"uint:7={aenv.aeid % 128}") + for n in range(3): + rt60 = mapNearestIndex(rt60Value, aenv.rt60[n]) + dsr = mapNearestIndex(dsrValue, aenv.dsr[n]) + bitstrm.append(f"uint:5={rt60}") + bitstrm.append(f"uint:6={dsr}") + if aenv.abscoeff and aenv.dim: + assert len(aenv.abscoeff) == 6 and len(aenv.dim) == 3 + for n in range(3): + dim = mapNearestIndex(roomDimensionValue, aenv.dim[n]) + bitstrm.append(f"uint:4={dim}") + for n in range(6): + absCoeff = mapNearestIndex(absorptionCoeffValues, aenv.abscoeff[n]) + bitstrm.append(f"uint:2={absCoeff}") + + +def unpackAudioFocus(bitstrm: ConstBitStream, piSize: int) -> AUDIO_FOCUS: + assert ( + piSize == 1 or piSize == 8 or piSize == 9 + ), "Incorrect PI Data Size for AUDIO_FOCUS" + direction = None + level = None + if piSize == 1: + level = bitstrm.read(4).uint + _ = bitstrm.read(4) + else: + direction = unpackOrientation(bitstrm, 8) + if piSize == 9: + level = bitstrm.read(4).uint + _ = bitstrm.read(4) + + return AUDIO_FOCUS(direction=direction, level=level) + + +def packAudioFocus(bitstrm: BitStream, data: any): + assert ( + type(data) == AUDIO_FOCUS + ), "Audio focus PI Data expects a data of type AUDIO_FOCUS" + auFocus = cast(AUDIO_FOCUS, data) + if auFocus.direction is not None: + packOrientations(bitstrm, [auFocus.direction]) + if auFocus.level is not None: + level = int(auFocus.level) + bitstrm.append(f"uint:4={level}") + bitstrm.append(f"uint:4=0") + + +PIDataUnpacker = [ + unpackOrientation, # SCENE_ORIENTATION, + unpackOrientation, # DEVICE_ORIENTATION_COMPENSATED, + unpackOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + unpackAcousticEnv, # ACOUSTIC_ENVIRONMENT + unpackAudioDescription, # AUDIO_DESCRIPTION + unpackUnsupported, # ISM_NUM + unpackUnsupported, # ISM_ID + unpackUnsupported, # ISM_GAIN + unpackOrientations, # ISM_ORIENTATION + unpackPositions, # ISM_POSITION + unpackUnsupported, # ISM_DISTANCE_ATTENUATION + unpackUnsupported, # ISM_DIRECTIVITY + unpackDiegetic, # DIEGETIC_TYPE + unpackUnsupported, # RESERVED13 + unpackAudioFocus, # AUDIO_FOCUS_INDICATION + unpackUnsupported, # RESERVED15 + unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION + unpackOrientation, # HEAD_ORIENTATION + unpackPosition, # LISTENER_POSITION + unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION + unpackAudioFocus, # AUDIO_FOCUS_REQUEST + unpackUnsupported, # PI_LATENCY + unpackUnsupported, # R_ISM_ID + unpackUnsupported, # R_ISM_GAIN + unpackOrientation, # R_ISM_ORIENTATION + unpackPosition, # R_ISM_POSITION + unpackUnsupported, # R_ISM_DIRECTION + unpackUnsupported, # RESERVED27 + unpackUnsupported, # RESERVED28 + unpackUnsupported, # RESERVED29 + unpackUnsupported, # RESERVED30 + unpackNoPiData, # NO_DATA +] + +PIDataPacker = [ + packOrientation, # SCENE_ORIENTATION, + packOrientation, # DEVICE_ORIENTATION_COMPENSATED, + packOrientation, # DEVICE_ORIENTATION_UNCOMPENSATED + packAcousticEnv, # ACOUSTIC_ENVIRONMENT + packAudioDescription, # AUDIO_DESCRIPTION + packUnsupported, # ISM_NUM + packUnsupported, # ISM_ID + packUnsupported, # ISM_GAIN + packOrientations, # ISM_ORIENTATION + packPositions, # ISM_POSITION + packUnsupported, # ISM_DISTANCE_ATTENUATION + packUnsupported, # ISM_DIRECTIVITY + packDiegetic, # DIEGETIC_TYPE + packUnsupported, # RESERVED13 + packAudioFocus, # AUDIO_FOCUS_INDICATION + packUnsupported, # RESERVED15 + packOrientation, # PLAYBACK_DEVICE_ORIENTATION + packOrientation, # HEAD_ORIENTATION + packPosition, # LISTENER_POSITION + packDAS, # DYNAMIC_AUDIO_SUPPRESSION + packAudioFocus, # AUDIO_FOCUS_DIRECTION + packUnsupported, # PI_LATENCY + packUnsupported, # R_ISM_ID + packUnsupported, # R_ISM_GAIN + packOrientation, # R_ISM_ORIENTATION + packPosition, # R_ISM_POSITION + packUnsupported, # R_ISM_DIRECTION + packUnsupported, # RESERVED27 + packUnsupported, # RESERVED28 + packUnsupported, # RESERVED29 + packUnsupported, # RESERVED30 + packNoPiData, # NO_DATA +] + + +def ReadG192Bitstream(g192File: Path) -> list[bytes]: + refPackets = list[bytes]() + with open(g192File, "rb") as fd: + refBitStrm = ConstBitStream(fd.read()) + while refBitStrm.pos < refBitStrm.len: + sync = hex(refBitStrm.read(16).intle) + nBits = refBitStrm.read(16).intle + assert sync == "0x6b21", "G192 syncword not found at start of packet" + writer = BitStream() + for _ in range(nBits): + bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" + writer.append(bit) + refPackets.append(writer.tobytes()) + return refPackets + + +def unpackEBytes(bitstrm: ConstBitStream) -> tuple[bool, dict]: + piIndicated = False + requests = dict() + try: + if bitstrm.read(1).bool: + T = bitstrm.read(3).uint + BR = bitstrm.read(4).uint + if T in [5, 6]: # CA MODES + if BR < 8: + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = 13200 + requests[REQUESTS.CA] = BR + requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception("Unsupported BR bits in CA Mode") + elif T == 7: # IVAS + if BR < 14: + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + requests[REQUESTS.CA] = -1 + elif BR == 14: + raise Exception("Reserved BR idx in IVAS EByte") + else: + if BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx: + requests[REQUESTS.CODEC] = cmrLookup[T].codec + requests[REQUESTS.BR] = cmrLookup[T].bitrates[BR] + requests[REQUESTS.CA] = -1 + requests[REQUESTS.BW] = cmrLookup[T].bandwidth + else: + raise Exception( + "Reserved BR idx in {} EByte".format(cmrLookup[T].codec) + ) + # Try to get all subsequent E-bytes + while bitstrm.read(1).bool: + ET = bitstrm.read(3).uint + if ET == 0: + supportedBW = [ + BANDWIDTH.WB, + BANDWIDTH.SWB, + BANDWIDTH.FB, + BANDWIDTH.NREQ, + ] + reserved = bitstrm.read(2) + BW = bitstrm.read(2).uint + requests[REQUESTS.BW] = supportedBW[BW] + elif ET == 1: + S = bitstrm.read(1).bool + FMT = bitstrm.read(3).uint + if not S: + requests[REQUESTS.FMT] = codedFormats[FMT] + requests[REQUESTS.SUBFMT] = NO_REQ + else: + reserved = bitstrm.read(2) + subFMT = bitstrm.read(6).uint + requests[REQUESTS.FMT] = NO_REQ + requests[REQUESTS.SUBFMT] = codedSubFormats[subFMT] + elif ET == 2: + reserved = bitstrm.read(4) + piIndicated = True + elif ET == 3: + D = bitstrm.read(1).bool + Y = bitstrm.read(1).bool + P = bitstrm.read(1).bool + R = bitstrm.read(1).bool + requests[REQUESTS.SRCFG] = SRCONFIG( + diegetic=D, yaw=Y, pitch=P, roll=R + ) + else: + reserved = bitstrm.read(4) + raise Exception( + "Unsupported subsequent EByte with ET={}".format(ET) + ) + except ReadError as error: + print("Underflow in E-Bytes parsing during unpacking, error = {}".format(error)) + return piIndicated, requests + + +def packEBytes(bitstrm: BitStream, requests: dict[str:any], piIndication: bool = False): + codec = requests[REQUESTS.CODEC] if REQUESTS.CODEC in requests.keys() else NO_REQ + bitrate = requests[REQUESTS.BR] if REQUESTS.BR in requests.keys() else 0 + bandwidth = requests[REQUESTS.BW] if REQUESTS.BW in requests.keys() else NO_REQ + camode = requests[REQUESTS.CA] if REQUESTS.CA in requests.keys() else NO_REQ + format = requests[REQUESTS.FMT] if REQUESTS.FMT in requests.keys() else NO_REQ + subFormat = ( + requests[REQUESTS.SUBFMT] if REQUESTS.SUBFMT in requests.keys() else NO_REQ + ) + srcfg = ( + SRCONFIG(requests[REQUESTS.SRCFG]) + if REQUESTS.SRCFG in requests.keys() + else None + ) + + # Check if any request needs to be sent + isInitialEByteNeeded = ( + piIndication + or bitrate != 0 + or bandwidth != NO_REQ + or camode != NO_REQ + or format != NO_REQ + or subFormat != NO_REQ + or srcfg != None + ) + + if not isInitialEByteNeeded: + return + + if camode != NO_REQ: + T = 6 if bandwidth == BANDWIDTH.SWB else 5 + BR = getListIndex(list(CAMODE), camode) + assert BR > 0, "Channel Aware Mode not supported" + elif bitrate == 0: + T = 7 + BR = 15 + else: + mapBandwidthToTBit = { + BANDWIDTH.NB: 0, + BANDWIDTH.WB: 2, + BANDWIDTH.SWB: 3, + BANDWIDTH.FB: 4, + NO_REQ: 2, + }[bandwidth] + T = {CODECS.AMRWB: 1, CODECS.IVAS: 7, CODECS.EVS: mapBandwidthToTBit}[codec] + BR = getListIndex(requestBitratesForCodec[codec], bitrate) + assert ( + BR >= cmrLookup[T].startIdx and BR < cmrLookup[T].endIdx + ), "EVS Bitrate Index and Bandwidth Combination cannot be requested" + + # Write the Initial E-Byte + bitstrm.append(f"bool={True}") # E-Byte H=1 + bitstrm.append(f"uint:3={T}") + bitstrm.append(f"uint:4={BR}") + + # Subsequent E-bytes follow + if piIndication: + bitstrm.append("hex:8=A0") + + if codec != CODECS.IVAS: + return + + # Bandwidth E-Byte + if bandwidth != NO_REQ: + bw = {BANDWIDTH.WB: 0, BANDWIDTH.SWB: 1, BANDWIDTH.FB: 2}[bandwidth] + bitstrm.append("hex:4=8") + bitstrm.append(f"uint:4={bw}") + + # Coded Format/SubFormat Request E-Byte + if subFormat in SUBFORMATS: + subFmt = getListIndex(codedSubFormats, subFormat) + bitstrm.append("hex:8=9F") # S=0, FMT=111 + bitstrm.append(f"uint:8={subFmt}") + elif format in FORMATS: + fmt = getListIndex(codedFormats, format) + bitstrm.append("hex:4=9") + bitstrm.append(f"bool={False}") # S=0 + bitstrm.append(f"uint:3={fmt}") + + # SR Config E-Byte + with srcfg: + bitstrm.append("hex:4=B") + bitstrm.append(f"bool={srcfg.diegetic}") + bitstrm.append(f"bool={srcfg.yaw}") + bitstrm.append(f"bool={srcfg.pitch}") + bitstrm.append(f"bool={srcfg.roll}") + + +def unpackAUFrames(bitstrm: ConstBitStream, frameList: list[FRAME]): + try: + # Unpack Frame AUs here + for idx, frm in enumerate(frameList): + auSize = (frm.frmSizeBits + 7) // 8 # Zero padded bytes in amrwb_io mode + frm.au = bitstrm.read(auSize * 8).tobytes() + except ReadError as error: + print( + "Underflow in AU Frames parsing during unpacking, error = {}".format(error) + ) + + +def packAUFrames(bitstrm: BitStream, frameList: list[FRAME]): + for frm in frameList: + bitstrm.append(frm.au) + bitstrm.bytealign() + + +def unpackToCBytes( + bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS +) -> list[FRAME]: + F = True + frmList = list() + + try: + while F: + F = bitstrm.read(1).bool + FT = bitstrm.read(2).uint + BR = bitstrm.read(4).uint + frm = FRAME(timestamp=rtpTimestamp) + if FT == 1: + frm.codec = CODECS.IVAS + if BR == 14: + supportedBitrates = [-1, 256000, 384000, 512000] + reserved = bitstrm.read(1) + D = bitstrm.read(1).bool + C = bitstrm.read(1).bool + SR_BR = bitstrm.read(2).uint + reserved = bitstrm.read(3) + if SR_BR == 0: + raise Exception("Reserved bitrate in SR Config ToC Indicated") + frm.srInfo = SRINFO( + bitrate=supportedBitrates[SR_BR], + diegetic=D, + transportCodec=SRCODEC.LC3PLUS if C else SRCODEC.LCLD, + ) + else: + frm.bitrate = ivasBitrates[BR] + elif FT == 0: + # Codec switch only if not NO_DATA_FRAME, as IVAS/EVS signal using this + frm.codec = CODECS.EVS if BR != 15 else Codec + if BR == 13: + raise Exception("Reserved bitrate in EVS ToC Indicated") + frm.speechLost = BR == 14 + frm.bitrate = evsBitrates[BR] if BR < 13 else 0 + else: + frm.codec = CODECS.AMRWB + if BR >= 10 and BR <= 13: + raise Exception("Reserved bitrate in AMRWB-IO ToC Indicated") + frm.speechLost = (BR == 14) or (FT == 2) + frm.bitrate = amrwbBitrates[BR] if BR < 10 else 0 + frm.frmSizeBits = frm.bitrate // 50 + rtpTimestamp += 320 + frmList.append(frm) + if F: + # skip all frame specific E-bytes before next header + while bitstrm.read(1).bool: + print("Skipping unsupported frame specific subsequent Ebytes") + reserved = bitstrm.read(7) + except ReadError as error: + print("Underflow in ToC parsing during unpacking, error = {}".format(error)) + + return frmList + + +def packToCBytes(bitstrm: BitStream, frameList: list[FRAME]): + numFrames = len(frameList) + + for idx, frame in enumerate(frameList): + F = idx != (numFrames - 1) + FT = 0 + BR = 0 + if frame.frmSizeBits == 0: + FT = ( + 3 if frame.codec == CODECS.AMRWB else 0 + ) # Only AMRWB or EVS support 0 frame case + BR = 14 if frame.speechLost else 15 # SPEECH_LOST or NO_DATA + elif frame.codec == CODECS.AMRWB: + FT = 3 + BR = getListIndex(amrwbBitrates[0:10], frame.bitrate) + elif frame.codec == CODECS.IVAS: + FT = 1 + BR = 14 if frame.srInfo else getListIndex(ivasBitrates, frame.bitrate) + else: + FT = 0 + BR = getListIndex(evsBitrates[0:13], frame.bitrate) + + assert BR >= 0, "Index for table not found" + + bitstrm.append(f"bool={False}") # ToC 0 bit + bitstrm.append(f"bool={F}") # Frame follows bit + bitstrm.append(f"uint:2={FT}") # Frame Type + bitstrm.append(f"uint:4={BR}") # Frame Type + if frame.srInfo and frame.codec == CODECS.IVAS: + SRBR = (frame.srInfo.bitrate // 128000) - 1 + bitstrm.append(f"bool={False}") # ToC 0 bit + bitstrm.append(f"bool={frame.srInfo.diegetic}") + bitstrm.append(f"bool={frame.srInfo.transportCodec == SRCODEC.LC3PLUS}") + bitstrm.append(f"uint:2={SRBR}") + bitstrm.append("uint:3=0") + + +def unpackPiData(bitstrm: ConstBitStream, rtpTimestamp: int) -> list[PIDATA]: + piDataList = list[PIDATA]() + try: + # PI Data if Indicated + PF = True + piTimeStamps = rtpTimestamp + while PF: + PF = bitstrm.read(1).bool + PM = bitstrm.read(2).uint + PiType = bitstrm.read(5).uint + PiSize = 0 + byte = 255 + while byte == 255: + byte = bitstrm.read(8).uint + PiSize += byte + + PiFrameData = PIDataUnpacker[PiType](bitstrm, PiSize) + + if PiTypeNames[PiType] != PIDATAS.NO_PI_DATA: + piDataList.append( + PIDATA( + timestamp=( + piTimeStamps if PM != 3 else rtpTimestamp + ), # Generic Pi has base timestamp + type=PiTypeNames[PiType], + data=PiFrameData, + ) + ) + piTimeStamps += 320 if PM == 2 else 0 + except ReadError as error: + print("Underflow before completion of unpacking, error = {}".format(error)) + return piDataList + + +def packPiData( + bitstrm: BitStream, rtpTimestampBounds: tuple[int, int], piDataList: list[PIDATA] +): + # sort the piDataList by timestamp and eliminate data where timestamp is OOB for this packet + piDataList = [ + data + for data in piDataList + if ( + data.timestamp >= rtpTimestampBounds[0] + and data.timestamp < rtpTimestampBounds[1] + ) + ] + sorted(piDataList, key=lambda data: data.timestamp) + numPiData = len(piDataList) + + # Group PI data by timestamps + piDataDict = dict[int, list[PIDATA]]() + for data in piDataList: + ts = (data.timestamp // 320) * 320 + if ts not in piDataDict.keys(): + piDataDict[ts] = list() + piDataDict[ts].append(data) + + curTimestamp = rtpTimestampBounds[0] + for ts, dataList in piDataDict.items(): + while curTimestamp < ts: + # Inset NO_PI_DATA till current Timestamp is reached + bitstrm.append( + "hex:16=DF00" + ) # PI Frame follows, Last PI header for this frame, NO_PI_DATA, size=0 + curTimestamp += 320 + + for idx, data in enumerate(dataList): + pack = BitStream() + PM = 2 if idx == len(dataList) - 1 else 1 + PF = 0 if numPiData == 1 else 1 + TYPE = PiTypeNames.index(data.type) + PIDataPacker[TYPE](pack, data.data) + assert (pack.pos % 8) == 0, "PI data must be byte aligned" + SIZE = pack.pos // 8 + assert ( + SIZE < MAX_PACKED_PI_SIZE + ), f"Packed PI Size should be less than MAX_PACKED_PI_SIZE ({MAX_PACKED_PI_SIZE})" + + bitstrm.append(f"uint:1={PF}") + bitstrm.append(f"uint:2={PM}") + bitstrm.append(f"uint:5={TYPE}") + bitstrm.append(f"uint:8={SIZE}") + bitstrm.append(pack) + numPiData -= 1 + curTimestamp += 320 + assert numPiData == 0, "Not all PI data was packed due to internal error" + + +@dataclass +class IvasPayload: + frameList: list[FRAME] = field(default_factory=list) + piDataList: list[PIDATA] = field(default_factory=list) + requests: dict[str, any] = field(default_factory=dict) + + def pack(self, bitstrm: BitStream) -> int: + piIndication = len(self.piDataList) > 0 + numFrames = len(self.frameList) + packEBytes(bitstrm, self.requests, piIndication) + packToCBytes(bitstrm, self.frameList) + packAUFrames(bitstrm, self.frameList) + if piIndication: + packPiData( + bitstrm, + (self.frameList[0].timestamp, self.frameList[-1].timestamp + 320), + self.piDataList, + ) + return numFrames + + @classmethod + def unpack(cls, bitstrm: ConstBitStream, rtpTimestamp: int, Codec: CODECS): + # Unpack the E-bytes + piIndicated, requests = unpackEBytes(bitstrm) + + # ToC Byte parsing starts with 'F' bit as H bit is already read for E-byte parsing + frameList = unpackToCBytes(bitstrm, rtpTimestamp, Codec) + + # Extract packed AU + unpackAUFrames(bitstrm, frameList) + + if piIndicated: + piDataList = unpackPiData(bitstrm, rtpTimestamp) + else: + piDataList = list() + + return cls(frameList=frameList, piDataList=piDataList, requests=requests) + + +@dataclass +class IvasPacket: + hdr: RTPHDR = field(default_factory=RTPHDR) + payload: IvasPayload = field(default_factory=IvasPayload) + + def pack(self, bitstrm: BitStream): + self.hdr.pack(bitstrm) + numFrames = self.payload.pack(bitstrm) + self.hdr.updateHeader(numFrames) + + +class IvasRtp: + def __init__(self, numFramesPerPacket=4, codec: CODECS = CODECS.IVAS): + self.numFramesPerPacket = numFramesPerPacket + self.packets = list[IvasPacket]() + self.Codec: CODECS = codec # Track last frame's codec + self.requests = dict() + self.piData = dict() + + def dumpToJSON(self, jsonFileName): + with open(jsonFileName, "w") as fd: + packets = list() + for packet in self.packets: + packetDict = asdict(packet) + for frame in packetDict["payload"]["frameList"]: + frame["au"] = base64.b64encode(frame["au"]).decode("utf-8") + packets.append(packetDict) + json_output = json.dumps(packets, indent=4) + fd.write(json_output) + + def requestReader(self, timestamp: int) -> dict[str, any]: + tsList = sorted(self.requests.keys()) + if len(tsList) > 0: + lastTs = int(tsList[0]) + for ts in tsList: + if timestamp >= lastTs and timestamp < int(ts): + return self.requests[str(lastTs)] + lastTs = int(ts) + return dict() + + def piDataReader(self, startTimestamp: int, endTimestamp: int) -> list[PIDATA]: + + piDataList = list() + while startTimestamp < endTimestamp: + ts = str(startTimestamp) + if ts in self.piData.keys(): + for piTypes in self.piData[ts].keys(): + dataDict = self.piData[ts][piTypes] + if type(dataDict) != dict: + data = dataDict + elif piTypes == PIDATAS.ISM_ORIENTATION: + data = list() + for orientation in dataDict: + data.append(ORIENTATION(**orientation)) + elif ( + "ORIENTATION" in piTypes + or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION + ): + data = ORIENTATION(**dataDict) + elif piTypes == PIDATAS.ACOUSTIC_ENVIRONMENT: + data = ACOUSTIC_ENVIRONMENT(**dataDict) + elif piTypes == PIDATAS.AUDIO_DESCRIPTION: + data = list() + for desc in dataDict: + data.append(AUDIO_DESCRIPTION(**desc)) + elif piTypes == PIDATAS.DIEGETIC_TYPE: + data = DIEGETIC_TYPE(**dataDict) + elif ( + piTypes == PIDATAS.LISTENER_POSITION + or piTypes == PIDATAS.R_ISM_POSITION + ): + data = POSITION(**dataDict) + elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: + data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) + else: + assert False, "Unhandled PI Data" + piDataList.append( + PIDATA(timestamp=startTimestamp, type=piTypes, data=data) + ) + startTimestamp += 320 + return piDataList + + def packG192File( + self, + g192File: Path, + rtpDumpOut: Path, + piData: dict = None, + requestsData: dict = None, + ): + packet = IvasPacket() + packet.hdr.sequenceNum = int("0xFFFF", 16) + packet.hdr.timestamp = 0 + packet.hdr.ssrc = int("0xDEADBEEF", 16) + + self.piData = piData + self.requests = requestsData + + timestamp = packet.hdr.timestamp + piTimestamps = 0 + with open(g192File, "rb") as fin: + with open(rtpDumpOut, "wb") as fout: + refBitStrm = ConstBitStream(fin.read()) + frames = list[FRAME]() + while refBitStrm.pos < refBitStrm.len: + sync = hex(refBitStrm.read(16).intle) + nBits = refBitStrm.read(16).intle + assert ( + sync == "0x6b21" + ), "G192 syncword not found at start of packet" + writer = BitStream() + for _ in range(nBits): + bit = "0b1" if refBitStrm.read(16).uintle == 129 else "0b0" + writer.append(bit) + frames.append( + FRAME( + codec=self.Codec, + frmSizeBits=nBits, + bitrate=nBits * 50, + speechLost=False, + timestamp=timestamp, + au=writer.tobytes(), + ) + ) + if (len(frames) == self.numFramesPerPacket) or ( + refBitStrm.pos == refBitStrm.len + ): + rtpBitstrm = BitStream() + numFrames = len(frames) + packet.payload = IvasPayload( + frameList=frames, + piDataList=self.piDataReader( + piTimestamps, piTimestamps + (numFrames * 320) + ), + requests=self.requestReader(piTimestamps), + ) + packet.pack(bitstrm=rtpBitstrm) + fout.write(struct.pack("i", rtpBitstrm.bytepos)) + fout.write(rtpBitstrm.tobytes()) + frames = list() + piTimestamps += numFrames * 320 + timestamp += 320 + + def getPackets(self): + return self.packets + + def unpackFile(self, rtpDumpFile): + with open(rtpDumpFile, mode="rb") as fd: + while True: + size = fd.read(4) + if not size: + break + size = struct.unpack("i", size)[0] + packet = fd.read(size) + if not packet: + break + self.packets.append(self.unpackPacket(packet)) + + def unpackPacket(self, packet) -> IvasPacket: + bitStrm = ConstBitStream(packet) + hdr = RTPHDR.unpack(bitStrm) + payload = IvasPayload.unpack( + bitStrm, rtpTimestamp=hdr.timestamp, Codec=self.Codec + ) + self.Codec = payload.frameList[ + -1 + ].codec # Last Frame's codec for next frame for NO_DATA case + return IvasPacket(hdr=hdr, payload=payload) + + +class ArgsParser: + def __init__(self): + self.parser = argparse.ArgumentParser() + self.parser.add_argument( + "-r", "--rtpdump", type=str, default=None, help="RTP Dump to unpack" + ) + self.parser.add_argument( + "-j", + "--json", + type=str, + default="unpack.json", + help="Output unpacked RTP frames to JSON file", + ) + self.parser.add_argument( + "-g", + "--g192", + type=str, + default=None, + help="G192 bitstream input for RTP Packing", + ) + self.parser.add_argument( + "-f", + "--framesPerPacket", + type=int, + default=1, + help="Number of IVAS frames per RTP Packet", + ) + self.parser.add_argument( + "-o", + "--outrtpdump", + type=str, + default="output.rtpdump", + help="Output RTP Dump file", + ) + self.parser.add_argument( + "-p", "--piDataJson", type=str, default=None, help="piData to be packed" + ) + self.parser.add_argument( + "-x", "--requestsJson", type=str, default=None, help="Requests to be packed" + ) + + def parse(self): + args = self.parser.parse_args() + return args + + +if __name__ == "__main__": + args = ArgsParser().parse() + rtp = IvasRtp(numFramesPerPacket=args.framesPerPacket) + + if args.rtpdump: + rtp.unpackFile(args.rtpdump) + rtp.dumpToJSON(args.json) + elif args.g192: + piData = dict() + requestsData = dict() + if args.piDataJson: + with open(args.piDataJson) as f: + piData = json.load(f) + if args.requestsJson: + with open(args.requestsJson) as f: + requestsData = json.load(f) + rtp.packG192File( + g192File=args.g192, + rtpDumpOut=args.outrtpdump, + piData=piData, + requestsData=requestsData, + ) diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py new file mode 100644 index 0000000000..69b85dc9af --- /dev/null +++ b/tests/rtp/test_rtp.py @@ -0,0 +1,665 @@ +#!/usr/bin/env python3 + +__copyright__ = """ +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +__doc__ = """ +This test does the following procedure:- + +DECODER TEST +============ +* Encode a input for given format, bitrate and dtx info using IVAS Encoder to g192 +* Generate random PI data to be used for RTP packaging +* Use g192 and random PI data to pack a stream into RTPDump stream using the reference rtp packer implementation in python +* Provide the rtpdump stream to IVAS decoder to decode the stream and generate a PI data dump in a JSON file +* Decode G192 stream using IVAS decoder and compare against output of RTPdump for bit-exactness +* Validate dumped JSON agains original random PI data to check for similar data after deocde + +ENCODER TEST +============ +* Encode the input for given format, bitrate and dtx info using IVAS Encoder to rtpdump directly +* Validate the generated rtp dump using reference python implementation of RTP depacker for following:- + * RTP Header consistency (Timestamp, seq number, etc) + * IVAS Payload (no of frames in packet, bitrate indicated, bitexactness of frames in packet) + * PI Data verification (Timestamp, Pidata Type, similarity of PI data) +""" + +import pytest +import csv +import os +import sys +import random + +from tempfile import TemporaryDirectory +from pathlib import Path +from ivasrtp import * +import numpy as np +from pyaudio3dtools.audiofile import readfile + +ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) +sys.path.append(ROOT_DIR) + +from tests.conftest import EncoderFrontend, DecoderFrontend + + +@pytest.mark.parametrize("dtx", [False, True]) +@pytest.mark.parametrize("bitrate", [6600, 12650, 23850]) +@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +def test_rtp_bitstream_amrwb( + test_info, + bitrate: int, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend, +): + run_rtp_bitstream_tests( + CODECS.AMRWB, + bitrate, + "WB", + "OFF", + "MONO", + dtx, + framesPerPacket, + dut_encoder_frontend, + dut_decoder_frontend, + ) + + +@pytest.mark.parametrize("dtx", [False, True]) +@pytest.mark.parametrize("bitrate", [9600, 24400, 128000]) +@pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) +@pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) +@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +def test_rtp_bitstream_evs( + test_info, + bitrate: int, + bandwidth: str, + caMode: str, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend, +): + run_rtp_bitstream_tests( + CODECS.EVS, + bitrate, + bandwidth, + caMode, + "MONO", + dtx, + framesPerPacket, + dut_encoder_frontend, + dut_decoder_frontend, + ) + + +@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +@pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) +@pytest.mark.parametrize("framesPerPacket", [8]) +def test_rtp_bitstream_ivas_nodtx( + test_info, + bitrate: int, + bandwidth: str, + format: str, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend, +): + run_rtp_bitstream_tests( + CODECS.IVAS, + bitrate, + bandwidth, + "OFF", + format, + False, + framesPerPacket, + dut_encoder_frontend, + dut_decoder_frontend, + ) + + +@pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) +@pytest.mark.parametrize("format", ["STEREO", "SBA"]) +@pytest.mark.parametrize("framesPerPacket", [1, 3]) +def test_rtp_bitstream_ivas_dtx( + test_info, + bitrate: int, + bandwidth: str, + format: str, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend, +): + run_rtp_bitstream_tests( + CODECS.IVAS, + bitrate, + bandwidth, + "OFF", + format, + True, + framesPerPacket, + dut_encoder_frontend, + dut_decoder_frontend, + ) + + +def generateRequests(startTs: int, endTs: int) -> dict: + requests = dict() + return requests + + +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, + ) + somePosition = lambda: POSITION( + x=random.randint(-32788, 32767) / 100.0, + y=random.randint(-32788, 32767) / 100.0, + z=random.randint(-32788, 32767) / 100.0, + ) + someDesc = lambda: AUDIO_DESCRIPTION( + isSpeech=bool(random.getrandbits(1)), + isMusic=bool(random.getrandbits(1)), + isAmbiance=bool(random.getrandbits(1)), + isEditable=bool(random.getrandbits(1)), + isBinaural=bool(random.getrandbits(1)), + ) + someDAS = lambda: DYNAMIC_AUDIO_SUPPRESSION( + preferSpeech=bool(random.getrandbits(1)), + preferMusic=bool(random.getrandbits(1)), + preferAmbiance=bool(random.getrandbits(1)), + level=random.randint(0, 15), + ) + 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, + ) + ) + someAuFocusLvl = lambda: AUDIO_FOCUS(level=AUDIO_FOCUS_LEVEL(random.randint(0, 15))) + someAuFocusList = [someAuFocusDirLvl, someAuFocusDir, someAuFocusLvl] + + for ts in range(startTs, endTs, 320): + pidata = dict() + pidata["SCENE_ORIENTATION"] = someOrientation() + pidata["DEVICE_ORIENTATION_COMPENSATED"] = someOrientation() + pidata["DEVICE_ORIENTATION_UNCOMPENSATED"] = someOrientation() + pidata["PLAYBACK_DEVICE_ORIENTATION"] = someOrientation() + pidata["HEAD_ORIENTATION"] = someOrientation() + pidata["AUDIO_FOCUS_REQUEST"] = random.choice(someAuFocusList)() + pidata["LISTENER_POSITION"] = somePosition() + pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() + pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] + pidata["DIEGETIC_TYPE"] = someDIG() + pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT( + aeid=random.randint(0, 127) + ) + data[str(ts)] = pidata + return data + + +def isEqualFrame(refFrame: bytes, dutFrame: bytes): + assert len(refFrame) == len(dutFrame), "Encoded frame size is different" + for refByte, dutByte in zip(refFrame, dutFrame): + assert ( + refByte == dutByte + ), "Encoded frames should be bitexact between ref and rtpdump" + + +def isEqualOrientation(ref: ORIENTATION, dut: ORIENTATION): + assert abs(ref.w - dut.w) < 0.0001, "Scene Orientation PI Data mismatch in w" + assert abs(ref.x - dut.x) < 0.0001, "Scene Orientation PI Data mismatch in x" + assert abs(ref.y - dut.y) < 0.0001, "Scene Orientation PI Data mismatch in y" + assert abs(ref.z - dut.z) < 0.0001, "Scene Orientation PI Data mismatch in z" + + +def isEqualPosition(ref: POSITION, dut: POSITION): + assert abs(ref.x - dut.x) < 0.3, "Position PI Data mismatch in x" + assert abs(ref.y - dut.y) < 0.3, "Position PI Data mismatch in y" + assert abs(ref.z - dut.z) < 0.3, "Position PI Data mismatch in z" + + +def isEqualAD(ref: AUDIO_DESCRIPTION, dut: AUDIO_DESCRIPTION): + assert ( + ref.isSpeech == dut.isSpeech + ), "Audio Description PI Data mismatch in isSpeech" + assert ref.isMusic == dut.isMusic, "Audio Description PI Data mismatch in isMusic" + assert ( + ref.isAmbiance == dut.isAmbiance + ), "Audio Description PI Data mismatch in isAmbiance" + assert ( + ref.isEditable == dut.isEditable + ), "Audio Description PI Data mismatch in isEditable" + assert ( + ref.isBinaural == dut.isBinaural + ), "Audio Description PI Data mismatch in isBinaural" + + +def isEqualDAS(ref: DYNAMIC_AUDIO_SUPPRESSION, dut: DYNAMIC_AUDIO_SUPPRESSION): + assert ( + ref.preferSpeech == dut.preferSpeech + ), "Dynamic Audio Suppression PI Data mismatch in preferSpeech" + assert ( + ref.preferMusic == dut.preferMusic + ), "Dynamic Audio Suppression PI Data mismatch in preferMusic" + assert ( + ref.preferAmbiance == dut.preferAmbiance + ), "Dynamic Audio Suppression PI Data mismatch in preferAmbiance" + assert ref.level == dut.level, "Dynamic Audio Suppression PI Data mismatch in level" + + +def isEqualDiegetic(ref: DIEGETIC_TYPE, dut: DIEGETIC_TYPE): + for r, d in zip(ref.isDigetic, dut.isDigetic): + assert r == d, f"Diegetic PI Data mismatch {r} != {d}" + + +def isEqualAcousticEnv(ref: ACOUSTIC_ENVIRONMENT, dut: ACOUSTIC_ENVIRONMENT): + assert ref.aeid == dut.aeid, "Acoustic Env PI Data mismatch in Acoustic Identifier" + assert len(ref.rt60) == len(dut.rt60), "Acoustic Env PI Data mismatch in len(rt60)" + assert len(ref.dsr) == len(dut.dsr), "Acoustic Env PI Data mismatch in len(dsr)" + assert len(ref.dim) == len(dut.dim), "Acoustic Env PI Data mismatch in len(dim)" + assert len(ref.abscoeff) == len( + dut.abscoeff + ), "Acoustic Env PI Data mismatch in len(abscoeff)" + for r, d in zip(ref.rt60, dut.rt60): + assert r == d, f"Acoustic Env PI Data mismatch in rt60 {r} != {d}" + + +def isEqualAudioFocus(ref: AUDIO_FOCUS, dut: AUDIO_FOCUS): + if ref.direction is not None or dut.direction is not None: + assert ref.direction is not None, "Audio Focus PI Data missing direction" + 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.0001 + ), "Audio Focus PI Data mismatch in direction w" + assert ( + abs(ref.direction["x"] - dut.direction.x) < 0.0001 + ), "Audio Focus PI Data mismatch in direction x" + assert ( + abs(ref.direction["y"] - dut.direction.y) < 0.0001 + ), "Audio Focus PI Data mismatch in direction y" + assert ( + abs(ref.direction["z"] - dut.direction.z) < 0.0001 + ), "Audio Focus PI Data mismatch in direction z" + assert ref.level == dut.level, "Audio Focus PI Data mismatch in level" + + +class CSVREADER: + def __init__(self, csvFile: Path): + self.rIdx = 0 + self.rows = [] + with open(csvFile, "r") as fd: + self.rows = [row for row in csv.reader(fd)] + self.count = len(self.rows) + + def next(self) -> list[float]: + row = self.rows[self.rIdx] + self.rIdx += 1 + if self.rIdx == self.count: + self.rIdx = 0 + return [float(x) for x in row] + + +class RTPVALIDATE: + + DTX_BITRATES = {CODECS.IVAS: 5200, CODECS.EVS: 2400, CODECS.AMRWB: 1750} + + def __init__(self, codec=CODECS.IVAS, bitrate=24400, framesPerPacket=1, dtx=False): + self.framesPerPacket = framesPerPacket + self.dtx = dtx + self.codec = codec + self.bitrate = bitrate + self.timestamp = 0 + self.seqnum = -1 + self.ssrc = -1 + self.numFrames = 0 + self.validatePiData = False + self.g192File = None + self.frameIdx = 0 + # PI DATA + self.readers: dict[str:CSVREADER] = dict() + + def setPiDataFiles(self, piFiles: tuple[Path]): + self.validatePiData = True + self.readers[PiTypeNames[0]] = CSVREADER(piFiles[0]) + self.readers[PiTypeNames[1]] = CSVREADER(piFiles[1]) + + def setRefG192Bitstream(self, g192File: Path): + self.refPackets = ReadG192Bitstream(g192File) + + def packet(self, packet: IvasPacket): + self.header(packet.hdr) + self.payload(packet.payload) + self.seqnum = (self.seqnum + 1) % 65536 + self.timestamp += 320 * self.numFrames + + def header(self, hdr: RTPHDR): + if self.timestamp == 0: + self.seqnum = hdr.sequenceNum + self.ssrc = hdr.ssrc + assert hdr.version == 2, "RTP Header Version must be 2" + assert self.ssrc == hdr.ssrc, "SSRC changed mid-stream in RTP Header" + assert self.timestamp == hdr.timestamp, "Timestamp mismatch in RTP Header" + assert self.seqnum == hdr.sequenceNum, "Sequence number mismatch in RTP Header" + + def payload(self, payload: IvasPayload): + self.numFrames = len(payload.frameList) + assert ( + self.numFrames >= 1 and self.numFrames <= self.framesPerPacket + ), f"Packet must have atleast 1 frame and atmost {self.framesPerPacket} frames" + + for frame in payload.frameList: + assert self.codec == frame.codec, "Codec mismatch in RTP Payload" + if self.dtx: + assert frame.bitrate in ( + self.bitrate, + RTPVALIDATE.DTX_BITRATES[self.codec], + 0, + ), "Bitrate mismatch in RTP Payload in DTX mode" + else: + assert frame.bitrate == self.bitrate, "Bitrate mismatch in RTP Payload" + + assert self.frameIdx < len(self.refPackets), "No. of frames mismatch" + isEqualFrame(frame.au, self.refPackets[self.frameIdx]) + self.frameIdx += 1 + + # Vallidate the PI Data + if self.validatePiData: + self.piData(payload.piDataList) + + def piData(self, piDataList: list[PIDATA]): + for piData in piDataList: + assert ( + piData.timestamp >= self.timestamp + and piData.timestamp < self.timestamp + (self.numFrames * 320) + ), "PI Data Time stamp is OOB" + assert ( + piData.type == PiTypeNames[0] or piData.type == PiTypeNames[1] + ), "PI Data is neither Scene nor Device Orientation" + assert type(piData.data) == ORIENTATION, "Orientation type data expected" + # validate the PI Data provided is the PI data in the packet + refData = self.readers[piData.type].next() + isEqualOrientation( + ORIENTATION(w=refData[0], x=refData[1], y=refData[2], z=refData[3]), + piData.data, + ) + + +@dataclass +class TVARGS: + TVROOT = Path(ROOT_DIR).joinpath("scripts/testv") + + def __init__(self): + self.tvDict = dict() + self.sceneFile = ( + Path(ROOT_DIR) + .joinpath("scripts/trajectories/azi_plus_2-ele_plus_2-every-25-rows.csv") + .absolute() + ) + self.deviceFile = ( + Path(ROOT_DIR).joinpath("scripts/trajectories/headrot-1.5s.csv").absolute() + ) + + def add(self, fmt: str, inputFile: str, args: list[str] = []): + inputFile = str(TVARGS.TVROOT.joinpath(inputFile).absolute()) + if fmt == "MASA": + args[2] = str(TVARGS.TVROOT.joinpath(args[2]).absolute()) + self.tvDict[fmt] = (inputFile, args) + + def input(self, fmt): + return self.tvDict[fmt][0] + + def args(self, fmt, addPI=False) -> list[str]: + args = [x for x in self.tvDict[fmt][1]] + if addPI and fmt != "MONO": + args += [ + "-scene_orientation", + str(self.sceneFile), + "-device_orientation", + str(self.deviceFile), + ] + return args + + def piFiles(self) -> tuple[Path]: + return (self.sceneFile, self.deviceFile) + + +def run_rtp_bitstream_tests( + codec: CODECS, + bitrate: int, + bandwidth: str, + caMode: str, + format: str, + dtx: bool, + framesPerPacket: int, + dut_encoder_frontend: EncoderFrontend, + dut_decoder_frontend: DecoderFrontend, +): + tvArgs = TVARGS() + tvArgs.add("MONO", "stv48n.wav") + + if dtx: # use bigger file for dtx stereo + tvArgs.add("STEREO", "stvST48n.wav", ["-stereo"]) + else: + tvArgs.add("STEREO", "stv2MASA2TC48c.wav", ["-stereo"]) + + tvArgs.add("MC", "stv51MC48c.wav", ["-mc", "5_1"]) + tvArgs.add("MASA", "stv2MASA2TC48c.wav", ["-masa", "2", "stv2MASA2TC48c.met"]) + tvArgs.add("SBA", "stvFOA48c.wav", ["-sba", "+1"]) + + if (bitrate > 24400 and bandwidth == "NB") or ( + format == "STEREO" and bitrate > 256000 + ): + pytest.skip() + + print( + "Test: dut_encoder_frontend={}, dtx={}, codec:={}, bitrate={}, bandwidth={}, caMode={}, format={},".format( + dut_encoder_frontend._path, dtx, codec, bitrate, bandwidth, caMode, format + ) + ) + + validate = RTPVALIDATE( + codec=codec, bitrate=bitrate, framesPerPacket=framesPerPacket, dtx=dtx + ) + + with TemporaryDirectory() as tmp_dir: + g192Out = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.g192") + .absolute() + ) + rtpdumpOut = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") + .absolute() + ) + rtpdumpIn = ( + Path(tmp_dir) + .joinpath(f"input-{codec}-{bitrate}-{caMode}-{format}-{dtx}.rtpdump") + .absolute() + ) + pcmOut = ( + Path(tmp_dir) + .joinpath(f"output-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") + .absolute() + ) + pcmOutG192 = ( + Path(tmp_dir) + .joinpath(f"output_g192-{codec}-{bitrate}-{caMode}-{format}-{dtx}.wav") + .absolute() + ) + piDataOutJson = ( + Path(tmp_dir) + .joinpath(f"piData-{codec}-{bitrate}-{caMode}-{format}-{dtx}.json") + .absolute() + ) + + # Run WITHOUT rtpdump first to generate reference bitstream + dut_encoder_frontend.run( + bitrate=bitrate, + input_sampling_rate=48, + input_path=tvArgs.input(format), + output_bitstream_path=g192Out, + sba_order=None, + dtx_mode=dtx, + max_band=bandwidth, + add_option_list=tvArgs.args(format), + ) + validate.setRefG192Bitstream(g192File=g192Out) + + packer = IvasRtp(numFramesPerPacket=framesPerPacket, codec=codec) + + if codec == CODECS.IVAS: + outMode = "STEREO" if format == "STEREO" else "BINAURAL" + generatedPIData = generatePiData(0, 16000) + else: + outMode = "" + generatedPIData = dict() + + packer.packG192File( + g192File=g192Out, + rtpDumpOut=rtpdumpIn, + piData=generatedPIData, + requestsData=generateRequests(0, 1600), + ) + + dut_decoder_frontend.run( + output_config=outMode, + output_sampling_rate=48, + input_bitstream_path=g192Out, + output_path=pcmOutG192, + add_option_list=[], + ) + + dut_decoder_frontend.run( + output_config=outMode, + output_sampling_rate=48, + input_bitstream_path=rtpdumpIn, + output_path=pcmOut, + add_option_list=["-VOIP_HF_ONLY=1", "-PiDataFile", str(piDataOutJson)], + ) + + decAudio, fs = readfile(pcmOut) + g192Audio, Fs = readfile(pcmOutG192) + decAudio /= 32768.0 # readfile reuturns 16 bit int + g192Audio /= 32768.0 # readfile reuturns 16 bit int + decAudio = decAudio[4 * 960 :] + assert abs(decAudio.shape[0] - g192Audio.shape[0]) <= ( + 4 * 960 + ), "Decoded PCM Audio is not same length as input" + minSamples = min(decAudio.shape[0], g192Audio.shape[0]) + rmsdB = 10.0 * np.log10( + np.finfo(float).eps + + np.sum(np.abs(g192Audio[:minSamples] - decAudio[:minSamples]) ** 2) + / minSamples + ) + + if dtx: + assert ( + rmsdB < -60.0 + ), "Bitdiff in the RTP unpacked and G192 streams for DTX stream" + else: + assert rmsdB < -96.0, "Bitdiff in the RTP unpacked and G192 streams" + + with open(piDataOutJson, "r") as fd: + decodedPiData = json.load(fd) + assert ( + decodedPiData.keys() == generatedPIData.keys() + ), f"Timestamp of PI data {generatedPIData.keys()} not found in Decoded PI Data {decodedPiData.keys()}" + for ts in generatedPIData.keys(): + for pitype in generatedPIData[ts]: + data = generatedPIData[ts][pitype] + decoded = decodedPiData[ts][pitype] + if type(generatedPIData[ts][pitype]) == ORIENTATION: + isEqualOrientation(ORIENTATION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == POSITION: + isEqualPosition(POSITION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == DYNAMIC_AUDIO_SUPPRESSION: + isEqualDAS(DYNAMIC_AUDIO_SUPPRESSION(**decoded), data) + elif type(generatedPIData[ts][pitype]) == DIEGETIC_TYPE: + isEqualDiegetic(DIEGETIC_TYPE(**decoded), data) + elif type(generatedPIData[ts][pitype]) == ACOUSTIC_ENVIRONMENT: + isEqualAcousticEnv(ACOUSTIC_ENVIRONMENT(**decoded), data) + elif type(generatedPIData[ts][pitype]) == AUDIO_FOCUS: + isEqualAudioFocus(AUDIO_FOCUS(**decoded), data) + elif type(generatedPIData[ts][pitype]) == list: + for r, d in zip( + generatedPIData[ts][pitype], decodedPiData[ts][pitype] + ): + isEqualAD(AUDIO_DESCRIPTION(**d), r) + else: + assert False, "Unsupported PI data found" + + # Generate RTPDUMP + addPI = False if format == "MONO" else True + if addPI: + # Add PI Data to Pack in RTP + validate.setPiDataFiles(tvArgs.piFiles()) + + extra_args = tvArgs.args(format, addPI) + extra_args += ["-rtpdump", str(framesPerPacket)] + dut_encoder_frontend.run( + bitrate=bitrate, + input_sampling_rate=48, + input_path=tvArgs.input(format), + output_bitstream_path=rtpdumpOut, + sba_order=None, + dtx_mode=dtx, + max_band=bandwidth, + add_option_list=extra_args, + ) + + unpacker = IvasRtp() + unpacker.unpackFile(rtpdumpOut) + for packet in unpacker.getPackets(): + validate.packet(packet) -- GitLab From 356e06023a5b662eb4cb6604632cde250b588185 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 9 Oct 2025 14:23:31 +0300 Subject: [PATCH 227/340] Add rtpdump test to pipeline --- .gitlab-ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b735a6df4..ef260a2b99 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -707,6 +707,28 @@ split-rendering-voip-be-to-binaural: junit: - report-junit.xml +# test rtpdump +rtpdump-test: + extends: + - .test-job-linux + - .rules-merge-request-to-main + needs: ["build-codec-linux-make"] + stage: test + script: + - make -j + - testcase_timeout=30 + - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout + artifacts: + name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" + expire_in: 1 week + when: always + paths: + - report-junit.xml + expose_as: "rtpdump pytest results" + reports: + junit: + - report-junit.xml + lc3-wrapper-unit-test: extends: - .test-job-linux -- GitLab From c37b61deb69d17de652c6a04dd5bd76a70c4284e Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Fri, 3 Oct 2025 13:09:28 +1000 Subject: [PATCH 228/340] Re-add the PI Application fixes and restart functionality Signed-off-by: Ripinder Singh --- CMakeLists.txt | 2 +- Workspace_msvc/lib_dec.vcxproj | 4 +- apps/decoder.c | 36 +++++++ lib_com/ivas_prot.h | 12 +++ lib_dec/ivas_binRenderer_internal.c | 4 + lib_dec/ivas_dirac_dec.c | 4 + lib_dec/ivas_init_dec.c | 4 + lib_dec/ivas_mc_param_dec.c | 4 + lib_dec/ivas_mc_paramupmix_dec.c | 4 + lib_dec/ivas_mct_dec.c | 4 + lib_dec/ivas_output_config.c | 16 ++++ lib_dec/lib_dec.c | 140 ++++++++++++++++++++++++++++ lib_dec/lib_dec.h | 6 ++ lib_rend/ivas_prot_rend.h | 8 ++ lib_rend/ivas_rotation.c | 102 ++++++++++++++++++++ lib_rend/ivas_stat_rend.h | 4 + lib_util/ivas_rtp_api.h | 1 - 17 files changed, 351 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe516c9ec8..bae6fa624e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,7 @@ file(GLOB libDecSrcs "lib_dec/*.c") file(GLOB libDecHeaders "lib_dec/*.h") add_library(lib_dec ${libDecSrcs} ${libDecHeaders}) target_link_libraries(lib_dec lib_com lib_rend lib_debug lib_isar) -target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar) +target_include_directories(lib_dec PUBLIC lib_dec lib_rend PRIVATE lib_enc lib_isar lib_util) file(GLOB libUtilSrcs "lib_util/*.c") file(GLOB libUtilHeaders "lib_util/*.h") diff --git a/Workspace_msvc/lib_dec.vcxproj b/Workspace_msvc/lib_dec.vcxproj index 3c7ea3e597..5f60decb08 100644 --- a/Workspace_msvc/lib_dec.vcxproj +++ b/Workspace_msvc/lib_dec.vcxproj @@ -68,7 +68,7 @@ Disabled - ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;%(AdditionalIncludeDirectories) + ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;..\lib_util;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;$(Macros);WIN32;%(PreprocessorDefinitions) EnableFastChecks @@ -109,7 +109,7 @@ Neither false false - ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;%(AdditionalIncludeDirectories) + ..\lib_com;..\lib_debug;..\lib_dec;..\lib_enc;..\lib_isar;..\lib_rend;..\lib_lc3plus;..\lib_util;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;$(Macros);WIN32;%(PreprocessorDefinitions) true diff --git a/apps/decoder.c b/apps/decoder.c index ed96f79196..2e65a53320 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -133,6 +133,7 @@ typedef struct bool tsmEnabled; IVAS_RENDER_FRAMESIZE renderFramesize; #ifdef IVAS_RTPDUMP + bool applyPiData; char *piOutputFilename; #endif #ifdef DEBUGGING @@ -976,6 +977,7 @@ static bool parseCmdlIVAS_dec( arg->enableExternalOrientation = false; arg->externalOrientationTrajFileName = NULL; #ifdef IVAS_RTPDUMP + arg->applyPiData = false; arg->piOutputFilename = NULL; #endif @@ -1077,6 +1079,11 @@ static bool parseCmdlIVAS_dec( arg->piOutputFilename = argv[i++]; } + else if ( strcmp( argv_to_upper, "-APPLYPIDATA" ) == 0 ) + { + arg->applyPiData = true; + i++; + } #endif #ifdef SUPPORT_JBM_TRACEFILE else if ( strcmp( argv_to_upper, "-TRACEFILE" ) == 0 ) @@ -1669,6 +1676,7 @@ static void usage_dec( void ) fprintf( stdout, " IVAS RTP Payload Format. The SDP parameter hf_only is required.\n" ); fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); fprintf( stdout, "-PiDataFile PF Log the timestampped PI data.\n" ); + fprintf( stdout, "-ApplyPiData Apply the PI data found in the rtp packet.\n" ); #else fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump\n" ); fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); @@ -3090,6 +3098,8 @@ static ivas_error decodeVoIP( int16_t i; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; + int32_t initialTsOffsetSystemAndRTP = 0; + uint32_t nProcPiData = 0; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; @@ -3232,6 +3242,7 @@ static ivas_error decodeVoIP( auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); + initialTsOffsetSystemAndRTP = rtpTimeStamp - systemTime_ms * 16; /* For time mapping */ #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); #endif @@ -3459,6 +3470,28 @@ static ivas_error decodeVoIP( /* decode and get samples */ while ( nSamplesRendered < nOutSamples ) { +#ifdef IVAS_RTPDUMP + if ( arg.applyPiData ) + { + /* Rudimentry Time Mapping to map system time to rtp timestamp */ + uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; + uint32_t numPiData = 0; + + while ( nProcPiData + numPiData < ivasRtp.nReadPiData && + ivasRtp.piData[nProcPiData + numPiData].timestamp <= piTs ) + { + numPiData++; + } + + if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[nProcPiData], numPiData ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + goto cleanup; + } + + nProcPiData += numPiData; + } +#endif #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( isSplitRend ) { @@ -3675,6 +3708,9 @@ static ivas_error decodeVoIP( } } } +#ifdef IVAS_RTPDUMP + IVAS_DEC_resetExternalOrientations( hIvasDec ); +#endif vec_pos_update = ( vec_pos_update + 1 ) % vec_pos_len; if ( vec_pos_update == 0 ) diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index eb75ab124d..4d664f9e5a 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -806,7 +806,19 @@ void ivas_apply_non_diegetic_panning( const int16_t output_frame /* i : output frame length per channel */ ); +#ifdef IVAS_RTPDUMP +void QuaternionProduct( + const IVAS_QUATERNION q1, + const IVAS_QUATERNION q2, + IVAS_QUATERNION *const r +); + +void QuaternionInverse( + const IVAS_QUATERNION q, + IVAS_QUATERNION *const r +); +#endif /*----------------------------------------------------------------------------------* * JBM prototypes *----------------------------------------------------------------------------------*/ diff --git a/lib_dec/ivas_binRenderer_internal.c b/lib_dec/ivas_binRenderer_internal.c index fecb412e6d..60808714ca 100644 --- a/lib_dec/ivas_binRenderer_internal.c +++ b/lib_dec/ivas_binRenderer_internal.c @@ -1026,7 +1026,11 @@ ivas_error ivas_binRenderer_open( ivas_dirac_dec_get_response( (int16_t) ls_azimuth_CICP19[k], (int16_t) ls_elevation_CICP19[k], hBinRenderer->hReverb->foa_enc[k], 1 ); } } +#ifdef IVAS_RTPDUMP + else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else else if ( st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_dirac_dec.c b/lib_dec/ivas_dirac_dec.c index b53d8cc200..31f47c4dfd 100644 --- a/lib_dec/ivas_dirac_dec.c +++ b/lib_dec/ivas_dirac_dec.c @@ -2082,7 +2082,11 @@ void ivas_dirac_dec_render_sf( { mvs2s( &hSpatParamRendCom->azimuth[md_idx][hDirAC->hConfig->enc_param_start_band], &azimuth[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); mvs2s( &hSpatParamRendCom->elevation[md_idx][hDirAC->hConfig->enc_param_start_band], &elevation[hDirAC->hConfig->enc_param_start_band], hSpatParamRendCom->num_freq_bands - hDirAC->hConfig->enc_param_start_band ); +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) +#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->hCombinedOrientationData->shd_rot_max_order == 0 ) +#endif { num_freq_bands = hDirAC->band_grouping[hDirAC->hConfig->enc_param_start_band]; rotateAziEle_DirAC( azimuth, elevation, num_freq_bands, hSpatParamRendCom->num_freq_bands, p_Rmat ); diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 31297ed45c..617a14b53f 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -2150,7 +2150,11 @@ ivas_error ivas_init_decoder( } else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV || st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM ) { +#ifdef IVAS_RTPDUMP + if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->ivas_format == MC_FORMAT && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( error = efap_init_data( &( st_ivas->hEFAPdata ), st_ivas->hIntSetup.ls_azimuth, st_ivas->hIntSetup.ls_elevation, st_ivas->hIntSetup.nchan_out_woLFE, EFAP_MODE_EFAP ) ) != IVAS_ERR_OK ) { diff --git a/lib_dec/ivas_mc_param_dec.c b/lib_dec/ivas_mc_param_dec.c index b0ce85ce51..fb1acc52ae 100644 --- a/lib_dec/ivas_mc_param_dec.c +++ b/lib_dec/ivas_mc_param_dec.c @@ -401,7 +401,11 @@ ivas_error ivas_param_mc_dec_open( ivas_param_mc_dec_compute_interpolator( 0, 0, DEFAULT_JBM_CLDFB_TIMESLOTS, hParamMC->h_output_synthesis_params.interpolator ); /* Head or external rotation */ +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( hParamMC->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mc_paramupmix_dec.c b/lib_dec/ivas_mc_paramupmix_dec.c index d6c5ceec40..3724372ab0 100644 --- a/lib_dec/ivas_mc_paramupmix_dec.c +++ b/lib_dec/ivas_mc_paramupmix_dec.c @@ -349,7 +349,11 @@ ivas_error ivas_mc_paramupmix_dec_open( } /* Head or external rotation */ +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { if ( ( hMCParamUpmix->hoa_encoder = (float *) malloc( st_ivas->hTransSetup.nchan_out_woLFE * MAX_INTERN_CHANNELS * sizeof( float ) ) ) == NULL ) { diff --git a/lib_dec/ivas_mct_dec.c b/lib_dec/ivas_mct_dec.c index abc6f074e0..6ffd1b4f7e 100644 --- a/lib_dec/ivas_mct_dec.c +++ b/lib_dec/ivas_mct_dec.c @@ -1142,7 +1142,11 @@ static ivas_error ivas_mc_dec_reconfig( { ivas_binRenderer_close( &st_ivas->hBinRenderer ); +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) ) +#else if ( ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) ) +#endif { efap_free_data( &st_ivas->hEFAPdata ); } diff --git a/lib_dec/ivas_output_config.c b/lib_dec/ivas_output_config.c index 17ce37c4d4..b4b241815d 100644 --- a/lib_dec/ivas_output_config.c +++ b/lib_dec/ivas_output_config.c @@ -71,7 +71,11 @@ void ivas_renderer_select( * Binaural rendering configurations *-----------------------------------------------------------------*/ +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { st_ivas->hCombinedOrientationData->shd_rot_max_order = -1; } @@ -142,7 +146,11 @@ void ivas_renderer_select( *internal_config = IVAS_AUDIO_CONFIG_7_1_4; } +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { nchan_internal = ivas_sba_get_nchan_metadata( st_ivas->sba_analysis_order, st_ivas->hDecoderConfig->ivas_total_brate ); @@ -185,7 +193,11 @@ void ivas_renderer_select( if ( output_config == IVAS_AUDIO_CONFIG_BINAURAL || output_config == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM || output_config == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) { +#ifdef IVAS_RTPDUMP + if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) && st_ivas->mc_mode == MC_MODE_MCT ) +#else if ( ( st_ivas->transport_config == IVAS_AUDIO_CONFIG_5_1 || st_ivas->transport_config == IVAS_AUDIO_CONFIG_7_1 ) && ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) && st_ivas->mc_mode == MC_MODE_MCT ) +#endif { *renderer_type = RENDERER_BINAURAL_OBJECTS_TD; } @@ -200,7 +212,11 @@ void ivas_renderer_select( *renderer_type = RENDERER_BINAURAL_FASTCONV; } +#ifdef IVAS_RTPDUMP + if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation || st_ivas->hCombinedOrientationData ) +#else if ( st_ivas->hDecoderConfig->Opt_Headrotation || st_ivas->hDecoderConfig->Opt_ExternalOrientation ) +#endif { /* force HOA3 domain for rotation*/ *internal_config = IVAS_AUDIO_CONFIG_HOA3; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 4f6c1f75ab..dc71db8e69 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5551,3 +5551,143 @@ ivas_error IVAS_DEC_is_split_rendering_coded_out( return IVAS_ERR_OK; } + +#ifdef IVAS_RTPDUMP +#ifdef DEBUGGING +#define DEBUG_PRINT fprintf +#else +#define DEBUG_PRINT( ... ) +#endif + +/*---------------------------------------------------------------------* + * IVAS_DEC_feedSinglePIorientation( ) + * + * Feed a single orientation PI data to external orientation handle. + *---------------------------------------------------------------------*/ + +static ivas_error IVAS_DEC_feedSinglePIorientation( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + bool isOrientationSaved, /* i : flag to indicate if an orientation for this PI type was previously saved */ + IVAS_QUATERNION *savedOrientation /* i : previously saved orientation for this PI type */ +) +{ + int16_t i; + ivas_error error = IVAS_ERR_OK; + IVAS_QUATERNION savedInvOrientation; + + if ( isOrientationSaved ) + { + if ( !hIvasDec->st_ivas->hExtOrientationData ) + { + if ( ( error = ivas_external_orientation_open( &( hIvasDec->st_ivas->hExtOrientationData ), hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + if ( !hIvasDec->st_ivas->hCombinedOrientationData ) + { + if ( ( error = ivas_combined_orientation_open( &( hIvasDec->st_ivas->hCombinedOrientationData ), hIvasDec->st_ivas->hDecoderConfig->output_Fs, hIvasDec->st_ivas->hDecoderConfig->render_framesize ) ) != IVAS_ERR_OK ) + { + return error; + } + } + + QuaternionInverse( *savedOrientation, &savedInvOrientation ); + + /* use the new PI orientation or the previously saved orientation in processing */ + for ( i = 0; i < hIvasDec->st_ivas->hExtOrientationData->num_subframes; i++ ) + { + QuaternionProduct( hIvasDec->st_ivas->hExtOrientationData->Quaternions[i], savedInvOrientation, + &hIvasDec->st_ivas->hExtOrientationData->Quaternions[i] ); + hIvasDec->st_ivas->hExtOrientationData->enableExternalOrientation[i] = true; + } + hIvasDec->updateOrientation = true; + } + return error; +} + +/*---------------------------------------------------------------------* + * IVAS_DEC_resetExternalOrientations( ) + * + * Reset external orientations + *---------------------------------------------------------------------*/ + +void IVAS_DEC_resetExternalOrientations( + IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ +) +{ + ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); +} + +#ifdef RTP_S4_251135_CR26253_0016_REV1 +/*---------------------------------------------------------------------* + * IVAS_DEC_setDiegeticInput( ) + * + * Set isDiegeticInput flag for combined orientation handle based on PI data. + *---------------------------------------------------------------------*/ + +static void IVAS_DEC_setDiegeticInputPI( + IVAS_DEC_HANDLE hIvasDec, /* i/o: IVAS decoder handle */ + const bool *diegeticPIValues /* i : diegetic values for the input stream */ +) +{ + if ( hIvasDec->st_ivas->hCombinedOrientationData != NULL ) + { + int8_t i; + for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) + { + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPI[i] = diegeticPIValues[i]; + } + hIvasDec->st_ivas->hCombinedOrientationData->isDiegeticInputPISet = true; + } +} +#endif + +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) +{ + ivas_error error = IVAS_ERR_OK; + while ( numPiData-- ) + { + uint32_t piDataType = piData->data.noPiData.piDataType; + switch ( piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + { + IVAS_QUATERNION *quat = &piData->data.scene.orientation; + DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + } + break; + + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + { + IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; + DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + } + break; + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_DIEGETIC_TYPE: + { + DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); + IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); + } + break; +#endif + + default: + { + /* NOT HANDLED PI DATA - DO NOTHING */ + } + break; + } + if ( error != IVAS_ERR_OK ) + { + return error; + } + } + return error; +} +#endif diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index a35dbd6ef4..f20984c5ee 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -557,6 +557,12 @@ void IVAS_DEC_PrintDisclaimer( void ); +#ifdef IVAS_RTPDUMP +#include "ivas_rtp_pi_data.h" +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); +void IVAS_DEC_resetExternalOrientations(IVAS_DEC_HANDLE hIvasDec); +#endif + /* clang-format on */ #endif diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index cca2f34dc6..868ffc4ada 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1301,6 +1301,12 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ); +#ifdef IVAS_RTPDUMP +void ivas_external_orientation_reset( + EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ +); + +#endif void ivas_external_orientation_close( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o: external orientation handle */ ); @@ -1348,6 +1354,7 @@ ivas_error ivas_render_config_init_from_rom( * Quaternion operations *----------------------------------------------------------------------------------*/ +#ifndef IVAS_RTPDUMP void QuaternionProduct( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, @@ -1359,6 +1366,7 @@ void QuaternionInverse( IVAS_QUATERNION *const r ); +#endif void QuaternionSlerp( const IVAS_QUATERNION q1, const IVAS_QUATERNION q2, diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index ad42bd515e..b441382a55 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -693,6 +693,7 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ) { +#ifndef IVAS_RTPDUMP int16_t i; IVAS_QUATERNION identity; @@ -700,12 +701,16 @@ ivas_error ivas_external_orientation_open( identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; +#endif /* Allocate handle */ if ( ( *hExtOrientationData = (EXTERNAL_ORIENTATION_HANDLE) malloc( sizeof( EXTERNAL_ORIENTATION_DATA ) ) ) == NULL ) { return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for external orientation memory\n" ) ); } ( *hExtOrientationData )->num_subframes = num_subframes; +#ifdef IVAS_RTPDUMP + ivas_external_orientation_reset( hExtOrientationData ); +#else /* Enable head rotation and disable external orientation as default */ for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) { @@ -715,10 +720,45 @@ ivas_error ivas_external_orientation_open( ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; ( *hExtOrientationData )->Quaternions[i] = identity; } +#endif return IVAS_ERR_OK; } +#ifdef IVAS_RTPDUMP +/*-----------------------------------------------------------------------* + * ivas_external_orientation_reset() + * + * Reset external orientation values + *-----------------------------------------------------------------------*/ + +void ivas_external_orientation_reset( + EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ +) +{ + if ( *hExtOrientationData != NULL ) + { + int16_t i; + IVAS_QUATERNION identity; + + identity.w = 1.0f; + identity.x = 0.0f; + identity.y = 0.0f; + identity.z = 0.0f; + /* Enable head rotation and disable external orientation as default */ + for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) + { + ( *hExtOrientationData )->enableHeadRotation[i] = 1; + ( *hExtOrientationData )->enableExternalOrientation[i] = 0; + ( *hExtOrientationData )->enableRotationInterpolation[i] = 0; + ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; + ( *hExtOrientationData )->Quaternions[i] = identity; + } + } +} + + +#endif /*-----------------------------------------------------------------------* * ivas_external_orientation_close() * @@ -822,6 +862,14 @@ ivas_error ivas_combined_orientation_open( ( *hCombinedOrientationData )->subframe_idx = 0; ( *hCombinedOrientationData )->subframe_size = (int16_t) ( fs / ( FRAMES_PER_SEC * MAX_PARAM_SPATIAL_SUBFRAMES ) ); ( *hCombinedOrientationData )->cur_subframe_samples_rendered = 0; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + + for ( i = 0; i < ( 1 + IVAS_MAX_NUM_OBJECTS ); i++ ) + { + ( *hCombinedOrientationData )->isDiegeticInputPI[i] = true; + } + ( *hCombinedOrientationData )->isDiegeticInputPISet = false; +#endif return IVAS_ERR_OK; } @@ -989,11 +1037,30 @@ ivas_error combine_external_and_head_orientations( } else if ( hExtOrientationData == NULL && headRotQuaternions != NULL ) { +#ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ + if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) + { + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = identity; + } + } + else + { + /* Head rotation only */ + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + } + } +#else /* Head rotation only */ for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) { hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; } +#endif } if ( hExtOrientationData != NULL ) @@ -1070,6 +1137,40 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternion_frozen_head = identity; hCombinedOrientationData->isHeadRotationFrozen = 0; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + /* Disable head rotation if diegetic PI data indicating non-diegetic audio is received */ + if ( hCombinedOrientationData->isDiegeticInputPISet && !hCombinedOrientationData->isDiegeticInputPI[0] && !hCombinedOrientationData->isDiegeticInputPI[1] && !hCombinedOrientationData->isDiegeticInputPI[2] && !hCombinedOrientationData->isDiegeticInputPI[3] && !hCombinedOrientationData->isDiegeticInputPI[4] ) + { + continue; + } + else + { + /* Use the most recent head rotation */ + if ( hExtOrientationData->enableHeadRotation[i] == 1 ) + { + if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) + { + QuaternionProduct( hCombinedOrientationData->Quaternions[i], headRotQuaternions[i], &hCombinedOrientationData->Quaternions[i] ); + } + else + { + hCombinedOrientationData->Quaternions[i] = headRotQuaternions[i]; + } + } + /* Use the freezed head rotation */ + else if ( hExtOrientationData->enableHeadRotation[i] == 2 ) + { + if ( hExtOrientationData->enableExternalOrientation[i] > 0 ) + { + QuaternionProduct( hCombinedOrientationData->Quaternions[i], hCombinedOrientationData->Quaternion_frozen_head, &hCombinedOrientationData->Quaternions[i] ); + } + else + { + hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; + } + } + } +#else /* Use the most recent head rotation */ if ( hExtOrientationData->enableHeadRotation[i] == 1 ) { @@ -1094,6 +1195,7 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->Quaternions[i] = hCombinedOrientationData->Quaternion_frozen_head; } } +#endif /* Reset the combined orientations to identity */ if ( hExtOrientationData->enableHeadRotation[i] == 0 && hExtOrientationData->enableExternalOrientation[i] == 0 ) diff --git a/lib_rend/ivas_stat_rend.h b/lib_rend/ivas_stat_rend.h index c575a9a60a..4e179fc320 100644 --- a/lib_rend/ivas_stat_rend.h +++ b/lib_rend/ivas_stat_rend.h @@ -710,6 +710,10 @@ typedef struct ivas_combined_orientation_struct int16_t cur_subframe_samples_rendered; int16_t subframe_idx_start; int16_t cur_subframe_samples_rendered_start; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool isDiegeticInputPI[1 + IVAS_MAX_NUM_OBJECTS]; + bool isDiegeticInputPISet; +#endif } COMBINED_ORIENTATION_DATA, *COMBINED_ORIENTATION_HANDLE; /*----------------------------------------------------------------------------------* diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 06c56cd70f..d2b2f5fbc8 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -33,7 +33,6 @@ #ifndef IVAS_RTP_API_H #define IVAS_RTP_API_H -#include #include #include "common_api_types.h" -- GitLab From db4b253e38b46f47a19e459014e19e9ee60b2a73 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 13 Oct 2025 10:34:53 +0200 Subject: [PATCH 229/340] increase coverage test timeout --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b735a6df4..ef4908c45b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2195,7 +2195,7 @@ sanitizer-test-osba-planar-hoa3-ism4: rules: - if: $COVERAGE_TEST - if: $MANUAL_PIPELINE_TYPE == "coverage" - timeout: 3 hours + timeout: 6 hours before_script: - !reference [.job-linux, before_script] - set -e -- GitLab From 1468278260411740c282d7a3090fbaa3c69e51d8 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 13 Oct 2025 10:46:51 +0200 Subject: [PATCH 230/340] add command line logging for renderer tests --- tests/renderer/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/renderer/utils.py b/tests/renderer/utils.py index 657b1aa616..2a0d50568c 100644 --- a/tests/renderer/utils.py +++ b/tests/renderer/utils.py @@ -474,6 +474,7 @@ def run_renderer( record_property(k, v) if output_differs_parts[0]: + logging.error(f"Command line was: {' '.join(cmd)}") pytest.fail(f"Output differs: ({reason_parts[0]})") # compare metadata files in case of MASA prerendering -- GitLab From 65465054821b8e176b060bcf7d5e2e560b03e663 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 13 Oct 2025 15:15:28 +0300 Subject: [PATCH 231/340] Create random quaternion trajectories for external orientations --- ci/create_trajectories.py | 17 +++++++++++++++++ ci/run_scheduled_sanitizer_test.py | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ci/create_trajectories.py b/ci/create_trajectories.py index 6b957df092..51f61cd64d 100644 --- a/ci/create_trajectories.py +++ b/ci/create_trajectories.py @@ -1,4 +1,5 @@ import numpy as np +from scipy.spatial.transform import Rotation FRAMES_PER_SEC = 50 @@ -23,3 +24,19 @@ def constant_trajectory(duration_sec, yaw=0, pitch=0, roll=0): trj[:, 3] = roll return trj + +def random_trajectory_quat(duration_sec): + n_frames = int(FRAMES_PER_SEC * duration_sec) + trj = Rotation.random(n_frames).as_quat() + return trj + + +def constant_trajectory_quat(duration_sec, w=1, x=0, y=0, z=0): + n_frames = int(FRAMES_PER_SEC * duration_sec) + trj = np.empty((n_frames, 4)) + trj[:, 0] = w + trj[:, 1] = x + trj[:, 2] = y + trj[:, 3] = z + return trj + diff --git a/ci/run_scheduled_sanitizer_test.py b/ci/run_scheduled_sanitizer_test.py index c2d3f30d11..fc6c18ddbe 100755 --- a/ci/run_scheduled_sanitizer_test.py +++ b/ci/run_scheduled_sanitizer_test.py @@ -43,7 +43,7 @@ from collect_artifacts import ( find_failed_files_for_sanitizer_test, ) from combine_genpatt_and_jbm_profile import combine_error_profiles -from create_trajectories import random_trajectory +from create_trajectories import random_trajectory, random_trajectory_quat SCRIPT_DIR = pathlib.Path("./scripts").resolve() @@ -156,10 +156,10 @@ def run_check(in_format: str, out_formats: list, tests: list, run_fec: bool = Tr # create random trajectory files if "BINAURAL" in oc_str: - trajectory_files = [HEAD_TRAJ_FILE, EXOF_TRAJ_FILE] - for tf in trajectory_files: - traj = random_trajectory(int(DURATION)) - np.savetxt(tf, traj, fmt="%.2f", delimiter=",") + traj = random_trajectory(int(DURATION)) + np.savetxt(HEAD_TRAJ_FILE, traj, fmt="%.2f", delimiter=",") + traj_exof = random_trajectory_quat(int(DURATION)) + np.savetxt(EXOF_TRAJ_FILE, traj_exof, fmt="%.2f", delimiter=",") ### always run encoder and decoder with no frameloss cmd_no_fec = [ -- GitLab From f3902b293200b76842bea1216e46c07e02b3fbd3 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 13 Oct 2025 17:12:54 +0300 Subject: [PATCH 232/340] Add html report, remove xfail for rtpdump tests --- .gitlab-ci.yml | 3 ++- tests/codec_be_on_mr_nonselection/test_param_file.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef260a2b99..4890fefa32 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -717,13 +717,14 @@ rtpdump-test: script: - make -j - testcase_timeout=30 - - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout + - python3 -m pytest -q -n auto -rA --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week when: always paths: - report-junit.xml + - report.html expose_as: "rtpdump pytest results" reports: junit: diff --git a/tests/codec_be_on_mr_nonselection/test_param_file.py b/tests/codec_be_on_mr_nonselection/test_param_file.py index 0e0e082c63..d2acacd39e 100644 --- a/tests/codec_be_on_mr_nonselection/test_param_file.py +++ b/tests/codec_be_on_mr_nonselection/test_param_file.py @@ -228,9 +228,6 @@ def test_param_file_tests( ): enc_opts, dec_opts, sim_opts, eid_opts = param_file_test_dict[test_tag] - if "rtpdump" in test_tag: - pytest.xfail("Skip RTP tests for now") - run_test( test_info, props_to_record, -- GitLab From 714ca36336956310575766db920eb5cfd69e00ae Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 13 Oct 2025 17:38:37 +0200 Subject: [PATCH 233/340] add reproducibility for random parts of sanitizer tests - seed the numpy rng with CI_JOB_ID - add command line arg for injecting a custom seed --- ci/create_trajectories.py | 6 +-- ci/run_scheduled_sanitizer_test.py | 70 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/ci/create_trajectories.py b/ci/create_trajectories.py index 51f61cd64d..522a26461d 100644 --- a/ci/create_trajectories.py +++ b/ci/create_trajectories.py @@ -3,12 +3,13 @@ from scipy.spatial.transform import Rotation FRAMES_PER_SEC = 50 +MAGIC_VAL_TO_SIGNAL_EULER_ANGLES = -3 def random_trajectory(duration_sec): n_frames = int(FRAMES_PER_SEC * duration_sec) trj = np.random.random((n_frames, 4)) - trj[:, 0] = -3 + trj[:, 0] = MAGIC_VAL_TO_SIGNAL_EULER_ANGLES trj[:, 1] *= 180 trj[:, 2] *= 90 trj[:, 3] *= 180 @@ -18,7 +19,7 @@ def random_trajectory(duration_sec): def constant_trajectory(duration_sec, yaw=0, pitch=0, roll=0): n_frames = int(FRAMES_PER_SEC * duration_sec) trj = np.empty((n_frames, 4)) - trj[:, 0] = -3 + trj[:, 0] = MAGIC_VAL_TO_SIGNAL_EULER_ANGLES trj[:, 1] = yaw trj[:, 2] = pitch trj[:, 3] = roll @@ -39,4 +40,3 @@ def constant_trajectory_quat(duration_sec, w=1, x=0, y=0, z=0): trj[:, 2] = y trj[:, 3] = z return trj - diff --git a/ci/run_scheduled_sanitizer_test.py b/ci/run_scheduled_sanitizer_test.py index fc6c18ddbe..4995c3b953 100755 --- a/ci/run_scheduled_sanitizer_test.py +++ b/ci/run_scheduled_sanitizer_test.py @@ -1,38 +1,39 @@ #!/usr/bin/env python3 """ - (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository. All Rights Reserved. - - This software is protected by copyright law and by international treaties. - The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, - Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., - Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, - Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other - contributors to this repository retain full ownership rights in their respective contributions in - the software. This notice grants no license of any kind, including but not limited to patent - license, nor is any license granted by implication, estoppel or otherwise. - - Contributors are required to enter into the IVAS codec Public Collaboration agreement before making - contributions. - - This software is provided "AS IS", without any express or implied warranties. The software is in the - development stage. It is intended exclusively for experts who have experience with such software and - solely for the purpose of inspection. All implied warranties of non-infringement, merchantability - and fitness for a particular purpose are hereby disclaimed and excluded. - - Any dispute, controversy or claim arising under or in relation to providing this software shall be - submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in - accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and - the United Nations Convention on Contracts on the International Sales of Goods. +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. """ import argparse import pathlib import subprocess import sys +import os import numpy as np import json @@ -75,12 +76,17 @@ ARGS_FOR_OC = { "BINAURAL_ROOM_REVERB": BINAURAL_OUT_ARGS, } +SEED_FOR_RANDOM = int(os.environ.get("CI_JOB_ID", 0)) + def main(args): in_format = args.in_format out_formats = args.out_formats tests = args.tests run_fec = not args.skip_fec + seed = SEED_FOR_RANDOM if args.inject_seed is None else args.inject_seed + + np.random.seed(seed) assert all([t in SUPPORTED_TESTS for t in tests]) @@ -92,7 +98,6 @@ def main(args): def get_modes(in_format: str) -> list: - in_format_for_script = in_format if in_format in MC_MODES: in_format_for_script = "MC" @@ -134,7 +139,6 @@ def assemble_oc_dict(out_formats: list): def get_md_file_command(in_format: str) -> list: - cmd = list() if "ISM" in in_format: cmd.append("--ism_metadata_files") @@ -145,8 +149,7 @@ def get_md_file_command(in_format: str) -> list: return cmd -def run_check(in_format: str, out_formats: list, tests: list, run_fec: bool = True): - +def run_check(in_format: str, out_formats: list, tests: list, run_fec: bool): modes = get_modes(in_format) if len(modes) == 0: return 0 @@ -187,7 +190,7 @@ def run_check(in_format: str, out_formats: list, tests: list, run_fec: bool = Tr # resue decoder part of StereDmxEVS mode (it is basically a duplicate of "normal" mono run) if in_format == "StereoDmxEVS": panning = np.random.randint(-90, 91) - cmd_no_fec += [f'-D=-non_diegetic_pan {panning}'] + cmd_no_fec += [f"-D=-non_diegetic_pan {panning}"] print( "======== Script command line WITHOUT plc: ========\n{}".format( @@ -277,5 +280,8 @@ if __name__ == "__main__": parser.add_argument("out_formats", type=str, nargs="+") parser.add_argument("--tests", type=str, nargs="+", default=["CLANG1", "CLANG2"]) parser.add_argument("--skip_fec", action="store_true") + parser.add_argument( + "--inject_seed", type=int, help="Value to use for seeding random generators" + ) sys.exit(main(parser.parse_args())) -- GitLab From 67fd3479bda51a01fe6567f1c1ff353cc2a6edcf Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 13 Oct 2025 17:42:38 +0200 Subject: [PATCH 234/340] add explicit default --- ci/run_scheduled_sanitizer_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ci/run_scheduled_sanitizer_test.py b/ci/run_scheduled_sanitizer_test.py index 4995c3b953..071270610f 100755 --- a/ci/run_scheduled_sanitizer_test.py +++ b/ci/run_scheduled_sanitizer_test.py @@ -281,7 +281,10 @@ if __name__ == "__main__": parser.add_argument("--tests", type=str, nargs="+", default=["CLANG1", "CLANG2"]) parser.add_argument("--skip_fec", action="store_true") parser.add_argument( - "--inject_seed", type=int, help="Value to use for seeding random generators" + "--inject_seed", + type=int, + default=None, + help="Value to use for seeding random generators", ) sys.exit(main(parser.parse_args())) -- GitLab From 3f957104dfd1e3a14c38441a590e996811360ceb Mon Sep 17 00:00:00 2001 From: vaclav Date: Tue, 14 Oct 2025 11:49:37 +0200 Subject: [PATCH 235/340] accept BE switches --- apps/renderer.c | 8 ---- lib_com/ivas_cnst.h | 2 - lib_com/options.h | 8 ---- lib_dec/igf_dec.c | 3 +- lib_dec/ivas_init_dec.c | 2 - lib_dec/ivas_mdct_core_dec.c | 2 - lib_dec/ivas_tcx_core_dec.c | 89 ++++-------------------------------- lib_dec/lib_dec.c | 11 +---- lib_enc/ivas_tcx_core_enc.c | 2 - lib_rend/ivas_rotation.c | 10 ---- lib_util/hrtf_file_reader.c | 4 -- 11 files changed, 10 insertions(+), 131 deletions(-) diff --git a/apps/renderer.c b/apps/renderer.c index bf2587680a..939371547e 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -1229,15 +1229,11 @@ int main( masaIds[i] = 0u; } -#ifdef FIX_1377_HANDLE_ERROR_CODE if ( ( error = IVAS_REND_SetObjectIDs( hIvasRend ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nIVAS_REND_SetObjectIDs: %s\n", ivas_error_to_string( error ) ); goto cleanup; } -#else - IVAS_REND_SetObjectIDs( hIvasRend ); -#endif for ( i = 0; i < args.inConfig.numMultiChannelBuses; ++i ) { @@ -2611,14 +2607,10 @@ static CmdlnArgs defaultArgs( args.outConfig.audioConfig = IVAS_AUDIO_CONFIG_INVALID; args.outConfig.outSetupCustom.num_spk = 0; args.outConfig.outSetupCustom.num_lfe = 0; -#ifdef FIX_HRTF_LEFTOVERS for ( i = 0; i < RENDERER_MAX_SBA_INPUTS; ++i ) { args.inConfig.ambisonicsBuses[i].audioConfig = IVAS_AUDIO_CONFIG_INVALID; } -#else - args.inConfig.ambisonicsBuses->audioConfig = IVAS_AUDIO_CONFIG_INVALID; -#endif for ( i = 0; i < RENDERER_MAX_ISM_INPUTS + RENDERER_MAX_MASA_INPUTS; ++i ) { diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index ff0e1972fe..6f679f0fd8 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -229,9 +229,7 @@ typedef enum /* format signaling in SID frames */ #define SID_FORMAT_NBITS 3 /* Bit 0 | Bit 1 | Bit 2 */ /*-------|-------|------ */ -#ifdef FIX_1384_MSAN_ivas_spar_dec_open #define SID_FORMAT_NONE (-0x1) /* n/a| n/a| n/a*/ -#endif #define SID_DFT_STEREO 0x0 /* 0| 0| 0 */ #define SID_MDCT_STEREO 0x1 /* 1| 0| 0 */ #define SID_ISM 0x2 /* 0| 1| 0 */ diff --git a/lib_com/options.h b/lib_com/options.h index 45d82a98d3..813d327a48 100644 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -164,14 +164,6 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ -#define FIX_1377_HANDLE_ERROR_CODE /* Eri: Add missing error code handling from IVAS_REND_SetObjectIDs */ -#define FIX_HRTF_LEFTOVERS /* VA: HRTF updates - bring float main in line with BASOP main */ -#define FIX_1384_MSAN_ivas_spar_dec_open /* VA: issue 1386: fix use-of-uninitialized value in ivas_spar_dec_open() */ -#define FIX_1384_MSAN_stereo_tcx_core_enc /* VA: issue 1384: fix use-of-uninitialized value in stereo_tcx_core_enc() */ -#define FIX_1388_MSAN_ivas_init_decoder /* VA: issue 1388: fix use-of-uninitialized value in ivas_init_decoder() */ -#define FIX_1383_HEAD_TRACK_SANITIZER /* Nok: issue 1383: Fix head tracking struc values reading in renderer */ -#define FIX_1385_INIT_IGF_STOP_FREQ /* FhG: Initialize infoIGFStopFreq in init_igf_dec() */ -#define FIX_1387_INIT_PRM_SQQ /* FhG: initialize pointer prm_sqQ, which might be uninitialized in case of bfi == 1 */ /* #################### End BE switches ################################## */ diff --git a/lib_dec/igf_dec.c b/lib_dec/igf_dec.c index ba7c4993fd..8f91c43fb2 100755 --- a/lib_dec/igf_dec.c +++ b/lib_dec/igf_dec.c @@ -1573,6 +1573,7 @@ void IGFDecRestoreTCX10SubFrameData( return; } + /*-----------------------------------------------------------------------* * init_igf_dec() * @@ -1592,9 +1593,7 @@ void init_igf_dec( hIGFDec->flag_sparse = &hIGFDec->flag_sparseBuf[0]; hIGFDec->infoTCXNoise = &hIGFDec->infoTCXNoiseBuf[0]; hIGFDec->virtualSpec = &hIGFDec->virtualSpecBuf[0]; -#ifdef FIX_1385_INIT_IGF_STOP_FREQ hIGFDec->infoIGFStopFreq = 0; -#endif return; } diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 31297ed45c..f83de6eb2e 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -1154,9 +1154,7 @@ ivas_error ivas_init_decoder_front( st_ivas->ism_mode = ISM_MODE_NONE; st_ivas->mc_mode = MC_MODE_NONE; -#ifdef FIX_1384_MSAN_ivas_spar_dec_open st_ivas->sid_format = SID_FORMAT_NONE; -#endif st_ivas->sba_dirac_stereo_flag = 0; /* HRTF binauralization latency in ns */ diff --git a/lib_dec/ivas_mdct_core_dec.c b/lib_dec/ivas_mdct_core_dec.c index eef4b37bf8..9cf401aba4 100644 --- a/lib_dec/ivas_mdct_core_dec.c +++ b/lib_dec/ivas_mdct_core_dec.c @@ -532,9 +532,7 @@ void ivas_mdct_core_invQ( set_s( total_nbbits, 0, CPE_CHANNELS ); set_s( bitsRead, 0, CPE_CHANNELS ); tmp_concealment_method = 0; -#ifdef FIX_1387_INIT_PRM_SQQ prm_sqQ = NULL; /* set prm_sqQ to NULL - in case of bfi == 1 it's not set or needed, but it triggers sanitizers */ -#endif for ( ch = 0; ch < CPE_CHANNELS; ch++ ) { diff --git a/lib_dec/ivas_tcx_core_dec.c b/lib_dec/ivas_tcx_core_dec.c index b7e5c11f95..634ac0f6d7 100644 --- a/lib_dec/ivas_tcx_core_dec.c +++ b/lib_dec/ivas_tcx_core_dec.c @@ -288,18 +288,7 @@ void stereo_tcx_core_dec( { int16_t tcx_lpc_cdk; -#ifdef FIX_1384_MSAN_stereo_tcx_core_enc tcx_lpc_cdk = tcxlpc_get_cdk( st->coder_type ); -#else - if ( bfi && st->use_partial_copy && st->rf_frame_type == RF_TCXFD ) - { - tcx_lpc_cdk = tcxlpc_get_cdk( GENERIC ); - } - else - { - tcx_lpc_cdk = tcxlpc_get_cdk( st->coder_type ); - } -#endif mvr2r( st->lsf_old, &lsf[0], M ); mvr2r( st->lsp_old, &lsp[0], M ); @@ -398,13 +387,6 @@ void stereo_tcx_core_dec( lsp2a_stab( st->lsp_old, st->old_Aq_12_8, M ); } -#ifndef FIX_1384_MSAN_stereo_tcx_core_enc - if ( st->enablePlcWaveadjust && bfi ) - { - st->hPlcInfo->nbLostCmpt++; - } -#endif - /*--------------------------------------------------------------------------------* * TD-TCX concealment *--------------------------------------------------------------------------------*/ @@ -538,26 +520,6 @@ void stereo_tcx_core_dec( if ( st->core == TCX_10_CORE || st->core == TCX_20_CORE ) { -#ifndef FIX_1384_MSAN_stereo_tcx_core_enc - if ( st->enablePlcWaveadjust || /* bfi */ - ( st->last_total_brate >= HQ_48k && /* recovery */ - st->last_codec_mode == MODE2 ) ) - { - /* waveform adjustment */ - concealment_signal_tuning( st, bfi, synthFB, st->last_core_bfi ); - - if ( ( bfi || st->prev_bfi ) && st->hPlcInfo->Pitch && st->hPlcInfo->concealment_method == TCX_NONTONAL ) - { - lerp( synthFB, synth, st->L_frame, hTcxDec->L_frameTCX ); - - if ( !bfi && st->prev_bfi ) - { - st->hPlcInfo->Pitch = 0; - } - } - } -#endif - if ( !bfi && st->hTonalMDCTConc != NULL ) { TonalMDCTConceal_SaveTimeSignal( st->hTonalMDCTConc, synthFB, hTcxDec->L_frameTCX ); @@ -681,13 +643,6 @@ void stereo_tcx_core_dec( if ( !bfi ) { -#ifndef FIX_1384_MSAN_stereo_tcx_core_enc - if ( st->enablePlcWaveadjust ) - { - st->hPlcInfo->nbLostCmpt = 0; - } -#endif - if ( param[1 + NOISE_FILL_RANGES] != 0 ) { set_f( pitch_buf, hTcxLtpDec->tcxltp_pitch_int + (float) hTcxLtpDec->tcxltp_pitch_fr / (float) st->pit_res_max, NB_SUBFR16k ); @@ -844,11 +799,7 @@ static void dec_prm_tcx( getTCXMode( st, st, 0 /* <- MCT_flag */ ); /* last_core for error concealment */ -#ifdef FIX_1384_MSAN_stereo_tcx_core_enc if ( st->element_mode != IVAS_CPE_MDCT ) -#else - if ( !st->use_partial_copy && st->element_mode != IVAS_CPE_MDCT ) -#endif { st->last_core_from_bs = get_next_indice( st, 1 ); /* Store decoder memory of last_core */ if ( st->last_core == ACELP_CORE && st->last_core_from_bs != ACELP_CORE ) @@ -874,23 +825,16 @@ static void dec_prm_tcx( } } -#ifndef FIX_1384_MSAN_stereo_tcx_core_enc - if ( !st->use_partial_copy ) + if ( st->element_mode != IVAS_CPE_MDCT ) { -#endif - if ( st->element_mode != IVAS_CPE_MDCT ) - { - getTCXWindowing( st->core, st->last_core, st->element_mode, st->hTcxCfg, st ); - } - st->flagGuidedAcelp = 0; + getTCXWindowing( st->core, st->last_core, st->element_mode, st->hTcxCfg, st ); + } + st->flagGuidedAcelp = 0; - if ( st->dec_glr ) - { - st->dec_glr_idx = -1; - } -#ifndef FIX_1384_MSAN_stereo_tcx_core_enc + if ( st->dec_glr ) + { + st->dec_glr_idx = -1; } -#endif #ifdef DEBUG_MODE_TCX fprintf( pF, "\t TCX Header: %d bits: %d %d %d %d\n", st->next_bit_pos - start_bit_pos, st->tcxonly, st->core, st->tcxonly ? st->clas_dec : st->hTcxCfg->coder_type, st->hTcxCfg->tcx_curr_overlap_mode ); nbits_tcx = st->next_bit_pos; @@ -912,7 +856,6 @@ static void dec_prm_tcx( * TCX20/10 parameters *--------------------------------------------------------------------------------*/ -#ifdef FIX_1384_MSAN_stereo_tcx_core_enc getTCXparam( st, st, hm_cfg, param, bits_common, start_bit_pos, NULL, NULL, NULL, -1 ); if ( *total_nbbits - bitsRead[0] < ( st->next_bit_pos - start_bit_pos ) ) @@ -922,27 +865,11 @@ static void dec_prm_tcx( } bitsRead[0] = st->next_bit_pos - start_bit_pos; -#else - if ( st->use_partial_copy == 0 ) - { - getTCXparam( st, st, hm_cfg, param, bits_common, start_bit_pos, NULL, NULL, NULL, -1 ); - } - - if ( !st->use_partial_copy ) - { - if ( *total_nbbits - bitsRead[0] < ( st->next_bit_pos - start_bit_pos ) ) - { - st->BER_detect = 1; - st->next_bit_pos = start_bit_pos + *total_nbbits - bitsRead[0]; - } - - bitsRead[0] = st->next_bit_pos - start_bit_pos; - } -#endif return; } + /*-----------------------------------------------------------------* * Function stereo_tcx_dec_mode_switch_reconf() * diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index e0fc232dc2..77c2c852a7 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -857,23 +857,19 @@ ivas_error IVAS_DEC_FeedFrame_Serial( { ivas_error error; -#ifdef FIX_1388_MSAN_ivas_init_decoder if ( hIvasDec == NULL || hIvasDec->st_ivas == NULL ) { return IVAS_ERR_UNEXPECTED_NULL_POINTER; } -#endif if ( !hIvasDec->isInitialized ) { /* Once first frame is fed, finish initialization in EVS Mono. * In IVAS mode, initialization is done in ivas_dec(). */ if ( hIvasDec->mode == IVAS_DEC_MODE_EVS ) { -#ifdef FIX_1388_MSAN_ivas_init_decoder hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = ACELP_8k00; -#endif if ( ( error = ivas_init_decoder( hIvasDec->st_ivas ) ) != IVAS_ERR_OK ) { return error; @@ -886,12 +882,7 @@ ivas_error IVAS_DEC_FeedFrame_Serial( st->prev_use_partial_copy = 0; hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = hIvasDec->hVoIP->hCurrentDataUnit->dataSize * FRAMES_PER_SEC; } -#ifndef FIX_1388_MSAN_ivas_init_decoder - else - { - hIvasDec->st_ivas->hDecoderConfig->ivas_total_brate = ACELP_8k00; - } -#endif + hIvasDec->isInitialized = true; } } diff --git a/lib_enc/ivas_tcx_core_enc.c b/lib_enc/ivas_tcx_core_enc.c index a2f96032d0..60a79ea771 100644 --- a/lib_enc/ivas_tcx_core_enc.c +++ b/lib_enc/ivas_tcx_core_enc.c @@ -336,9 +336,7 @@ void stereo_tcx_core_enc( } } -#ifdef FIX_1384_MSAN_stereo_tcx_core_enc st->acelp_cfg.midLpc = 0; -#endif last_core_orig = st->last_core; for ( n = 0; n < n_subframes; n++ ) { diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index ad42bd515e..b9286d3d96 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -898,20 +898,10 @@ ivas_error combine_external_and_head_orientations_rend( int16_t i; sr_pose_pred_axis = DEFAULT_AXIS; -#ifdef FIX_1383_HEAD_TRACK_SANITIZER if ( hHeadTrackData->headRotEnabled ) { headRotQuaternions = hHeadTrackData->headPositions; listenerPos = hHeadTrackData->Pos; -#else - if ( hHeadTrackData != NULL ) - { - if ( hHeadTrackData->headRotEnabled ) - { - headRotQuaternions = hHeadTrackData->headPositions; - listenerPos = hHeadTrackData->Pos; - } -#endif sr_pose_pred_axis = hHeadTrackData->sr_pose_pred_axis; } else if ( hExtOrientationData != NULL ) diff --git a/lib_util/hrtf_file_reader.c b/lib_util/hrtf_file_reader.c index 0affb9401c..164120d79d 100644 --- a/lib_util/hrtf_file_reader.c +++ b/lib_util/hrtf_file_reader.c @@ -816,11 +816,7 @@ static ivas_error load_reverb_from_binary( hHrtfStatistics->average_energy_r_dyn = (float *) malloc( lr_iac_len * sizeof( float ) ); hHrtfStatistics->inter_aural_coherence_dyn = (float *) malloc( lr_iac_len * sizeof( float ) ); -#ifdef FIX_HRTF_LEFTOVERS if ( hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->average_energy_r_dyn == NULL || hHrtfStatistics->inter_aural_coherence_dyn == NULL ) -#else - if ( hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->average_energy_l_dyn == NULL || hHrtfStatistics->inter_aural_coherence_dyn == NULL ) -#endif { return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Could not allocate memory for hrtf data" ); } -- GitLab From b7d8b783654f75822c77fe4bb2709c4ff15f1f71 Mon Sep 17 00:00:00 2001 From: vaclav Date: Tue, 14 Oct 2025 12:31:15 +0200 Subject: [PATCH 236/340] clang-format --- lib_rend/lib_rend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index bd689e136a..bba3fbb187 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7962,7 +7962,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From e6681da504e878f4bdcf7f834210b4cd74f1a5cb Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 14 Oct 2025 16:04:17 +0300 Subject: [PATCH 237/340] Add tb=no, reduce rtpdump test combinations --- .gitlab-ci.yml | 2 +- tests/rtp/test_rtp.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4890fefa32..986ec444e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -717,7 +717,7 @@ rtpdump-test: script: - make -j - testcase_timeout=30 - - python3 -m pytest -q -n auto -rA --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout + - python3 -m pytest -q --tb=no -n auto -rA --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" expire_in: 1 week diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 69b85dc9af..fd177fc84e 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -94,10 +94,10 @@ def test_rtp_bitstream_amrwb( @pytest.mark.parametrize("dtx", [False, True]) -@pytest.mark.parametrize("bitrate", [9600, 24400, 128000]) -@pytest.mark.parametrize("bandwidth", ["NB", "WB", "SWB", "FB"]) +@pytest.mark.parametrize("bitrate", [24400, 48000]) +@pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("caMode", ["OFF", "LO", "HI"]) -@pytest.mark.parametrize("framesPerPacket", [1, 3, 8]) +@pytest.mark.parametrize("framesPerPacket", [3]) def test_rtp_bitstream_evs( test_info, bitrate: int, @@ -121,7 +121,7 @@ def test_rtp_bitstream_evs( ) -@pytest.mark.parametrize("bitrate", [24400, 80000, 512000]) +@pytest.mark.parametrize("bitrate", [16400, 32000, 48000]) @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA", "MC", "MASA"]) @pytest.mark.parametrize("framesPerPacket", [8]) @@ -147,7 +147,7 @@ def test_rtp_bitstream_ivas_nodtx( ) -@pytest.mark.parametrize("bitrate", [13200, 24400, 80000]) +@pytest.mark.parametrize("bitrate", [13200, 24400, 64000]) @pytest.mark.parametrize("bandwidth", ["WB", "SWB", "FB"]) @pytest.mark.parametrize("format", ["STEREO", "SBA"]) @pytest.mark.parametrize("framesPerPacket", [1, 3]) -- GitLab From 65f594514a3147c64ba14f820bcdc64053aa5af6 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 14 Oct 2025 16:58:32 +0300 Subject: [PATCH 238/340] Reset external orientations after combinin orientations, remove decoder api call for ext orientation reset --- apps/decoder.c | 3 --- lib_dec/lib_dec.c | 12 ------------ lib_dec/lib_dec.h | 1 - lib_rend/ivas_prot_rend.h | 6 ------ lib_rend/ivas_rotation.c | 8 +++++++- 5 files changed, 7 insertions(+), 23 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 2e65a53320..33e0aeeff9 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3708,9 +3708,6 @@ static ivas_error decodeVoIP( } } } -#ifdef IVAS_RTPDUMP - IVAS_DEC_resetExternalOrientations( hIvasDec ); -#endif vec_pos_update = ( vec_pos_update + 1 ) % vec_pos_len; if ( vec_pos_update == 0 ) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index dc71db8e69..3c2b2b3d5e 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5607,18 +5607,6 @@ static ivas_error IVAS_DEC_feedSinglePIorientation( return error; } -/*---------------------------------------------------------------------* - * IVAS_DEC_resetExternalOrientations( ) - * - * Reset external orientations - *---------------------------------------------------------------------*/ - -void IVAS_DEC_resetExternalOrientations( - IVAS_DEC_HANDLE hIvasDec /* i/o: IVAS decoder handle */ -) -{ - ivas_external_orientation_reset( &( hIvasDec->st_ivas->hExtOrientationData ) ); -} #ifdef RTP_S4_251135_CR26253_0016_REV1 /*---------------------------------------------------------------------* diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index f20984c5ee..6331f647f7 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -560,7 +560,6 @@ void IVAS_DEC_PrintDisclaimer( #ifdef IVAS_RTPDUMP #include "ivas_rtp_pi_data.h" ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); -void IVAS_DEC_resetExternalOrientations(IVAS_DEC_HANDLE hIvasDec); #endif /* clang-format on */ diff --git a/lib_rend/ivas_prot_rend.h b/lib_rend/ivas_prot_rend.h index 868ffc4ada..e95ab4fe11 100644 --- a/lib_rend/ivas_prot_rend.h +++ b/lib_rend/ivas_prot_rend.h @@ -1301,12 +1301,6 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ); -#ifdef IVAS_RTPDUMP -void ivas_external_orientation_reset( - EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ -); - -#endif void ivas_external_orientation_close( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o: external orientation handle */ ); diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index b441382a55..c4d25bf2ef 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -56,6 +56,9 @@ static void external_target_interpolation( EXTERNAL_ORIENTATION_HANDLE hExtOrien static bool are_orientations_same( const IVAS_QUATERNION *orientation1, const IVAS_QUATERNION *orientation2 ); +#ifdef IVAS_RTPDUMP +static void ivas_external_orientation_reset( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData ); +#endif /*-----------------------------------------------------------------------* * ivas_headTrack_open() @@ -732,7 +735,7 @@ ivas_error ivas_external_orientation_open( * Reset external orientation values *-----------------------------------------------------------------------*/ -void ivas_external_orientation_reset( +static void ivas_external_orientation_reset( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ ) { @@ -1294,6 +1297,9 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->subframe_idx_start = 0; hCombinedOrientationData->cur_subframe_samples_rendered_start = 0; +#ifdef IVAS_RTPDUMP + ivas_external_orientation_reset( &hExtOrientationData ); +#endif return IVAS_ERR_OK; } -- GitLab From 0e85000030fef41d1aa8f0faeeb928a0787dd833 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 14 Oct 2025 17:22:28 +0200 Subject: [PATCH 239/340] add fix for IGF init printout --- lib_com/options.h | 2 +- lib_enc/ivas_cpe_enc.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib_com/options.h b/lib_com/options.h index 6d394089cd..0092baf252 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -168,7 +168,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ - +#define FIX_1413_IGF_INIT_PRINTOUT /* FhG: use correct variable for IGF initiliazation */ /* #################### End BE switches ################################## */ diff --git a/lib_enc/ivas_cpe_enc.c b/lib_enc/ivas_cpe_enc.c index 7c5b66cb8c..6c38228ea5 100644 --- a/lib_enc/ivas_cpe_enc.c +++ b/lib_enc/ivas_cpe_enc.c @@ -559,7 +559,11 @@ ivas_error ivas_cpe_enc( bw = ( hCPE->element_mode == IVAS_CPE_MDCT ) ? sts[n]->bwidth : max_bwidth; igf = getIgfPresent( sts[n]->element_mode, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->rf_mode ); +#ifdef FIX_1413_IGF_INIT_PRINTOUT if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, max_bwidth, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) +#else + if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) +#endif { return error; } -- GitLab From 1e90da4225c52e106f7fa11fcfb2f87bd08e0c79 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Wed, 15 Oct 2025 09:35:25 +1100 Subject: [PATCH 240/340] Fix for crash in debug mode in macos when reading windows stlye line ending * remove_cr() utilizes strcpy with overlapped memory * strcpy with overlapping buffers results in undefined behavior in C * Entire buffer movement is needlessly expensinve Signed-off-by: Ripinder Singh --- lib_util/cmdl_tools.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib_util/cmdl_tools.c b/lib_util/cmdl_tools.c index bec772ce42..625db12733 100644 --- a/lib_util/cmdl_tools.c +++ b/lib_util/cmdl_tools.c @@ -185,16 +185,16 @@ void convert_backslash( void remove_cr( char *str ) { - char *pos; + int32_t n, p = 0; /* remove all \r characters from the string */ - pos = strchr( str, '\r' ); - while ( pos != NULL ) + for ( n = 0; str[n] != 0; n++ ) { - strcpy( pos, pos + 1 ); - pos = strchr( pos, '\r' ); + char c = str[n]; + str[p] = c; + p += ( c == '\r' ) ? 0 : 1; } - + str[p] = 0; return; } -- GitLab From c0c05c8e7fb5cb5f4aee966e19d836171228527b Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 15 Oct 2025 08:57:10 +0200 Subject: [PATCH 241/340] fix ifdef logic --- lib_enc/ivas_cpe_enc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_enc/ivas_cpe_enc.c b/lib_enc/ivas_cpe_enc.c index 6c38228ea5..3f20082c82 100644 --- a/lib_enc/ivas_cpe_enc.c +++ b/lib_enc/ivas_cpe_enc.c @@ -560,9 +560,9 @@ ivas_error ivas_cpe_enc( bw = ( hCPE->element_mode == IVAS_CPE_MDCT ) ? sts[n]->bwidth : max_bwidth; igf = getIgfPresent( sts[n]->element_mode, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->rf_mode ); #ifdef FIX_1413_IGF_INIT_PRINTOUT - if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, max_bwidth, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) -#else if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) +#else + if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, max_bwidth, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) #endif { return error; -- GitLab From cad37be1e258d22aba78be8aade747720978bc87 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 15 Oct 2025 10:12:50 +0300 Subject: [PATCH 242/340] Remove ext orientation reset function, set ext orientations to identity at the end of combination --- lib_rend/ivas_rotation.c | 54 ++++++++-------------------------------- 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/lib_rend/ivas_rotation.c b/lib_rend/ivas_rotation.c index ddfda8e593..95361e116a 100644 --- a/lib_rend/ivas_rotation.c +++ b/lib_rend/ivas_rotation.c @@ -56,9 +56,6 @@ static void external_target_interpolation( EXTERNAL_ORIENTATION_HANDLE hExtOrien static bool are_orientations_same( const IVAS_QUATERNION *orientation1, const IVAS_QUATERNION *orientation2 ); -#ifdef IVAS_RTPDUMP -static void ivas_external_orientation_reset( EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData ); -#endif /*-----------------------------------------------------------------------* * ivas_headTrack_open() @@ -696,7 +693,6 @@ ivas_error ivas_external_orientation_open( const int16_t num_subframes /* i : number of subframes */ ) { -#ifndef IVAS_RTPDUMP int16_t i; IVAS_QUATERNION identity; @@ -704,16 +700,13 @@ ivas_error ivas_external_orientation_open( identity.w = 1.0f; identity.x = identity.y = identity.z = 0.0f; -#endif /* Allocate handle */ if ( ( *hExtOrientationData = (EXTERNAL_ORIENTATION_HANDLE) malloc( sizeof( EXTERNAL_ORIENTATION_DATA ) ) ) == NULL ) { return ( IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Can not allocate memory for external orientation memory\n" ) ); } ( *hExtOrientationData )->num_subframes = num_subframes; -#ifdef IVAS_RTPDUMP - ivas_external_orientation_reset( hExtOrientationData ); -#else + /* Enable head rotation and disable external orientation as default */ for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) { @@ -723,45 +716,11 @@ ivas_error ivas_external_orientation_open( ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; ( *hExtOrientationData )->Quaternions[i] = identity; } -#endif - return IVAS_ERR_OK; -} - - -#ifdef IVAS_RTPDUMP -/*-----------------------------------------------------------------------* - * ivas_external_orientation_reset() - * - * Reset external orientation values - *-----------------------------------------------------------------------*/ -static void ivas_external_orientation_reset( - EXTERNAL_ORIENTATION_HANDLE *hExtOrientationData /* i/o : external orientation handle */ -) -{ - if ( *hExtOrientationData != NULL ) - { - int16_t i; - IVAS_QUATERNION identity; - - identity.w = 1.0f; - identity.x = 0.0f; - identity.y = 0.0f; - identity.z = 0.0f; - /* Enable head rotation and disable external orientation as default */ - for ( i = 0; i < MAX_PARAM_SPATIAL_SUBFRAMES; i++ ) - { - ( *hExtOrientationData )->enableHeadRotation[i] = 1; - ( *hExtOrientationData )->enableExternalOrientation[i] = 0; - ( *hExtOrientationData )->enableRotationInterpolation[i] = 0; - ( *hExtOrientationData )->numFramesToTargetOrientation[i] = 0; - ( *hExtOrientationData )->Quaternions[i] = identity; - } - } + return IVAS_ERR_OK; } -#endif /*-----------------------------------------------------------------------* * ivas_external_orientation_close() * @@ -1288,7 +1247,14 @@ ivas_error combine_external_and_head_orientations( hCombinedOrientationData->cur_subframe_samples_rendered_start = 0; #ifdef IVAS_RTPDUMP - ivas_external_orientation_reset( &hExtOrientationData ); + /* Reset external orientations */ + if ( hExtOrientationData != NULL ) + { + for ( i = 0; i < hCombinedOrientationData->num_subframes; i++ ) + { + hExtOrientationData->Quaternions[i] = identity; + } + } #endif return IVAS_ERR_OK; } -- GitLab From af0382e7a2db1c15e1f2df506a2cfe63ea066009 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 15 Oct 2025 10:29:35 +0200 Subject: [PATCH 243/340] add logging for the various command lines in sr tests --- tests/split_rendering/utils.py | 130 +++++++++++++++++---------------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index 1d54d24e08..89f8aa2370 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -38,6 +38,7 @@ from typing import Tuple, Optional import numpy as np import pytest +import logging from tests.renderer.constants import ( BIN_SUFFIX_MERGETARGET, @@ -185,7 +186,7 @@ def run_full_chain_split_rendering( get_ssnr=False, get_odg=False, get_odg_bin=False, - delay_profile: Optional[Path]=None, + delay_profile: Optional[Path] = None, ) -> str: """ Runs the full split rendering chain consisting of @@ -235,80 +236,80 @@ def run_full_chain_split_rendering( in_meta_files = None # run encoder - cmd = SPLIT_PRE_COD_CMD[:] + enc_cmd = SPLIT_PRE_COD_CMD[:] if test_info.config.option.create_ref: - cmd[0] += BIN_SUFFIX_MERGETARGET - cmd[0] += binary_suffix - cmd[1] = bitrate + enc_cmd[0] += BIN_SUFFIX_MERGETARGET + enc_cmd[0] += binary_suffix + enc_cmd[1] = bitrate # if in REF or CUT creation mode use the comparetestv if test_info.config.option.create_ref or test_info.config.option.create_cut: in_file = FORMAT_TO_FILE_COMPARETEST[in_fmt] truncate_signal(in_file, cut_in_file) - cmd[3] = str(cut_in_file) + enc_cmd[3] = str(cut_in_file) else: - cmd[3] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) - cmd[4] = str(ivas_bitstream) + enc_cmd[3] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) + enc_cmd[4] = str(ivas_bitstream) if in_meta_files: - cmd[1:1] = in_meta_files + enc_cmd[1:1] = in_meta_files - cmd[1:1] = FORMAT_TO_IVAS_COD_FORMAT[in_fmt] + enc_cmd[1:1] = FORMAT_TO_IVAS_COD_FORMAT[in_fmt] - run_ivas_isar_enc_cmd(cmd) + run_ivas_isar_enc_cmd(enc_cmd) if delay_profile: rtp_bitstream = ivas_bitstream.with_suffix(".rtpg192") - cmd = [ + netsim_cmd = [ str(TOOLS_DIR / f"networkSimulator_g192{EXE_SUFFIX}"), str(delay_profile), str(ivas_bitstream), str(rtp_bitstream), str(tmp_dir / "tmp.netsimtrace"), "1", - "0" + "0", ] - run_cmd(cmd, test_info=test_info) + run_cmd(netsim_cmd, test_info=test_info) ivas_bitstream = rtp_bitstream # decode to split-rendering bitstream - cmd = SPLIT_PRE_DEC_CMD[:] + dec_cmd = SPLIT_PRE_DEC_CMD[:] if test_info.config.option.create_ref: - cmd[0] += BIN_SUFFIX_MERGETARGET - cmd[0] += binary_suffix - cmd[2] = str(pre_trajectory) - cmd[4] = str(render_config) - cmd[6] = str(pre_rend_fr) - cmd[7] = renderer_fmt - cmd[9] = str(ivas_bitstream) - cmd[10] = str(split_bitstream) + dec_cmd[0] += BIN_SUFFIX_MERGETARGET + dec_cmd[0] += binary_suffix + dec_cmd[2] = str(pre_trajectory) + dec_cmd[4] = str(render_config) + dec_cmd[6] = str(pre_rend_fr) + dec_cmd[7] = renderer_fmt + dec_cmd[9] = str(ivas_bitstream) + dec_cmd[10] = str(split_bitstream) if renderer_fmt == "BINAURAL_SPLIT_PCM": - cmd[5:5] = ["-om", str(split_md_file)] + dec_cmd[5:5] = ["-om", str(split_md_file)] if delay_profile: - cmd[5:5] = ["-voip"] + dec_cmd[5:5] = ["-voip"] - run_ivas_isar_dec_cmd(cmd) + run_ivas_isar_dec_cmd(dec_cmd) # run split renderer - cmd = SPLIT_POST_REND_CMD[:] + post_rend_cmd = SPLIT_POST_REND_CMD[:] if test_info.config.option.create_ref: - cmd[0] += BIN_SUFFIX_MERGETARGET - cmd[0] += binary_suffix - cmd[4] = str(split_bitstream) - cmd[6] = renderer_fmt - cmd[8] = str(out_file) - cmd[10] = str(post_trajectory) - cmd[12] = post_rend_fr + post_rend_cmd[0] += BIN_SUFFIX_MERGETARGET + post_rend_cmd[0] += binary_suffix + post_rend_cmd[4] = str(split_bitstream) + post_rend_cmd[6] = renderer_fmt + post_rend_cmd[8] = str(out_file) + post_rend_cmd[10] = str(post_trajectory) + post_rend_cmd[12] = post_rend_fr if renderer_fmt == "BINAURAL_SPLIT_PCM": - cmd[7:7] = ["-im", str(split_md_file)] + post_rend_cmd[7:7] = ["-im", str(split_md_file)] - run_isar_post_rend_cmd(cmd) + run_isar_post_rend_cmd(post_rend_cmd) if test_info.config.option.create_cut: # CUT creation mode will run a comparison with REF @@ -335,6 +336,11 @@ def run_full_chain_split_rendering( record_property(k, v) if output_differs: + logging.error(f"Encoder command line was: {' '.join(enc_cmd)}") + if delay_profile: + logging.error(f"Netsim command line was: {' '.join(netsim_cmd)}") + logging.error(f"Decoder command line was: {' '.join(dec_cmd)}") + logging.error(f"Post renderer command line was: {' '.join(post_rend_cmd)}") pytest.fail(f"Output differs: ({reason})") return out_file @@ -399,52 +405,52 @@ def run_external_split_rendering( in_meta_files = None # generate split-rendering bitstream - cmd = SPLIT_PRE_REND_CMD[:] + split_pre_cmd = SPLIT_PRE_REND_CMD[:] if test_info.config.option.create_ref: - cmd[0] += BIN_SUFFIX_MERGETARGET - cmd[0] += binary_suffix - cmd[4] = str(render_config) + split_pre_cmd[0] += BIN_SUFFIX_MERGETARGET + split_pre_cmd[0] += binary_suffix + split_pre_cmd[4] = str(render_config) if is_comparetest: in_file = FORMAT_TO_FILE_COMPARETEST[in_fmt] truncate_signal(in_file, cut_in_file) - cmd[6] = str(cut_in_file) + split_pre_cmd[6] = str(cut_in_file) else: - cmd[6] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) - cmd[8] = in_fmt - cmd[10] = str(split_bitstream) - cmd[12] = renderer_fmt - cmd[14] = str(pre_trajectory) - cmd[16] = pre_rend_fr + split_pre_cmd[6] = str(FORMAT_TO_FILE_SMOKETEST[in_fmt]) + split_pre_cmd[8] = in_fmt + split_pre_cmd[10] = str(split_bitstream) + split_pre_cmd[12] = renderer_fmt + split_pre_cmd[14] = str(pre_trajectory) + split_pre_cmd[16] = pre_rend_fr if renderer_fmt == "BINAURAL_SPLIT_PCM": - cmd[13:13] = ["-om", str(split_md_file)] + split_pre_cmd[13:13] = ["-om", str(split_md_file)] if in_meta_files: - cmd[9:9] = ["-im", *in_meta_files] + split_pre_cmd[9:9] = ["-im", *in_meta_files] - run_isar_ext_rend_cmd(cmd) + run_isar_ext_rend_cmd(split_pre_cmd) # run split renderer - cmd = SPLIT_POST_REND_CMD[:] + split_post_cmd = SPLIT_POST_REND_CMD[:] if test_info.config.option.create_ref: - cmd[0] += BIN_SUFFIX_MERGETARGET - cmd[0] += binary_suffix - cmd[4] = str(split_bitstream) - cmd[6] = renderer_fmt - cmd[8] = str(out_file) - cmd[10] = str(post_trajectory) - cmd[12] = post_rend_fr + split_post_cmd[0] += BIN_SUFFIX_MERGETARGET + split_post_cmd[0] += binary_suffix + split_post_cmd[4] = str(split_bitstream) + split_post_cmd[6] = renderer_fmt + split_post_cmd[8] = str(out_file) + split_post_cmd[10] = str(post_trajectory) + split_post_cmd[12] = post_rend_fr if renderer_fmt == "BINAURAL_SPLIT_PCM": - cmd[7:7] = ["-im", str(split_md_file)] + split_post_cmd[7:7] = ["-im", str(split_md_file)] if plc_error_pattern: - cmd[1:1] = ["-prbfi", str(plc_error_pattern)] + split_post_cmd[1:1] = ["-prbfi", str(plc_error_pattern)] - run_isar_ext_rend_cmd(cmd) + run_isar_ext_rend_cmd(split_post_cmd) if test_info.config.option.create_cut: # CUT creation mode will run a comparison with REF @@ -474,6 +480,8 @@ def run_external_split_rendering( record_property(k, v) if output_differs: + logging.error(f"Split Pre command line was: {' '.join(split_pre_cmd)}") + logging.error(f"Split Post command line was: {' '.join(split_post_cmd)}") pytest.fail(f"Output differs: ({reason})") return out_file -- GitLab From 54dcc5fba2685e82f86f7b28ebeba8fa7615b53d Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 15 Oct 2025 12:47:00 +0300 Subject: [PATCH 244/340] Add null check for csrc --- lib_util/ivas_rtp_payload.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 4e4b11f2aa..e759f674bc 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -199,7 +199,10 @@ ivas_error IVAS_RTP_PACK_UpdateHeader( header->ssrc = ssrc; header->CC = numCC; - memcpy( header->CSRC, csrc, numCC & 0xF ); + if ( csrc != NULL ) + { + memcpy( header->CSRC, csrc, numCC & 0xF ); + } if ( ( numExtensionBytes > 0 ) && ( extData != NULL ) ) { -- GitLab From 3927ad65aff53a9727eb57bc56e619ab0afc6aac Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Wed, 15 Oct 2025 14:38:07 +0300 Subject: [PATCH 245/340] Reduce rtpdump test timeout to 15min --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0789767ef..f532bfb1d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -716,7 +716,7 @@ rtpdump-test: stage: test script: - make -j - - testcase_timeout=30 + - testcase_timeout=15 - python3 -m pytest -q --tb=no -n auto -rA --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/rtp/test_rtp.py --testcase_timeout=$testcase_timeout artifacts: name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results" -- GitLab From 982416e7189f8d3e96b6bac04a9c2d1058cd2df4 Mon Sep 17 00:00:00 2001 From: naghibza Date: Mon, 13 Oct 2025 15:35:02 +0200 Subject: [PATCH 246/340] Pass float values of azimuth and elevation to efap_determine_gains() --- lib_com/options.h | 2 ++ lib_dec/ivas_ism_param_dec.c | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 6d394089cd..915a76f116 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -181,6 +181,8 @@ #define NONBE_1122_KEEP_EVS_MODE_UNCHANGED /* FhG: Disables fix for issue 1122 in EVS mode to keep BE tests green. This switch should be removed once the 1122 fix is added to EVS via a CR. */ #define NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES /* Nokia: Fix for issues 1399: obj edit broken with MC/SBA output in VOIP, and 1400: negative energy estimate used for gaining. */ +#define NONBE_1412_AVOID_ROUNDING_AZ_ELEV /* FhG: Avoid rounding when passing azimuth and elevation to efap_determine_gains() */ + /* ##################### End NON-BE switches ########################### */ /* ################## End DEVELOPMENT switches ######################### */ diff --git a/lib_dec/ivas_ism_param_dec.c b/lib_dec/ivas_ism_param_dec.c index ba29576848..f21a533c3e 100644 --- a/lib_dec/ivas_ism_param_dec.c +++ b/lib_dec/ivas_ism_param_dec.c @@ -709,8 +709,9 @@ void ivas_ism_dec_digest_tc( ( st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM && st_ivas->hDecoderConfig->Opt_Headrotation == 0 ) ) { int16_t i; +#ifndef NONBE_1412_AVOID_ROUNDING_AZ_ELEV int16_t azimuth, elevation; - +#endif /* we have a full frame interpolator, adapt it */ /* for BE testing */ if ( ( st_ivas->hDecoderConfig->output_Fs / FRAMES_PER_SEC ) == st_ivas->hTcBuffer->n_samples_available ) @@ -754,14 +755,18 @@ void ivas_ism_dec_digest_tc( else { // TODO tmu review when #215 is resolved +#ifndef NONBE_1412_AVOID_ROUNDING_AZ_ELEV azimuth = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_azimuth + 0.5f ); elevation = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_elevation + 0.5f ); - +#endif if ( ( st_ivas->renderer_type == RENDERER_TD_PANNING || st_ivas->renderer_type == RENDERER_OSBA_LS || st_ivas->renderer_type == RENDERER_BINAURAL_MIXER_CONV_ROOM || ( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV_ROOM && st_ivas->ivas_format == SBA_ISM_FORMAT && st_ivas->ism_mode == ISM_SBA_MODE_DISC ) ) && st_ivas->hCombinedOrientationData == NULL ) { +#ifdef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + float elevation = st_ivas->hIsmMetaData[i]->edited_elevation; +#endif if ( st_ivas->hIntSetup.is_planar_setup ) { /* If no elevation support in output format, then rendering should be done with zero elevation */ @@ -770,7 +775,11 @@ void ivas_ism_dec_digest_tc( if ( st_ivas->hEFAPdata != NULL ) { +#ifdef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + efap_determine_gains( st_ivas->hEFAPdata, st_ivas->hIsmRendererData->gains[i], st_ivas->hIsmMetaData[i]->edited_azimuth, elevation, EFAP_MODE_EFAP ); +#else efap_determine_gains( st_ivas->hEFAPdata, st_ivas->hIsmRendererData->gains[i], azimuth, elevation, EFAP_MODE_EFAP ); +#endif v_multc( st_ivas->hIsmRendererData->gains[i], st_ivas->hIsmMetaData[i]->edited_gain, st_ivas->hIsmRendererData->gains[i], st_ivas->hEFAPdata->numSpk ); } } @@ -778,6 +787,12 @@ void ivas_ism_dec_digest_tc( st_ivas->renderer_type == RENDERER_OSBA_AMBI || st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV ) { +#ifdef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + int16_t azimuth, elevation; + + azimuth = (int16_t) roundf( st_ivas->hIsmMetaData[i]->edited_azimuth ); + elevation = (int16_t) roundf( st_ivas->hIsmMetaData[i]->edited_elevation ); +#endif /*get HOA gets for direction (ACN/SN3D)*/ ivas_dirac_dec_get_response( azimuth, elevation, st_ivas->hIsmRendererData->gains[i], st_ivas->hIntSetup.ambisonics_order ); v_multc( st_ivas->hIsmRendererData->gains[i], st_ivas->hIsmMetaData[i]->edited_gain, st_ivas->hIsmRendererData->gains[i], ivas_sba_get_nchan( st_ivas->hIntSetup.ambisonics_order, 0 ) ); -- GitLab From 64ade2da770e54de392f54ce6341a964ba138c01 Mon Sep 17 00:00:00 2001 From: naghibza Date: Thu, 16 Oct 2025 09:41:46 +0200 Subject: [PATCH 247/340] replace roundf with floorf(x + 0.5) --- lib_dec/ivas_ism_param_dec.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib_dec/ivas_ism_param_dec.c b/lib_dec/ivas_ism_param_dec.c index f21a533c3e..253f480c6a 100644 --- a/lib_dec/ivas_ism_param_dec.c +++ b/lib_dec/ivas_ism_param_dec.c @@ -754,8 +754,8 @@ void ivas_ism_dec_digest_tc( } else { - // TODO tmu review when #215 is resolved #ifndef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + // TODO tmu review when #215 is resolved azimuth = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_azimuth + 0.5f ); elevation = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_elevation + 0.5f ); #endif @@ -770,7 +770,11 @@ void ivas_ism_dec_digest_tc( if ( st_ivas->hIntSetup.is_planar_setup ) { /* If no elevation support in output format, then rendering should be done with zero elevation */ +#ifdef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + elevation = 0.f; +#else elevation = 0; +#endif } if ( st_ivas->hEFAPdata != NULL ) @@ -788,10 +792,11 @@ void ivas_ism_dec_digest_tc( st_ivas->renderer_type == RENDERER_BINAURAL_FASTCONV ) { #ifdef NONBE_1412_AVOID_ROUNDING_AZ_ELEV + // TODO tmu review when #215 is resolved int16_t azimuth, elevation; - azimuth = (int16_t) roundf( st_ivas->hIsmMetaData[i]->edited_azimuth ); - elevation = (int16_t) roundf( st_ivas->hIsmMetaData[i]->edited_elevation ); + azimuth = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_azimuth + 0.5f ); + elevation = (int16_t) floorf( st_ivas->hIsmMetaData[i]->edited_elevation + 0.5f ); #endif /*get HOA gets for direction (ACN/SN3D)*/ ivas_dirac_dec_get_response( azimuth, elevation, st_ivas->hIsmRendererData->gains[i], st_ivas->hIntSetup.ambisonics_order ); -- GitLab From f7008ecf57fccb55ed47798de0b6023816dd3fc8 Mon Sep 17 00:00:00 2001 From: Eleni Fotopoulou Date: Thu, 16 Oct 2025 19:17:29 +0200 Subject: [PATCH 248/340] fix crash with IGF and bandwidth switching when DTX is enabled --- lib_com/options.h | 1 + lib_enc/ivas_cpe_enc.c | 58 +++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib_com/options.h b/lib_com/options.h index 6d394089cd..d98cf24c39 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -168,6 +168,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ +#define FIX_1411_IGF_CRASH_BW_SWITCHING /* FhG: Fix for issue 1411: fixes crash that can happen for IGF with BW switching and DTX*/ /* #################### End BE switches ################################## */ diff --git a/lib_enc/ivas_cpe_enc.c b/lib_enc/ivas_cpe_enc.c index 2c954fde2e..87d0cd7c0d 100644 --- a/lib_enc/ivas_cpe_enc.c +++ b/lib_enc/ivas_cpe_enc.c @@ -528,43 +528,55 @@ ivas_error ivas_cpe_enc( * Core codec configuration *----------------------------------------------------------------*/ - if ( hCPE->element_mode == IVAS_CPE_MDCT && st_ivas->hMCT == NULL ) +#ifdef FIX_1411_IGF_CRASH_BW_SWITCHING + if ( !( sts[0]->core_brate == SID_2k40 || sts[0]->core_brate == FRAME_NO_DATA ) ) /* Reconfigurations not needed with DTX*/ { - /* set coded BW for MDCT stereo */ - set_bw_stereo( hCPE ); - - /* reconfiguration of MDCT stereo */ - if ( sts[0]->bwidth != sts[0]->last_bwidth || ( ( hCPE->last_element_brate != hCPE->element_brate || hCPE->last_element_mode != hCPE->element_mode ) && sts[0]->bwidth != max_bwidth ) ) +#endif + if ( hCPE->element_mode == IVAS_CPE_MDCT && st_ivas->hMCT == NULL ) { - initMdctStereoEncData( hCPE->hStereoMdct, ivas_format, hCPE->element_mode, hCPE->element_brate, sts[0]->bwidth, 0, NULL, 0 ); - hCPE->hStereoMdct->isSBAStereoMode = ( ( ivas_format == SBA_FORMAT || ivas_format == SBA_ISM_FORMAT ) && ( st_ivas->nchan_transport == 2 ) ); + /* set coded BW for MDCT stereo */ + set_bw_stereo( hCPE ); - if ( hCPE->element_brate <= MAX_MDCT_ITD_BRATE && ivas_format == STEREO_FORMAT ) + /* reconfiguration of MDCT stereo */ + if ( sts[0]->bwidth != sts[0]->last_bwidth || ( ( hCPE->last_element_brate != hCPE->element_brate || hCPE->last_element_mode != hCPE->element_mode ) && sts[0]->bwidth != max_bwidth ) ) { - if ( ( error = initMdctItdHandling( hCPE->hStereoMdct, input_Fs ) ) != IVAS_ERR_OK ) + initMdctStereoEncData( hCPE->hStereoMdct, ivas_format, hCPE->element_mode, hCPE->element_brate, sts[0]->bwidth, 0, NULL, 0 ); + hCPE->hStereoMdct->isSBAStereoMode = ( ( ivas_format == SBA_FORMAT || ivas_format == SBA_ISM_FORMAT ) && ( st_ivas->nchan_transport == 2 ) ); + + if ( hCPE->element_brate <= MAX_MDCT_ITD_BRATE && ivas_format == STEREO_FORMAT ) { - return error; + if ( ( error = initMdctItdHandling( hCPE->hStereoMdct, input_Fs ) ) != IVAS_ERR_OK ) + { + return error; + } } } } - } - /* IGF reconfiguration */ - for ( n = 0; n < n_CoreChannels; n++ ) - { - if ( ( hCPE->last_element_brate != hCPE->element_brate || hCPE->element_mode != hCPE->last_element_mode || ( hCPE->element_mode == IVAS_CPE_TD && sts[0]->bits_frame_nominal != last_bits_frame_nominal ) || sts[n]->last_bwidth != sts[n]->bwidth ) && ( n == 0 || hCPE->element_mode == IVAS_CPE_MDCT ) ) + /* IGF reconfiguration */ + for ( n = 0; n < n_CoreChannels; n++ ) { - int16_t igf; - int16_t bw; - - bw = ( hCPE->element_mode == IVAS_CPE_MDCT ) ? sts[n]->bwidth : max_bwidth; - igf = getIgfPresent( sts[n]->element_mode, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->rf_mode ); - if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, max_bwidth, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) + if ( ( hCPE->last_element_brate != hCPE->element_brate || hCPE->element_mode != hCPE->last_element_mode || ( hCPE->element_mode == IVAS_CPE_TD && sts[0]->bits_frame_nominal != last_bits_frame_nominal ) || sts[n]->last_bwidth != sts[n]->bwidth +#ifdef FIX_1411_IGF_CRASH_BW_SWITCHING + || ( sts[n]->last_core_brate == SID_2k40 || sts[n]->last_core_brate == FRAME_NO_DATA ) +#endif + ) && + ( n == 0 || hCPE->element_mode == IVAS_CPE_MDCT ) ) { - return error; + int16_t igf; + int16_t bw; + + bw = ( hCPE->element_mode == IVAS_CPE_MDCT ) ? sts[n]->bwidth : max_bwidth; + igf = getIgfPresent( sts[n]->element_mode, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, bw, sts[n]->rf_mode ); + if ( ( error = IGF_Reconfig( &sts[n]->hIGFEnc, igf, 0, sts[n]->bits_frame_nominal * FRAMES_PER_SEC, max_bwidth, sts[n]->element_mode, sts[n]->rf_mode ) ) != IVAS_ERR_OK ) + { + return error; + } } } +#ifdef FIX_1411_IGF_CRASH_BW_SWITCHING } +#endif /* set ACELP@12k8 / ACELP@16k flag for flexible ACELP core */ for ( n = 0; n < n_CoreChannels; n++ ) -- GitLab From 967b7687d15da42bd8e2453cad92adb4294e174a Mon Sep 17 00:00:00 2001 From: Eleni Fotopoulou Date: Thu, 16 Oct 2025 21:38:15 +0200 Subject: [PATCH 249/340] move define --- lib_com/options.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib_com/options.h b/lib_com/options.h index d98cf24c39..48124e1e79 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -168,7 +168,7 @@ /*#define FIX_I4_OL_PITCH*/ /* fix open-loop pitch used for EVS core switching */ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ -#define FIX_1411_IGF_CRASH_BW_SWITCHING /* FhG: Fix for issue 1411: fixes crash that can happen for IGF with BW switching and DTX*/ + /* #################### End BE switches ################################## */ @@ -181,6 +181,7 @@ #define NONBE_1244_FIX_SWB_BWE_MEMORY /* VA: issue 1244: fix to SWB BWE memory in case of switching from FB coding - pending a review by Huawei */ #define NONBE_1122_KEEP_EVS_MODE_UNCHANGED /* FhG: Disables fix for issue 1122 in EVS mode to keep BE tests green. This switch should be removed once the 1122 fix is added to EVS via a CR. */ #define NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES /* Nokia: Fix for issues 1399: obj edit broken with MC/SBA output in VOIP, and 1400: negative energy estimate used for gaining. */ +#define FIX_1411_IGF_CRASH_BW_SWITCHING /* FhG: Fix for issue 1411: fixes crash that can happen for IGF with BW switching and DTX*/ /* ##################### End NON-BE switches ########################### */ -- GitLab From d189a9050bfe469a3773c11097326b14cf359d7e Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Fri, 17 Oct 2025 14:48:00 +0300 Subject: [PATCH 250/340] Add external output to PI data --- apps/decoder.c | 10 +- lib_util/ivas_rtp_file.c | 283 ++++++++++++++++++++++++++++++++++++++- lib_util/ivas_rtp_file.h | 7 +- 3 files changed, 295 insertions(+), 5 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 33e0aeeff9..16c1e0af3a 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -2950,7 +2950,7 @@ static ivas_error printBitstreamInfoVoip( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) { fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; @@ -3178,7 +3178,7 @@ static ivas_error decodeVoIP( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP - if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) { fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; @@ -3706,6 +3706,8 @@ static ivas_error decodeVoIP( goto cleanup; } } + + IVAS_RTP_WriteExtPiData( ivasRtp.f_piExtOut, ivasRtp.piData, ivasRtp.nReadPiData, numObj ); } } @@ -3806,6 +3808,8 @@ static ivas_error decodeVoIP( goto cleanup; } } + + IVAS_RTP_WriteExtPiData( ivasRtp.f_piExtOut, ivasRtp.piData, ivasRtp.nReadPiData, numObj ); } } @@ -3859,6 +3863,8 @@ static ivas_error decodeVoIP( { fprintf( stdout, "\nOutput MASA metadata file: %s\n", MasaFileWriter_getFilePath( masaWriter ) ); } + + fprintf( stdout, "\nOutput PI data file: %s\n", IVAS_RTP_GetExtPiFilePath( &ivasRtp ) ); } /*------------------------------------------------------------------------------------------* diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 8555480efd..fbe5a2f3d4 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -337,6 +337,258 @@ void IVAS_RTP_LogPiData( fprintf( f_piDataOut, "\n\t}" ); } +void IVAS_RTP_WriteExtPiData( + FILE *f_piDataOut, /* i/o : Output csv file handle to dump PI data for external output */ + const PIDATA_TS *piData, /* i : PI Data + Timestamp array containing all PI data in current packet */ + uint32_t nPiDataPresent, /* i : Number of valid elements in the piData array */ + uint16_t numObj /* i : Number of objects */ +) +{ + if ( f_piDataOut == NULL || piData == NULL || nPiDataPresent == 0 ) + { + return; + } + + int i = 0; + + while ( nPiDataPresent-- > 0 ) + { + const PIDATA_TS *cur = piData++; + + /* The writing follows the pattern of: timestamp, PI DATA TYPE, PI DATA */ + + fprintf( f_piDataOut, "%d,%s,", cur->timestamp, PiDataNames[cur->data.noPiData.piDataType] ); + switch ( cur->data.noPiData.piDataType ) + { + case IVAS_PI_SCENE_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.scene.orientation.w, cur->data.scene.orientation.x, cur->data.scene.orientation.y, cur->data.scene.orientation.z ); + } + break; + case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.deviceCompensated.orientation.w, cur->data.deviceCompensated.orientation.x, cur->data.deviceCompensated.orientation.y, cur->data.deviceCompensated.orientation.z ); + } + break; + case IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.deviceUnCompensated.orientation.w, cur->data.deviceUnCompensated.orientation.x, cur->data.deviceUnCompensated.orientation.y, cur->data.deviceUnCompensated.orientation.z ); + } + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.playbackOrientation.orientation.w, cur->data.playbackOrientation.orientation.x, cur->data.playbackOrientation.orientation.y, cur->data.playbackOrientation.orientation.z ); + } + break; + case IVAS_PI_HEAD_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.headOrientation.orientation.w, cur->data.headOrientation.orientation.x, cur->data.headOrientation.orientation.y, cur->data.headOrientation.orientation.z ); + } + break; +#endif + case IVAS_PI_ACOUSTIC_ENVIRONMENT: + { + fprintf( f_piDataOut, "%d", cur->data.acousticEnv.aeid ); + if ( cur->data.acousticEnv.availLateReverb ) + { + fprintf( f_piDataOut, ",%f,%f,%f", cur->data.acousticEnv.rt60[0], cur->data.acousticEnv.rt60[1], cur->data.acousticEnv.rt60[2] ); + fprintf( f_piDataOut, ",%f,%f,%f", cur->data.acousticEnv.dsr[0], cur->data.acousticEnv.dsr[1], cur->data.acousticEnv.dsr[2] ); + } + if ( cur->data.acousticEnv.availEarlyReflections ) + { + fprintf( f_piDataOut, ",%f,%f,%f", cur->data.acousticEnv.roomDimensions.x, cur->data.acousticEnv.roomDimensions.y, cur->data.acousticEnv.roomDimensions.z ); + fprintf( f_piDataOut, ",%f,%f,%f,%f,%f,%f", cur->data.acousticEnv.absorbCoeffs[0], cur->data.acousticEnv.absorbCoeffs[1], cur->data.acousticEnv.absorbCoeffs[2], cur->data.acousticEnv.absorbCoeffs[3], cur->data.acousticEnv.absorbCoeffs[4], cur->data.acousticEnv.absorbCoeffs[5] ); + } + } + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_PI_LISTENER_POSITION: + { + fprintf( f_piDataOut, "%f,%f,%f", cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); + } + break; + case IVAS_PI_AUDIO_DESCRIPTION: + { + uint32_t nEntries = cur->data.audioDesc.nValidEntries; + const IVAS_AUDIO_ID *audioId = cur->data.audioDesc.audioId; + + while ( nEntries-- > 0 ) + { + fprintf( f_piDataOut, "%d,%d,%d,%d,%d", audioId->speech, audioId->music, audioId->ambiance, audioId->editable, audioId->binaural ); + audioId++; + } + } + break; + case IVAS_PI_DIEGETIC_TYPE: + { + const bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; + fprintf( f_piDataOut, "%d,%d,%d,%d,%d", isDiegetic[0], isDiegetic[1], isDiegetic[2], isDiegetic[3], isDiegetic[4] ); + } + break; + case IVAS_PI_AUDIO_FOCUS_INDICATION: + { + if ( cur->data.focusIndication.availDirection ) + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "%d", cur->data.focusIndication.flvl ); + } + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: + { + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; + fprintf( f_piDataOut, "%d,%d,%d,%d", das->speech, das->music, das->ambiance, das->sli ); + } + break; + case IVAS_PI_RESERVED13: + case IVAS_PI_AUDIO_FOCUS_REQUEST: + { + + if ( cur->data.focusRequest.availDirection ) + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "%d", cur->data.focusRequest.flvl ); + } + } + break; + case IVAS_PI_RESERVED15: + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: + case IVAS_PI_RESERVED30: + case IVAS_PI_ISM_NUM: + { + fprintf( f_piDataOut, "%d", cur->data.ismNum.numObjects ); + } + break; + case IVAS_PI_ISM_ID: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%d", cur->data.ismId.id[i] ); + } + } + break; + case IVAS_PI_ISM_GAIN: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%d", cur->data.ismGain.dB[i] ); + } + } + break; + case IVAS_PI_ISM_ORIENTATION: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.ismOrientation.orientation[i].w, cur->data.ismOrientation.orientation[i].x, cur->data.ismOrientation.orientation[i].y, cur->data.ismOrientation.orientation[i].z ); + } + } + break; + case IVAS_PI_ISM_POSITION: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismPosition.position[i].x, cur->data.ismPosition.position[i].y, cur->data.ismPosition.position[i].z ); + } + } + break; + case IVAS_PI_ISM_DISTANCE_ATTENUATION: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismAttenuation.distAtten[i].ref_dist, cur->data.ismAttenuation.distAtten[i].max_dist, cur->data.ismAttenuation.distAtten[i].roll ); + } + } + break; + case IVAS_PI_ISM_DIRECTIVITY: + { + for ( i = 0; i < numObj; ++i ) + { + if ( i != 0 ) + { + fprintf( f_piDataOut, "," ); + } + fprintf( f_piDataOut, "%d,%d,%f", cur->data.ismDirectivity.directivity[i].innerConeAngle, cur->data.ismDirectivity.directivity[i].outerConeAngle, cur->data.ismDirectivity.directivity[i].outerAttenuationdB ); + } + } + break; + case IVAS_PI_PI_LATENCY: + { + fprintf( f_piDataOut, "%d", cur->data.piLatency.latency ); + } + break; + case IVAS_PI_R_ISM_ID: + { + fprintf( f_piDataOut, "%d", cur->data.ismEditId.id ); + } + break; + case IVAS_PI_R_ISM_GAIN: + { + fprintf( f_piDataOut, "%d", cur->data.ismEditGain.piDataType ); + } + break; + case IVAS_PI_R_ISM_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.ismEditOrientation.orientation.w, cur->data.ismEditOrientation.orientation.x, cur->data.ismEditOrientation.orientation.y, cur->data.ismEditOrientation.orientation.z ); + } + break; + case IVAS_PI_R_ISM_POSITION: + { + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismEditPosition.position.x, cur->data.ismEditPosition.position.y, cur->data.ismEditPosition.position.z ); + } + break; + case IVAS_PI_R_ISM_DIRECTION: + { + fprintf( f_piDataOut, "%f,%f", cur->data.ismEditDirection.azimuth, cur->data.ismEditDirection.elevation ); + } + break; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + } + fprintf( f_piDataOut, "\n" ); + } +} + +const char *IVAS_RTP_GetExtPiFilePath( IVAS_RTP *rtp ) +{ + return rtp->piExtFilename; +} + void IVAS_RTP_Term( IVAS_RTP *rtp /* i/o : IVAS RTP File reader/writer handle */ ) @@ -378,6 +630,12 @@ void IVAS_RTP_Term( rtp->f_piDataOut = NULL; } + if ( rtp->f_piExtOut != NULL ) + { + fclose( rtp->f_piExtOut ); + rtp->f_piExtOut = NULL; + } + if ( rtp->hRtpFile != NULL ) { IvasRtpFile_Close( &rtp->hRtpFile ); @@ -423,7 +681,9 @@ ivas_error IVAS_RTP_WRITER_Init( ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, /* i/o : IVAS RTP File reader handle */ const char *inputBitstreamFilename, /* i : Input rtpdump filename */ - const char *piOutputFilename /* i : Output PI data json filename */ + const char *piOutputFilename, /* i : Output PI data json filename */ + bool isExtOutput, /* i : External output mode */ + const char *outputWavFilename /* i : name of the output audio file */ ) { ivas_error error = IVAS_ERR_OK; @@ -453,6 +713,27 @@ ivas_error IVAS_RTP_READER_Init( } fprintf( rtp->f_piDataOut, "{\n" ); } + + if ( isExtOutput ) + { + char ext_pi[12]; + + /* sizeof( ext_pi ) accounts for terminating NULL, don't subtract extra 1 */ + const int32_t maxNameLenWithoutExt = FILENAME_MAX - (int32_t) sizeof( ext_pi ); + strncpy( rtp->piExtFilename, outputWavFilename , maxNameLenWithoutExt ); + snprintf( ext_pi, sizeof( ext_pi ), ".pidata.csv" ); + + /* strlen( metadata_filename[0] ) doesn't account for terminating NULL, subtract extra 1 */ + const int32_t maxNumCharactersToAppend = FILENAME_MAX - (int32_t) strlen( rtp->piExtFilename ) - 1; + strncat( rtp->piExtFilename, ext_pi, maxNumCharactersToAppend ); + + rtp->f_piExtOut = fopen( rtp->piExtFilename, "w" ); + if ( rtp->f_piExtOut == NULL ) + { + fprintf( stderr, "could not open: %s\n", rtp->piExtFilename ); + return IVAS_ERR_FAILED_FILE_OPEN; + } + } } return error; diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 2a58dc1c96..062b29c5c4 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -46,6 +46,8 @@ typedef struct IVAS_RTP_FILE_HANDLE hRtpFile; FILE *f_piDataOut; + FILE *f_piExtOut; + char piExtFilename[FILENAME_MAX]; IVAS_RTP_CODEC codecId; uint32_t nWrittenPiData; uint32_t nReadPiData; @@ -69,11 +71,12 @@ typedef struct } IVAS_RTP; ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ); -ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename ); +ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename, bool isExtOutput, const char *outputWavFilename ); void IVAS_RTP_Term( IVAS_RTP *rtp ); ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ); ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ); void IVAS_RTP_LogPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent ); - +void IVAS_RTP_WriteExtPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent, uint16_t numObj ); +const char *IVAS_RTP_GetExtPiFilePath( IVAS_RTP *rtp ); #endif /* IVAS_RTP_FILE_H */ -- GitLab From ba0ddac43ba60a5de779a576d7fa061cb623dd85 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Fri, 17 Oct 2025 16:10:12 +0300 Subject: [PATCH 251/340] Make the comparison operator correct --- lib_dec/ivas_init_dec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_dec/ivas_init_dec.c b/lib_dec/ivas_init_dec.c index 11359e6658..80b8c7e5e9 100644 --- a/lib_dec/ivas_init_dec.c +++ b/lib_dec/ivas_init_dec.c @@ -3198,7 +3198,7 @@ static ivas_error doSanityChecks_IVAS( } } - if ( st_ivas->hDecoderConfig->Opt_ObjEdit_on & st_ivas->hDecoderConfig->Opt_non_diegetic_pan ) + if ( st_ivas->hDecoderConfig->Opt_ObjEdit_on && st_ivas->hDecoderConfig->Opt_non_diegetic_pan ) { return IVAS_ERROR( IVAS_ERR_OBJECTS_EDITING_AND_PANNING_NOT_SUPPORTED, "Wrong set-up: Only object editing or Non-diegetic panning can be used." ); } -- GitLab From 57d71e79a745f8f7f13c80e47154fa9bbb545b15 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Sun, 19 Oct 2025 16:18:59 +0200 Subject: [PATCH 252/340] Fix for ivas-conformance-linux --- .gitlab-ci.yml | 10 ++----- scripts/parse_commands.py | 58 ++++++--------------------------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef4908c45b..ce484f4950 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1385,10 +1385,7 @@ ivas-conformance-linux: # Reference creation - python3 scripts/prepare_combined_format_inputs.py - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" - - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files - - # Output creation - - python3 -m pytest -q $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html + - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt # Copy input data and output ref data @@ -1406,9 +1403,8 @@ ivas-conformance-linux: - cp -r scripts/trajectories testvec - cp -r scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface - cp -r tests/ref testvec/testv/ref - - cp -r tests/dut/* testvec/testv/ref - - cp -r tests/renderer/cut testvec/testv/renderer/ref - - cp -r tests/split_rendering/cut testvec/testv/split_rendering/ref + - cp -r tests/renderer/ref testvec/testv/renderer/ref + - cp -r tests/split_rendering/ref testvec/testv/split_rendering/ref - cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 3a5fb6c1af..50419e9490 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -21,56 +21,16 @@ if __name__ == '__main__': here = Path(__file__).parent.resolve() - cmds_enc=[] - cmds_dec=[] - cmds_rend=[] - cmds_isar_post_rend=[] + with open(input,'r') as infile: + report = infile.read() + cmds_enc = re.findall(r'REF encoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) + cmds_dec = re.findall(r'REF decoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) + cmds_rend = re.findall(r'Running command\s*\n\s*(\S.*)', report, re.MULTILINE) + cmds_isar_post_rend = re.findall(r'Running ISAR post renderer command\s*\n\s*(\S.*)', report, re.MULTILINE) - - if path.isdir(input): - input = Path(input).rglob('*.html') - else: - input = [input] - for html_report in input: - - with open(html_report,'r') as infile: - for line in infile.readlines(): - cmds_enc.extend(re.findall(r"DUT encoder command:\\n\\t(.*?)\\n", line)) - cmds_dec.extend(re.findall(r"DUT decoder command:\\n\\t(.*?)\\n", line)) - cmds_rend.extend(re.findall(r"Running command\\n(.*?)\\n", line)) - cmds_isar_post_rend.extend(re.findall(r"Running ISAR post renderer command\\n(.*?)\\n", line)) - - # If pytest-html < v4 is used, the parsing will fail and render empty lists. This is a work-around in case that happens. - if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): - for html_report in input: - with open(html_report,'r') as infile: - enc_cmd = False - dec_cmd = False - rend_cmd = False - isar_post_rend_cmd = False - for line in infile.readlines(): - line = line.split("
    ")[0] # Remove trailing html tags - if enc_cmd: - cmds_enc.append(line) - enc_cmd = False - elif dec_cmd: - cmds_dec.append(line) - dec_cmd = False - elif rend_cmd: - cmds_rend.append(line) - rend_cmd = False - elif isar_post_rend_cmd: - cmds_isar_post_rend.append(line) - isar_post_rend_cmd = False - else: - if "DUT encoder command" in line: - enc_cmd = True - elif "DUT decoder command" in line: - dec_cmd = True - elif "Running command" in line: - rend_cmd = True - elif "Running ISAR post renderer command" in line: - isar_post_rend_cmd = True + # Remove HTML tags + cmds_rend = [x.split('<')[0] for x in cmds_rend] + cmds_isar_post_rend = [x.split('<')[0] for x in cmds_isar_post_rend] # Sort lists to keep deterministic order between runs cmds_enc.sort() -- GitLab From b6d649fd2effeda3d8ce4a1d72ff3f2b57f81d68 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Sun, 19 Oct 2025 18:04:11 +0200 Subject: [PATCH 253/340] Adapt script to use _ref suffix in command lines --- scripts/parse_commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 50419e9490..86b584c981 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -47,7 +47,7 @@ if __name__ == '__main__': # Adjust file arguments, pass other arguments as they are if path.exists(arg): arg = path.relpath(arg).replace('\\','/') - arg = re.sub('IVAS_cod(.exe)?', '$CUT_ENC_BIN', arg) + arg = re.sub('IVAS_cod_ref(.exe)?', '$CUT_ENC_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) arg = re.sub('tests', CUT_PATH, arg) args.append(arg) @@ -75,7 +75,7 @@ if __name__ == '__main__': # Adjust file arguments, pass other arguments as they are if path.exists(arg): arg = path.relpath(arg).replace('\\','/') - arg = re.sub('IVAS_dec(.exe)?', '$CUT_DEC_BIN', arg) + arg = re.sub('IVAS_dec_ref(.exe)?', '$CUT_DEC_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # For .fer cases the bitstream is in ref arg = re.sub('tests/split_rendering/renderer_configs', REF_PATH + r'/split_rendering/renderer_configs', arg) @@ -131,7 +131,7 @@ if __name__ == '__main__': # Adjust file arguments, pass other arguments as they are if path.exists(arg): arg = path.relpath(arg).replace('\\','/') - arg = re.sub('IVAS_rend(.exe)?', '$CUT_REND_BIN', arg) + arg = re.sub('IVAS_rend_ref(.exe)?', '$CUT_REND_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) arg = re.sub('tests/renderer/data', TESTV_PATH + r'renderer/data/', arg) arg = re.sub('tests', CUT_PATH, arg) @@ -156,7 +156,7 @@ if __name__ == '__main__': # Adjust file arguments, pass other arguments as they are if path.exists(arg): arg = path.relpath(arg).replace('\\','/') - arg = re.sub('ISAR_post_rend(.exe)?', '$CUT_ISAR_POST_REND_BIN', arg) + arg = re.sub('ISAR_post_rend_ref(.exe)?', '$CUT_ISAR_POST_REND_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) if re.search("^tests.*bit$",arg): arg = re.sub('tests/split_rendering/cut', REF_PATH + r'/split_rendering/ref', arg) -- GitLab From 833b502f85992c52e211f0fcc01ad1fd12b62e25 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 09:22:29 +0200 Subject: [PATCH 254/340] Updates for parse_commands.py and split renderer logging of REF decoder commands --- scripts/parse_commands.py | 40 +++++++++++++++++++++++++++++++++++---- tests/renderer/utils.py | 5 ++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 86b584c981..88ac9ca4a7 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -2,6 +2,7 @@ import argparse import re +import json from os import path from pathlib import Path import glob @@ -28,6 +29,37 @@ if __name__ == '__main__': cmds_rend = re.findall(r'Running command\s*\n\s*(\S.*)', report, re.MULTILINE) cmds_isar_post_rend = re.findall(r'Running ISAR post renderer command\s*\n\s*(\S.*)', report, re.MULTILINE) + + if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): + lines = re.split(r'\\n', report) + enc_cmd = False + dec_cmd = False + rend_cmd = False + isar_post_rend_cmd = False + for line in lines: + line = line.split("
    ")[0].replace('\\t','') # Remove trailing html tags + if enc_cmd: + cmds_enc.append(line) + enc_cmd = False + elif dec_cmd: + cmds_dec.append(line) + dec_cmd = False + elif rend_cmd: + cmds_rend.append(line) + rend_cmd = False + elif isar_post_rend_cmd: + cmds_isar_post_rend.append(line) + isar_post_rend_cmd = False + else: + if "REF encoder command" in line: + enc_cmd = True + elif "REF decoder command" in line: + dec_cmd = True + elif "Running command" in line: + rend_cmd = True + elif "Running ISAR post renderer command" in line: + isar_post_rend_cmd = True + # Remove HTML tags cmds_rend = [x.split('<')[0] for x in cmds_rend] cmds_isar_post_rend = [x.split('<')[0] for x in cmds_isar_post_rend] @@ -138,10 +170,10 @@ if __name__ == '__main__': args.append(arg) cmd = ' '.join(args) - if "cut" in cmd: + if "ref" in cmd: outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) - if out and "cut" in out.group(1): + if out and "ref" in out.group(1): outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/renderer/cut',REF_PATH + r'/renderer/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: @@ -164,10 +196,10 @@ if __name__ == '__main__': args.append(arg) cmd = ' '.join(args) - if "cut" in cmd: + if "ref" in cmd: outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) - if out and "cut" in out.group(1): + if out and "ref" in out.group(1): outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/split_rendering/cut',REF_PATH + r'/split_rendering/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: diff --git a/tests/renderer/utils.py b/tests/renderer/utils.py index 2a0d50568c..9acb1b7f18 100644 --- a/tests/renderer/utils.py +++ b/tests/renderer/utils.py @@ -109,7 +109,10 @@ def run_ivas_isar_enc_cmd(cmd, env=None): def run_ivas_isar_dec_cmd(cmd, env=None): - logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") + if BIN_SUFFIX_MERGETARGET in cmd[0]: + logging.info(f"\nREF decoder command:\n\t{' '.join(cmd)}\n") + else: + logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") _run_cmd(cmd, env) -- GitLab From 47fb79ad007262860f3eb92f885b588a4dd3ea49 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 11:32:28 +0200 Subject: [PATCH 255/340] Correct dut->ref cut->ref in headers for Readme* scripts --- scripts/dec_header.txt | 6 +++--- scripts/dec_isar_header.txt | 2 +- scripts/enc_header.txt | 6 +++--- scripts/isar_post_rend_header.txt | 2 +- scripts/jbm_header.txt | 6 +++--- scripts/rend_header.txt | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/dec_header.txt b/scripts/dec_header.txt index 599c7e73ed..68aff36355 100644 --- a/scripts/dec_header.txt +++ b/scripts/dec_header.txt @@ -16,7 +16,7 @@ LOG_FILE=Readme_IVAS_dec_log.txt rm -rf tmp rm -rf $CUT_PATH mkdir -p $CUT_PATH -mkdir -p $CUT_PATH/dut/masa_test/dec_output -mkdir -p $CUT_PATH/dut/param_file/dec -mkdir -p $CUT_PATH/dut/sba_bs/raw +mkdir -p $CUT_PATH/ref/masa_test/dec_output +mkdir -p $CUT_PATH/ref/param_file/dec +mkdir -p $CUT_PATH/ref/sba_bs/raw diff --git a/scripts/dec_isar_header.txt b/scripts/dec_isar_header.txt index 454383382f..0f449b21c2 100644 --- a/scripts/dec_isar_header.txt +++ b/scripts/dec_isar_header.txt @@ -16,6 +16,6 @@ LOG_FILE=Readme_IVAS_ISAR_dec_log.txt rm -rf tmp rm -rf $CUT_PATH mkdir -p $CUT_PATH -mkdir -p $CUT_PATH/split_rendering/cut +mkdir -p $CUT_PATH/split_rendering/ref diff --git a/scripts/enc_header.txt b/scripts/enc_header.txt index 18387a9326..76ebeee3e1 100644 --- a/scripts/enc_header.txt +++ b/scripts/enc_header.txt @@ -16,7 +16,7 @@ LOG_FILE=Readme_IVAS_enc_log.txt rm -rf tmp rm -rf $CUT_PATH mkdir -p $CUT_PATH -mkdir -p $CUT_PATH/dut/masa_test/bitstreams -mkdir -p $CUT_PATH/dut/param_file/enc -mkdir -p $CUT_PATH/dut/sba_bs/pkt +mkdir -p $CUT_PATH/ref/masa_test/bitstreams +mkdir -p $CUT_PATH/ref/param_file/enc +mkdir -p $CUT_PATH/ref/sba_bs/pkt diff --git a/scripts/isar_post_rend_header.txt b/scripts/isar_post_rend_header.txt index ed7eb20988..99a84b6c36 100644 --- a/scripts/isar_post_rend_header.txt +++ b/scripts/isar_post_rend_header.txt @@ -15,7 +15,7 @@ LOG_FILE=Readme_IVAS_isar_post_rend_log.txt rm -rf tmp rm -rf $CUT_PATH -mkdir -p $CUT_PATH/split_rendering/cut +mkdir -p $CUT_PATH/split_rendering/ref diff --git a/scripts/jbm_header.txt b/scripts/jbm_header.txt index d244c83b3f..57f59809f9 100644 --- a/scripts/jbm_header.txt +++ b/scripts/jbm_header.txt @@ -16,7 +16,7 @@ LOG_FILE=Readme_IVAS_jbm_log.txt rm -rf tmp rm -rf $CUT_PATH mkdir -p $CUT_PATH -mkdir -p $CUT_PATH/dut/masa_test/dec_output -mkdir -p $CUT_PATH/dut/param_file/dec -mkdir -p $CUT_PATH/dut/sba_bs/raw +mkdir -p $CUT_PATH/ref/masa_test/dec_output +mkdir -p $CUT_PATH/ref/param_file/dec +mkdir -p $CUT_PATH/ref/sba_bs/raw diff --git a/scripts/rend_header.txt b/scripts/rend_header.txt index c56eb17cb0..a83b0a17bc 100644 --- a/scripts/rend_header.txt +++ b/scripts/rend_header.txt @@ -15,7 +15,7 @@ LOG_FILE=Readme_IVAS_rend_log.txt rm -rf tmp rm -rf $CUT_PATH -mkdir -p $CUT_PATH/renderer/cut +mkdir -p $CUT_PATH/renderer/ref mkdir -p $CUT_PATH/renderer/data -- GitLab From 9fcd9b038c1d4ce498d9f49ff93ba675edc83d34 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 13:20:01 +0200 Subject: [PATCH 256/340] Correct dut->ref cut->ref in parse_commands.py --- scripts/parse_commands.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 88ac9ca4a7..3f95d4281d 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -87,7 +87,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') bts = re.search(r"\s(([\S]+)(.bts|.192|.pkt|.fer))$", cmd) if bts: - outfile.write('$DIFF_BIN '+bts.group(1).replace(CUT_PATH + r'/dut',REF_PATH + r'/ref')+' '+bts.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+bts.group(1).replace(CUT_PATH + r'/ref',REF_PATH + r'/ref')+' '+bts.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) @@ -112,11 +112,11 @@ if __name__ == '__main__': arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # For .fer cases the bitstream is in ref arg = re.sub('tests/split_rendering/renderer_configs', REF_PATH + r'/split_rendering/renderer_configs', arg) if re.search("^tests.*192$",arg): - arg = re.sub('tests/split_rendering/cut', REF_PATH + r'/split_rendering/ref', arg) + arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) if re.search("\.wav$",arg): arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav else: - arg = re.sub('tests/dut', REF_PATH + r'/ref', arg) # Input argument + arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # Input argument if re.search("^tests.*bit$",arg): arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav args.append(arg) @@ -138,12 +138,12 @@ if __name__ == '__main__': for output in glob.glob(absolute_out.group(1) + '*'): output = path.relpath(output).replace('\\','/') output = re.sub('tests', CUT_PATH, output) - diff_cmds.append('$DIFF_BIN '+output.replace(CUT_PATH + r'/dut',REF_PATH + r'/ref')+' '+output+' >> $LOG_FILE 2>&1') + diff_cmds.append('$DIFF_BIN '+output.replace(CUT_PATH + r'/ref',REF_PATH + r'/ref')+' '+output+' >> $LOG_FILE 2>&1') outfile.write(('; ').join(diff_cmds)) - if isar_out and "cut" in isar_out.group(1): - outfile.write('$DIFF_BIN '+isar_out.group(1).replace(CUT_PATH + r'/split_rendering/cut',REF_PATH + r'/split_rendering/ref')+' '+isar_out.group(1)+' >> $LOG_FILE 2>&1') - if isar_md_out and "cut" in isar_md_out.group(1): - outfile.write('; $DIFF_BIN '+isar_md_out.group(1).replace(CUT_PATH + r'/split_rendering/cut',REF_PATH + r'/split_rendering/ref')+' '+isar_md_out.group(1)+' >> $LOG_FILE 2>&1\n') + if isar_out and "ref" in isar_out.group(1): + outfile.write('$DIFF_BIN '+isar_out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+isar_out.group(1)+' >> $LOG_FILE 2>&1') + if isar_md_out and "ref" in isar_md_out.group(1): + outfile.write('; $DIFF_BIN '+isar_md_out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+isar_md_out.group(1)+' >> $LOG_FILE 2>&1\n') else: outfile.write('\n') outfile.write('\n\n') @@ -174,7 +174,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) if out and "ref" in out.group(1): - outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/renderer/cut',REF_PATH + r'/renderer/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/renderer/ref',REF_PATH + r'/renderer/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) @@ -191,7 +191,7 @@ if __name__ == '__main__': arg = re.sub('ISAR_post_rend_ref(.exe)?', '$CUT_ISAR_POST_REND_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) if re.search("^tests.*bit$",arg): - arg = re.sub('tests/split_rendering/cut', REF_PATH + r'/split_rendering/ref', arg) + arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) arg = re.sub('tests', CUT_PATH, arg) args.append(arg) cmd = ' '.join(args) @@ -200,7 +200,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) if out and "ref" in out.group(1): - outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/split_rendering/cut',REF_PATH + r'/split_rendering/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) \ No newline at end of file -- GitLab From b711a74c3d7d6f16320d137ead67a56a311b2fc0 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 13:59:52 +0200 Subject: [PATCH 257/340] Updates for decoder tests in parse_commands.py --- scripts/parse_commands.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 3f95d4281d..9592b587a4 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -103,22 +103,32 @@ if __name__ == '__main__': absolute_out = re.search(r"\s(([\S]+)(.wav))$", cmd) args = [] + input_set = False for arg in cmd.split(): # Adjust file arguments, pass other arguments as they are if path.exists(arg): arg = path.relpath(arg).replace('\\','/') arg = re.sub('IVAS_dec_ref(.exe)?', '$CUT_DEC_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) - arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # For .fer cases the bitstream is in ref - arg = re.sub('tests/split_rendering/renderer_configs', REF_PATH + r'/split_rendering/renderer_configs', arg) - if re.search("^tests.*192$",arg): - arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) - if re.search("\.wav$",arg): - arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav - else: - arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # Input argument - if re.search("^tests.*bit$",arg): - arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav + if 'dectrace' in arg: + arg = re.sub('tests', CUT_PATH, arg) # dectrace output files occur before the input file + if 'tests' in arg: + if not input_set: + arg = re.sub('tests', REF_PATH, arg) # First occurence of tests/* is the input file + input_set = True + else: + arg = re.sub('tests', CUT_PATH, arg) # Remaining occurences of tests/* are output files + + #arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # For .fer cases the bitstream is in ref + # arg = re.sub('tests/split_rendering/renderer_configs', REF_PATH + r'/split_rendering/renderer_configs', arg) + # if re.search("^tests.*192$",arg): + # arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) + # if re.search("\.wav$",arg): + # arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav + # else: + # arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # Input argument + # if re.search("^tests.*bit$",arg): + # arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav args.append(arg) cmd = ' '.join(args) -- GitLab From c9503d7532c51bd11c1958f8ad5ebc474d6cfc9d Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 14:49:21 +0200 Subject: [PATCH 258/340] Fix for ISAR dec cases --- scripts/parse_commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 9592b587a4..5a6b27916c 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -29,7 +29,7 @@ if __name__ == '__main__': cmds_rend = re.findall(r'Running command\s*\n\s*(\S.*)', report, re.MULTILINE) cmds_isar_post_rend = re.findall(r'Running ISAR post renderer command\s*\n\s*(\S.*)', report, re.MULTILINE) - + # Depending on pytest-html version, the data may be in a json-blob. If so, the code below should handle this format. if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): lines = re.split(r'\\n', report) enc_cmd = False @@ -110,6 +110,9 @@ if __name__ == '__main__': arg = path.relpath(arg).replace('\\','/') arg = re.sub('IVAS_dec_ref(.exe)?', '$CUT_DEC_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) + # Identify input files + if '.txt' in arg: + arg = re.sub('tests', REF_PATH, arg) # Decoder configuration file -- input file if 'dectrace' in arg: arg = re.sub('tests', CUT_PATH, arg) # dectrace output files occur before the input file if 'tests' in arg: -- GitLab From 59df04d914ea4a5e85ae99924bd09880797b9565 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Mon, 20 Oct 2025 15:37:54 +0200 Subject: [PATCH 259/340] Fixes for identifying input and output files in parse_commands.py --- scripts/parse_commands.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 5a6b27916c..d891f75caf 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -112,26 +112,16 @@ if __name__ == '__main__': arg = re.sub('scripts', TESTV_PATH, arg) # Identify input files if '.txt' in arg: - arg = re.sub('tests', REF_PATH, arg) # Decoder configuration file -- input file - if 'dectrace' in arg: - arg = re.sub('tests', CUT_PATH, arg) # dectrace output files occur before the input file + arg = re.sub('tests', REF_PATH, arg) + # Identify special cases of output files: dectrace, spltmd.bit + if 'spltmd.bit' in arg or 'dectrace' in arg: + arg = re.sub('tests', CUT_PATH, arg) if 'tests' in arg: if not input_set: arg = re.sub('tests', REF_PATH, arg) # First occurence of tests/* is the input file input_set = True else: arg = re.sub('tests', CUT_PATH, arg) # Remaining occurences of tests/* are output files - - #arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # For .fer cases the bitstream is in ref - # arg = re.sub('tests/split_rendering/renderer_configs', REF_PATH + r'/split_rendering/renderer_configs', arg) - # if re.search("^tests.*192$",arg): - # arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) - # if re.search("\.wav$",arg): - # arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav - # else: - # arg = re.sub('tests/ref', REF_PATH + r'/ref', arg) # Input argument - # if re.search("^tests.*bit$",arg): - # arg = re.sub('tests', CUT_PATH, arg) # Output argument for decoder ends with .wav args.append(arg) cmd = ' '.join(args) -- GitLab From c8362451d91cce498c03018c586e21cc65fd13d3 Mon Sep 17 00:00:00 2001 From: Kacper Sagnowski Date: Mon, 20 Oct 2025 17:26:38 +0200 Subject: [PATCH 260/340] Fix input file selection for external split rendering tests --- tests/split_rendering/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index 89f8aa2370..f2125bcc90 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -357,7 +357,6 @@ def run_external_split_rendering( plc_error_pattern: Path = None, renderer_fmt: str = "BINAURAL_SPLIT_CODED", binary_suffix: str = "", - is_comparetest: bool = False, post_rend_fr: str = "20", pre_rend_fr: str = "20", get_mld=False, @@ -411,7 +410,8 @@ def run_external_split_rendering( split_pre_cmd[0] += BIN_SUFFIX_MERGETARGET split_pre_cmd[0] += binary_suffix split_pre_cmd[4] = str(render_config) - if is_comparetest: + # if in REF or CUT creation mode use the comparetestv + if test_info.config.option.create_ref or test_info.config.option.create_cut: in_file = FORMAT_TO_FILE_COMPARETEST[in_fmt] truncate_signal(in_file, cut_in_file) -- GitLab From b354be8e791ed1d66f136e05192e750d70bae4fb Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Tue, 21 Oct 2025 11:24:48 +1100 Subject: [PATCH 261/340] Fix to reset of the PI data pointer at each packet read boundary * Format fixes * Pull main Signed-off-by: Ripinder Singh --- apps/decoder.c | 9 ++++----- lib_util/ivas_rtp_file.c | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 16c1e0af3a..4996122e70 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3099,7 +3099,6 @@ static ivas_error decodeVoIP( #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; int32_t initialTsOffsetSystemAndRTP = 0; - uint32_t nProcPiData = 0; #else FILE *f_rtpstream = NULL; EVS_RTPDUMP_DEPACKER rtpdumpDepacker; @@ -3477,19 +3476,19 @@ static ivas_error decodeVoIP( uint32_t piTs = systemTime_ms * 16 + initialTsOffsetSystemAndRTP; uint32_t numPiData = 0; - while ( nProcPiData + numPiData < ivasRtp.nReadPiData && - ivasRtp.piData[nProcPiData + numPiData].timestamp <= piTs ) + while ( ivasRtp.nProcPiData + numPiData < ivasRtp.nReadPiData && + ivasRtp.piData[ivasRtp.nProcPiData + numPiData].timestamp <= piTs ) { numPiData++; } - if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[nProcPiData], numPiData ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } - nProcPiData += numPiData; + ivasRtp.nProcPiData += numPiData; } #endif #ifdef FIX_1119_SPLIT_RENDERING_VOIP diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index fbe5a2f3d4..384bdd57e2 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -570,7 +570,7 @@ void IVAS_RTP_WriteExtPiData( break; case IVAS_PI_R_ISM_POSITION: { - fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismEditPosition.position.x, cur->data.ismEditPosition.position.y, cur->data.ismEditPosition.position.z ); + fprintf( f_piDataOut, "%f,%f,%f", cur->data.ismEditPosition.position.x, cur->data.ismEditPosition.position.y, cur->data.ismEditPosition.position.z ); } break; case IVAS_PI_R_ISM_DIRECTION: @@ -720,7 +720,7 @@ ivas_error IVAS_RTP_READER_Init( /* sizeof( ext_pi ) accounts for terminating NULL, don't subtract extra 1 */ const int32_t maxNameLenWithoutExt = FILENAME_MAX - (int32_t) sizeof( ext_pi ); - strncpy( rtp->piExtFilename, outputWavFilename , maxNameLenWithoutExt ); + strncpy( rtp->piExtFilename, outputWavFilename, maxNameLenWithoutExt ); snprintf( ext_pi, sizeof( ext_pi ), ".pidata.csv" ); /* strlen( metadata_filename[0] ) doesn't account for terminating NULL, subtract extra 1 */ -- GitLab From 8989debeb2893b0ea76eed959d6f65c0b281dbeb Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 21 Oct 2025 07:56:36 +0200 Subject: [PATCH 262/340] fix for suboptimal spatial CNG in MDCT-Stereo DTX --- lib_com/bitstream.c | 3 +- lib_com/fd_cng_com.c | 5 + lib_com/ivas_cnst.h | 5 + lib_com/ivas_prot.h | 2 + lib_com/ivas_rom_com.c | 6 + lib_com/ivas_rom_com.h | 3 + lib_com/options.h | 1 + lib_com/stat_com.h | 7 +- lib_dec/fd_cng_dec.c | 157 +++++++++++++++++++++++++- lib_dec/ivas_decision_matrix_dec.c | 4 + lib_dec/ivas_ism_metadata_dec.c | 22 ++++ lib_dec/ivas_ism_param_dec.c | 4 + lib_dec/ivas_stereo_mdct_stereo_dec.c | 4 + lib_enc/fd_cng_enc.c | 99 +++++++++++++++- lib_enc/stat_enc.h | 4 + 15 files changed, 322 insertions(+), 4 deletions(-) diff --git a/lib_com/bitstream.c b/lib_com/bitstream.c index 5309e6735d..bd8bf608de 100644 --- a/lib_com/bitstream.c +++ b/lib_com/bitstream.c @@ -5409,7 +5409,7 @@ void evs_dec_previewFrame( return; } - +#ifndef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG void dtx_read_padding_bits( DEC_CORE_HANDLE st, @@ -5425,3 +5425,4 @@ void dtx_read_padding_bits( return; } +#endif diff --git a/lib_com/fd_cng_com.c b/lib_com/fd_cng_com.c index 4f57ceaed9..608e2ed50e 100644 --- a/lib_com/fd_cng_com.c +++ b/lib_com/fd_cng_com.c @@ -34,6 +34,7 @@ EVS Codec 3GPP TS26.443 Nov 04, 2021. Version 12.14.0 / 13.10.0 / 14.6.0 / 15.4.0 / 16.3.0 ====================================================================================*/ +#include "ivas_cnst.h" #include #include #include "options.h" @@ -140,7 +141,11 @@ void initFdCngCom( hFdCngCom->msMinBufferPtr = 0; set_f( hFdCngCom->msAlphaCor, 0.3f, 2 ); +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + set_f( hFdCngCom->coherence, 0.5f, MDCT_ST_DTX_NUM_COHERENCE_BANDS ); +#else hFdCngCom->coherence = 0.5f; +#endif return; } diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index 6f679f0fd8..e16553e85a 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -850,6 +850,11 @@ enum fea_names #define MDCT_ST_PLC_FADEOUT_TO_ZERO_LEN 20 #define MDCT_ST_PLC_FADEOUT_DELAY_4_LSP_FADE 3 +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG +#define MDCT_ST_DTX_NUM_COHERENCE_BANDS 5 +#define MDCT_ST_DTX_FIRST_BAND_OFFSET 2 +#endif + typedef enum { NOISE_GEN_MODE_UNDEF = -1, EQUAL_CORES = 0, diff --git a/lib_com/ivas_prot.h b/lib_com/ivas_prot.h index eb75ab124d..bd168207f0 100755 --- a/lib_com/ivas_prot.h +++ b/lib_com/ivas_prot.h @@ -793,12 +793,14 @@ Word16 matrix_product_q30_fx( Word32 *Z_fx /* o : resulting matrix after the matrix multiplication Q30*/ ); +#ifndef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG void dtx_read_padding_bits( DEC_CORE_HANDLE st, const int16_t num_bits ); +#endif void ivas_apply_non_diegetic_panning( float *input_f, /* i : non-diegetic object */ float *output_f[], /* o : core-coder transport mono channel/stereo output */ diff --git a/lib_com/ivas_rom_com.c b/lib_com/ivas_rom_com.c index 237583bb58..75098e78f5 100644 --- a/lib_com/ivas_rom_com.c +++ b/lib_com/ivas_rom_com.c @@ -793,6 +793,12 @@ const float nf_tw_smoothing_coeffs[N_LTP_GAIN_MEMS] = 0.4f,0.2f,0.2f,0.2f }; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG +const int16_t mdct_stereo_dtx_coherence_bandlengths[MDCT_ST_DTX_NUM_COHERENCE_BANDS] = { + 6, /* 8 - MDCT_ST_DTX_FIRST_BAND_OFFSET */ 8, 16, 16, 80 +}; +#endif + /*----------------------------------------------------------------------------------* * Stereo DTX tables diff --git a/lib_com/ivas_rom_com.h b/lib_com/ivas_rom_com.h index c68e16ba9a..b90a30699f 100644 --- a/lib_com/ivas_rom_com.h +++ b/lib_com/ivas_rom_com.h @@ -138,6 +138,9 @@ extern const SpectrumWarping sw32000Hz[]; /* PsychLPC */ extern const MDCTStereoBands_config mdctStereoBands_32000_640[]; extern const float nf_tw_smoothing_coeffs[N_LTP_GAIN_MEMS]; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG +extern const int16_t mdct_stereo_dtx_coherence_bandlengths[MDCT_ST_DTX_NUM_COHERENCE_BANDS]; +#endif /*----------------------------------------------------------------------------------* * Stereo DTX ROM tables diff --git a/lib_com/options.h b/lib_com/options.h index 151c651bb8..b341621ded 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -182,6 +182,7 @@ #define NONBE_1399_1400_FIX_OBJ_EDIT_ISSUES /* Nokia: Fix for issues 1399: obj edit broken with MC/SBA output in VOIP, and 1400: negative energy estimate used for gaining. */ #define NONBE_1412_AVOID_ROUNDING_AZ_ELEV /* FhG: Avoid rounding when passing azimuth and elevation to efap_determine_gains() */ +#define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ /* ##################### End NON-BE switches ########################### */ diff --git a/lib_com/stat_com.h b/lib_com/stat_com.h index 4f459ef531..2eb9b5ee95 100644 --- a/lib_com/stat_com.h +++ b/lib_com/stat_com.h @@ -38,6 +38,7 @@ #ifndef STAT_COM_H #define STAT_COM_H +#include "ivas_cnst.h" #include #include "options.h" #include "typedef.h" @@ -404,7 +405,11 @@ typedef struct int16_t flag_noisy_speech; float likelihood_noisy_speech; - float coherence; /* inter-channel coherence of noise */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + float coherence[MDCT_ST_DTX_NUM_COHERENCE_BANDS]; /* inter-channel coherence of noise */ +#else + float coherence; /* inter-channel coherence of noise */ +#endif int16_t no_side_flag; /* indicates whether the side noise shape should be zeroed-out or not */ } FD_CNG_COM, *HANDLE_FD_CNG_COM; diff --git a/lib_dec/fd_cng_dec.c b/lib_dec/fd_cng_dec.c index 535af8b39c..c34364f090 100644 --- a/lib_dec/fd_cng_dec.c +++ b/lib_dec/fd_cng_dec.c @@ -34,6 +34,8 @@ EVS Codec 3GPP TS26.443 Nov 04, 2021. Version 12.14.0 / 13.10.0 / 14.6.0 / 15.4.0 / 16.3.0 ====================================================================================*/ +#include "ivas_cnst.h" +#include "ivas_rom_com.h" #include #include #include "options.h" @@ -422,7 +424,7 @@ void ApplyFdCng( /* set noise estimation inactive when we have bit errors, as no update with noise generated by corrupt frame (biterror) should be performed. */ if ( concealWholeFrame == 0 && ( timeDomainInput == NULL || - ( *timeDomainInput( -FLT_MAX ) && + ( *timeDomainInput < FLT_MAX && *timeDomainInput > ( -FLT_MAX ) && *( timeDomainInput + hFdCngCom->frameSize - 1 ) < FLT_MAX && *( timeDomainInput + hFdCngCom->frameSize - 1 ) > ( -FLT_MAX ) ) ) && ( ( ( ( st->element_mode != IVAS_CPE_TD && st->element_mode != IVAS_CPE_DFT && hFdCngDec->flag_dtx_mode ) || !st->VAD || ( st->ini_frame < 100 && st->is_ism_format ) ) && @@ -1100,8 +1102,13 @@ void generate_comfort_noise_dec( scale = 1.f; scaleCldfb = CLDFB_SCALING / hFdCngCom->scalingFactor; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + c1 = (float) sqrt( hFdCngCom->coherence[0] ); + c2 = (float) sqrt( 1 - hFdCngCom->coherence[0] ); +#else c1 = (float) sqrt( hFdCngCom->coherence ); c2 = (float) sqrt( 1 - hFdCngCom->coherence ); +#endif seed2 = &( hFdCngCom->seed2 ); if ( st->element_mode == IVAS_CPE_MDCT && st->idchan == 1 ) @@ -1114,7 +1121,11 @@ void generate_comfort_noise_dec( if ( hFdCngCom->startBand == 0 ) { +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#else if ( st->element_mode == IVAS_CPE_MDCT || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#endif { rand_gauss( &tmp1, seed ); rand_gauss( &tmp2, seed2 ); @@ -1136,10 +1147,102 @@ void generate_comfort_noise_dec( } ptr_i = ptr_r + 1; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) + { + int16_t band_len_accu; + + band_len_accu = 0; + i = 0; + for ( int16_t b = 0; b < MDCT_ST_DTX_NUM_COHERENCE_BANDS; b++ ) + { + band_len_accu += mdct_stereo_dtx_coherence_bandlengths[b]; + + /* First band needs to be shortened. The offset from encoder-side estimation is already in, so add it back here */ + if ( b == 0 ) + { + band_len_accu += MDCT_ST_DTX_FIRST_BAND_OFFSET - hFdCngCom->startBand; + } + + /* + * for last band, we need to keep going until the end of the fft section - if there is still any + * this way, the coherence value of the last band is used for eveyrthing above as well + */ + if ( b == MDCT_ST_DTX_NUM_COHERENCE_BANDS - 1 ) + { + band_len_accu = max( band_len_accu, hFdCngCom->stopFFTbin - hFdCngCom->startBand ); + } + + /* mixing values for coherence is now frequency-dependent */ + c1 = (float) sqrt( hFdCngCom->coherence[b] ); + c2 = (float) sqrt( 1 - hFdCngCom->coherence[b] ); + + for ( ; i < band_len_accu; i++ ) + { + float val_level; + val_level = (float) sqrt( ( scale * *ptr_level ) * 0.5f ); + + /* Real part in FFT bins */ + rand_gauss( &tmp1, seed ); + rand_gauss( &tmp2, seed2 ); + *ptr_r = tmp1 * c1 + tmp2 * c2; + ( *ptr_r ) *= val_level; + + /* Imaginary part in FFT bins */ + rand_gauss( &tmp1, seed ); + rand_gauss( &tmp2, seed2 ); + *ptr_i = tmp1 * c1 + tmp2 * c2; + ( *ptr_i ) *= val_level; + + /* advance all pointers together here */ + ptr_r += 2; + ptr_i += 2; + ptr_level++; + } + } + } + else + { + for ( ; ptr_level < cngNoiseLevel + hFdCngCom->stopFFTbin - hFdCngCom->startBand; ptr_level++ ) + { + /* Real part in FFT bins */ + if ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) + { + rand_gauss( &tmp1, seed ); + rand_gauss( &tmp2, seed2 ); + *ptr_r = tmp1 * c1 + tmp2 * c2; + } + else + { + rand_gauss( ptr_r, seed ); + } + ( *ptr_r ) *= (float) sqrt( ( scale * *ptr_level ) * 0.5f ); + ptr_r += 2; + + /* Imaginary part in FFT bins */ + if ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) + { + rand_gauss( &tmp1, seed ); + rand_gauss( &tmp2, seed2 ); + *ptr_i = tmp1 * c1 + tmp2 * c2; + } + else + { + rand_gauss( ptr_i, seed ); + } + ( *ptr_i ) *= (float) sqrt( ( scale * *ptr_level ) * 0.5f ); + ptr_i += 2; + } + } +#else for ( ; ptr_level < cngNoiseLevel + hFdCngCom->stopFFTbin - hFdCngCom->startBand; ptr_level++ ) { /* Real part in FFT bins */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#else if ( st->element_mode == IVAS_CPE_MDCT || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#endif { rand_gauss( &tmp1, seed ); rand_gauss( &tmp2, seed2 ); @@ -1153,7 +1256,11 @@ void generate_comfort_noise_dec( ptr_r += 2; /* Imaginary part in FFT bins */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#else if ( st->element_mode == IVAS_CPE_MDCT || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#endif { rand_gauss( &tmp1, seed ); rand_gauss( &tmp2, seed2 ); @@ -1166,6 +1273,7 @@ void generate_comfort_noise_dec( ( *ptr_i ) *= (float) sqrt( ( scale * *ptr_level ) * 0.5f ); ptr_i += 2; } +#endif /* Remaining FFT bins are set to zero */ set_f( fftBuffer + 2 * hFdCngCom->stopFFTbin, 0.0f, hFdCngCom->fftlen - 2 * hFdCngCom->stopFFTbin ); @@ -1224,6 +1332,13 @@ void generate_comfort_noise_dec( /* Generate Gaussian random noise in real and imaginary parts of the CLDFB bands Amplitudes are adjusted to the estimated noise level cngNoiseLevel in each band */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + /* + * Note: for the stereo DTX noise mixing, c1 and c2 at this point are set to the value calculated for the last band + * as all the coherence bands are in the FFT region, we do not need the special handling here + */ +#endif + if ( bufferReal != NULL && hFdCngCom->numCoreBands < hFdCngCom->regularStopBand ) { for ( j = hFdCngCom->numCoreBands; j < hFdCngCom->regularStopBand; j++ ) @@ -1231,7 +1346,11 @@ void generate_comfort_noise_dec( for ( i = 0; i < hFdCngCom->numSlots; i++ ) { /* Real part in CLDFB band */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#else if ( st->element_mode == IVAS_CPE_MDCT || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#endif { rand_gauss( &tmp1, seed ); rand_gauss( &tmp2, seed2 ); @@ -1244,7 +1363,11 @@ void generate_comfort_noise_dec( bufferReal[i][j] *= (float) sqrt( ( scaleCldfb * *ptr_level ) * 0.5f ); /* Imaginary part in CLDFB band */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + if ( ( st->element_mode == IVAS_CPE_MDCT && nchan_out != 1 ) || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#else if ( st->element_mode == IVAS_CPE_MDCT || ( st->element_mode == IVAS_SCE && st->cng_ism_flag ) ) +#endif { rand_gauss( &tmp1, seed ); rand_gauss( &tmp2, seed2 ); @@ -1361,8 +1484,14 @@ void generate_comfort_noise_dec_hf( { seed2 = &( hFdCngCom->seed2 ); +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + /* alwas use the value for the last band - frequency-wise we are here always above */ + c1 = (float) sqrt( hFdCngCom->coherence[MDCT_ST_DTX_NUM_COHERENCE_BANDS - 1] ); + c2 = (float) sqrt( 1 - hFdCngCom->coherence[MDCT_ST_DTX_NUM_COHERENCE_BANDS - 1] ); +#else c1 = (float) sqrt( hFdCngCom->coherence ); c2 = (float) sqrt( 1 - hFdCngCom->coherence ); +#endif } ptr_level = hFdCngCom->cngNoiseLevel + hFdCngCom->stopFFTbin - hFdCngCom->startBand; @@ -2156,7 +2285,25 @@ void FdCngDecodeMDCTStereoSID( msvq_dec( cdk_37bits_ivas, NULL, NULL, stages, N, FD_CNG_maxN_37bits, indices, 1, invTrfMatrix, ms_ptr[ch], NULL ); } +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + /* TODO: temporary hack, need to decide what to do with core-coder bitrate */ + int32_t tmp; + + tmp = sts[1]->total_brate; + sts[1]->total_brate = sts[1]->total_brate + 16 * FRAMES_PER_SEC; + /* read the four additional coherence values */ + for ( int16_t b = 1; b < MDCT_ST_DTX_NUM_COHERENCE_BANDS; b++ ) + { + uint16_t tmp_bit; + + tmp_bit = get_next_indice( sts[1], 4 ); + sts[0]->hFdCngDec->hFdCngCom->coherence[b] = (float) tmp_bit / 15.f; + sts[1]->hFdCngDec->hFdCngCom->coherence[b] = sts[0]->hFdCngDec->hFdCngCom->coherence[b]; + } + sts[1]->total_brate = tmp; +#else dtx_read_padding_bits( sts[1], ( IVAS_SID_5k2 - 4400 ) / FRAMES_PER_SEC ); +#endif if ( sts[0]->hFdCngDec->hFdCngCom->no_side_flag ) { @@ -2267,8 +2414,16 @@ void FdCngDecodeDiracMDCTStereoSID( lpc_from_spectrum( hFdCngCom, hFdCngCom->startBand, hFdCngCom->stopFFTbin, sts[ch]->preemph_fac ); } +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + sts[0]->hFdCngDec->hFdCngCom->coherence[i] = 0.0f; + sts[1]->hFdCngDec->hFdCngCom->coherence[i] = 0.0f; + } +#else sts[0]->hFdCngDec->hFdCngCom->coherence = 0.0f; sts[1]->hFdCngDec->hFdCngCom->coherence = 0.0f; +#endif if ( hCPE->nchan_out == 1 ) { diff --git a/lib_dec/ivas_decision_matrix_dec.c b/lib_dec/ivas_decision_matrix_dec.c index 47e8627865..da624d5a66 100644 --- a/lib_dec/ivas_decision_matrix_dec.c +++ b/lib_dec/ivas_decision_matrix_dec.c @@ -104,7 +104,11 @@ void ivas_decision_matrix_dec( else if ( st->total_brate == SID_2k40 && st->idchan == 1 && st->element_mode == IVAS_CPE_MDCT && st->cng_sba_flag == 0 ) { /* read channel coherence */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + st->hFdCngDec->hFdCngCom->coherence[0] = (float) get_next_indice( st, 4 ) / 15.f; +#else st->hFdCngDec->hFdCngCom->coherence = (float) get_next_indice( st, 4 ) / 15.f; +#endif /* read flag for no side noise shape */ st->hFdCngDec->hFdCngCom->no_side_flag = get_next_indice( st, 1 ); diff --git a/lib_dec/ivas_ism_metadata_dec.c b/lib_dec/ivas_ism_metadata_dec.c index 1a1e4161d4..d81b77de53 100644 --- a/lib_dec/ivas_ism_metadata_dec.c +++ b/lib_dec/ivas_ism_metadata_dec.c @@ -1057,18 +1057,40 @@ void ivas_ism_metadata_sid_dec( { if ( ch == *sce_id_dtx ) { +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = 1.0f; + } +#else hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence = 1.0f; +#endif continue; } idx = get_next_indice( st0, nBits_coh ); +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[0] = (float) ( idx ) / (float) ( ( 1 << nBits_coh ) - 1 ); + for ( int16_t i = 1; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[0]; + } +#else hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence = (float) ( idx ) / (float) ( ( 1 << nBits_coh ) - 1 ); +#endif } } if ( ism_mode == ISM_MODE_PARAM ) { +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + hSCE[*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = hSCE[!*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i]; + } +#else hSCE[*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence = hSCE[!*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence; +#endif } /*----------------------------------------------------------------* diff --git a/lib_dec/ivas_ism_param_dec.c b/lib_dec/ivas_ism_param_dec.c index 253f480c6a..0c51b5216b 100644 --- a/lib_dec/ivas_ism_param_dec.c +++ b/lib_dec/ivas_ism_param_dec.c @@ -1288,7 +1288,11 @@ void ivas_param_ism_params_to_masa_param_mapping( if ( st_ivas->hISMDTX.dtx_flag ) { float energy_ratio; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + energy_ratio = powf( st_ivas->hSCE[0]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[0], 2.0f ); +#else energy_ratio = powf( st_ivas->hSCE[0]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence, 2.0f ); +#endif hSpatParamRendCom->numSimultaneousDirections = 1; azimuth[0] = (int16_t) roundf( hParamIsmDec->azimuth_values[0] ); diff --git a/lib_dec/ivas_stereo_mdct_stereo_dec.c b/lib_dec/ivas_stereo_mdct_stereo_dec.c index f41ffb495d..3fd4b3184f 100644 --- a/lib_dec/ivas_stereo_mdct_stereo_dec.c +++ b/lib_dec/ivas_stereo_mdct_stereo_dec.c @@ -523,7 +523,11 @@ void synchonize_channels_mdct_sid( sts[1]->L_frame = sts[0]->L_frame; sts[1]->cng_type = sts[0]->cng_type; sts[1]->bwidth = sts[0]->bwidth; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + sts[0]->hFdCngDec->hFdCngCom->coherence[0] = sts[1]->hFdCngDec->hFdCngCom->coherence[0]; /* coherence is stored in sts[1] - see ivas_decision_matrix_dec() and FdCngDecodeMDCTStereoSID() */ +#else sts[0]->hFdCngDec->hFdCngCom->coherence = sts[1]->hFdCngDec->hFdCngCom->coherence; /* coherence is stored in sts[1] - see ivas_decision_matrix_dec() */ +#endif sts[0]->hFdCngDec->hFdCngCom->no_side_flag = sts[1]->hFdCngDec->hFdCngCom->no_side_flag; /* configure when there is a switching from DFT CNG to MDCT CNG */ diff --git a/lib_enc/fd_cng_enc.c b/lib_enc/fd_cng_enc.c index da13d3deb8..da49568c45 100644 --- a/lib_enc/fd_cng_enc.c +++ b/lib_enc/fd_cng_enc.c @@ -34,6 +34,8 @@ EVS Codec 3GPP TS26.443 Nov 04, 2021. Version 12.14.0 / 13.10.0 / 14.6.0 / 15.4.0 / 16.3.0 ====================================================================================*/ +#include "cnst.h" +#include "ivas_cnst.h" #include #include #include "options.h" @@ -43,6 +45,9 @@ #include #include "rom_enc.h" #include "rom_com.h" +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG +#include "ivas_rom_com.h" +#endif #include "prot.h" #include "ivas_prot.h" #include "stat_enc.h" @@ -154,7 +159,14 @@ void initFdCngEnc( set_f( hFdCngEnc->msLogPeriodog, 0.0f, NPART ); set_f( hFdCngEnc->msLogNoiseEst, 0.0f, NPART ); +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + set_f( hFdCngEnc->mem_coherence[i], EPSILON, 4 ); + } +#else set_f( hFdCngEnc->mem_coherence, EPSILON, 4 ); +#endif return; } @@ -891,7 +903,14 @@ void stereoFdCngCoherence( if ( last_element_mode != IVAS_CPE_MDCT ) { +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + { + set_f( sts[0]->hFdCngEnc->mem_coherence[i], EPSILON, 4 ); + } +#else set_f( sts[0]->hFdCngEnc->mem_coherence, EPSILON, 4 ); +#endif } if ( sts[0]->core_brate == -1 || sts[1]->core_brate == -1 ) @@ -941,8 +960,65 @@ void stereoFdCngCoherence( pt_fftL = fft_buff[0]; pt_fftR = fft_buff[1]; +#ifndef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG mem = sts[0]->hFdCngEnc->mem_coherence; +#endif + + /* only estimate coherence in inactive frames (or in the first 50 frames to build an initial value) */ + if ( !( sts[0]->ini_frame <= 50 || ( sts[0]->vad_flag == 0 && sts[1]->vad_flag == 0 ) ) ) + { + return; + } +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + for ( i_subfr = 0; i_subfr < 2; i_subfr++ ) + { + int16_t band_len_cum; + + band_len_cum = 0; + i = MDCT_ST_DTX_FIRST_BAND_OFFSET; + + for ( int16_t b = 0; b < MDCT_ST_DTX_NUM_COHERENCE_BANDS; b++ ) + { + band_len_cum += mdct_stereo_dtx_coherence_bandlengths[b]; + cr = ci = eL = eR = EPSILON; + mem = sts[0]->hFdCngEnc->mem_coherence[b]; + + /* for last band, we need to make adjustments to fit with the FFT buffer layout */ + /* do the calculations for fftbin 0 (not anymore as it is skipped - we only calculate coherence above the 2nd bin) and L_FFT/2 outside the loop - imaginary part is always zero there, but not part of the buffer */ + if ( b == MDCT_ST_DTX_NUM_COHERENCE_BANDS - 1 ) + { + /* this calculates values for fft bin at L_FFT/2 - no imaginary value there, not even part of the array */ + cr += pt_fftL[L_FFT / 2] * pt_fftR[L_FFT / 2]; + eL += pt_fftL[L_FFT / 2] * pt_fftL[L_FFT / 2]; + eR += pt_fftR[L_FFT / 2] * pt_fftR[L_FFT / 2]; + + /* skip last fft bin (fs/2 Hz) bin in the loop later */ + --band_len_cum; + } + + for ( ; i < band_len_cum; i++ ) + { + cr += pt_fftL[i] * pt_fftR[i] + pt_fftL[L_FFT - i] * pt_fftR[L_FFT - i]; + ci += -pt_fftL[i] * pt_fftR[L_FFT - i] + pt_fftR[i] * pt_fftL[L_FFT - i]; + eL += pt_fftL[i] * pt_fftL[i] + pt_fftL[L_FFT - i] * pt_fftL[L_FFT - i]; + eR += pt_fftR[i] * pt_fftR[i] + pt_fftR[L_FFT - i] * pt_fftR[L_FFT - i]; + } + + mem[0] = 0.95f * mem[0] + 0.05f * cr; + mem[1] = 0.95f * mem[1] + 0.05f * ci; + mem[2] = 0.95f * mem[2] + 0.05f * eL; + mem[3] = 0.95f * mem[3] + 0.05f * eR; + + /* could be done outside the loop, as the second assignment overwrites the first, but this would mean a second loop over bands */ + sts[0]->hFdCngEnc->hFdCngCom->coherence[b] = sqrtf( ( mem[0] * mem[0] + mem[1] * mem[1] ) / ( mem[2] * mem[3] ) ); + } + + pt_fftL += L_FFT; + pt_fftR += L_FFT; + } + +#else for ( i_subfr = 0; i_subfr < 2; i_subfr++ ) { cr = ci = eL = eR = EPSILON; @@ -970,8 +1046,10 @@ void stereoFdCngCoherence( pt_fftL += L_FFT; pt_fftR += L_FFT; } - +#endif +#ifndef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG sts[0]->hFdCngEnc->hFdCngCom->coherence = sqrtf( ( mem[0] * mem[0] + mem[1] * mem[1] ) / ( mem[2] * mem[3] ) ); +#endif return; } @@ -1172,7 +1250,11 @@ void FdCngEncodeMDCTStereoSID( } /* quantize channel coherence */ +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + coh_idx = (int16_t) floor( sts[0]->hFdCngEnc->hFdCngCom->coherence[0] * 15.f + 0.5f ); +#else coh_idx = (int16_t) floor( sts[0]->hFdCngEnc->hFdCngCom->coherence * 15.f + 0.5f ); +#endif coh_idx = max( 0, min( coh_idx, 15 ) ); /* ---- Write SID bitstream ---- */ @@ -1206,8 +1288,18 @@ void FdCngEncodeMDCTStereoSID( push_indice( sts[ch]->hBstr, IND_ENERGY, gain_idx[ch], 7 ); } +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + /* write the four additional coherence values */ + for ( int16_t b = 1; b < MDCT_ST_DTX_NUM_COHERENCE_BANDS; b++ ) + { + coh_idx = (int16_t) floor( sts[0]->hFdCngEnc->hFdCngCom->coherence[b] * 15.f + 0.5f ); + coh_idx = max( 0, min( coh_idx, 15 ) ); + push_indice( sts[1]->hBstr, IND_ENERGY, coh_idx, 4 ); + } +#else /* pad with zeros to reach common SID frame size */ push_indice( sts[1]->hBstr, IND_ENERGY, 0, ( IVAS_SID_5k2 - 4400 ) / FRAMES_PER_SEC ); +#endif return; @@ -1348,8 +1440,13 @@ void FdCngEncodeDiracMDCTStereoSID( sts[ch]->hDtxEnc->last_CNG_L_frame = sts[ch]->L_frame; } +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + sts[0]->hFdCngEnc->hFdCngCom->coherence[0] = 0.0f; + sts[1]->hFdCngEnc->hFdCngCom->coherence[0] = 0.0f; +#else sts[0]->hFdCngEnc->hFdCngCom->coherence = 0.0f; sts[1]->hFdCngEnc->hFdCngCom->coherence = 0.0f; +#endif /* ---- Write SID bitstream ---- */ diff --git a/lib_enc/stat_enc.h b/lib_enc/stat_enc.h index 19a87b6a1d..4d6fe1134b 100755 --- a/lib_enc/stat_enc.h +++ b/lib_enc/stat_enc.h @@ -359,7 +359,11 @@ typedef struct fd_cng_enc_structure int16_t nFFTpartDec; int16_t partDec[NPART]; +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + float mem_coherence[MDCT_ST_DTX_NUM_COHERENCE_BANDS][4]; +#else float mem_coherence[4]; +#endif } FD_CNG_ENC, *HANDLE_FD_CNG_ENC; -- GitLab From dd07d21049744650e2fa10aaec632203aa420b19 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 21 Oct 2025 08:00:27 +0200 Subject: [PATCH 263/340] apply clang-format --- lib_dec/fd_cng_dec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_dec/fd_cng_dec.c b/lib_dec/fd_cng_dec.c index c34364f090..e6435632b1 100644 --- a/lib_dec/fd_cng_dec.c +++ b/lib_dec/fd_cng_dec.c @@ -424,7 +424,7 @@ void ApplyFdCng( /* set noise estimation inactive when we have bit errors, as no update with noise generated by corrupt frame (biterror) should be performed. */ if ( concealWholeFrame == 0 && ( timeDomainInput == NULL || - ( *timeDomainInput < FLT_MAX && *timeDomainInput > ( -FLT_MAX ) && + ( *timeDomainInput( -FLT_MAX ) && *( timeDomainInput + hFdCngCom->frameSize - 1 ) < FLT_MAX && *( timeDomainInput + hFdCngCom->frameSize - 1 ) > ( -FLT_MAX ) ) ) && ( ( ( ( st->element_mode != IVAS_CPE_TD && st->element_mode != IVAS_CPE_DFT && hFdCngDec->flag_dtx_mode ) || !st->VAD || ( st->ini_frame < 100 && st->is_ism_format ) ) && -- GitLab From 106b0e638b46b4ce34757d4635c848dfed0214c3 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 21 Oct 2025 08:07:28 +0200 Subject: [PATCH 264/340] fix windows build --- lib_dec/fd_cng_dec.c | 4 ++-- lib_dec/ivas_ism_metadata_dec.c | 6 +++--- lib_enc/fd_cng_enc.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib_dec/fd_cng_dec.c b/lib_dec/fd_cng_dec.c index e6435632b1..a34b9f36c6 100644 --- a/lib_dec/fd_cng_dec.c +++ b/lib_dec/fd_cng_dec.c @@ -424,7 +424,7 @@ void ApplyFdCng( /* set noise estimation inactive when we have bit errors, as no update with noise generated by corrupt frame (biterror) should be performed. */ if ( concealWholeFrame == 0 && ( timeDomainInput == NULL || - ( *timeDomainInput( -FLT_MAX ) && + ( *timeDomainInput < FLT_MAX && *timeDomainInput > ( -FLT_MAX ) && *( timeDomainInput + hFdCngCom->frameSize - 1 ) < FLT_MAX && *( timeDomainInput + hFdCngCom->frameSize - 1 ) > ( -FLT_MAX ) ) ) && ( ( ( ( st->element_mode != IVAS_CPE_TD && st->element_mode != IVAS_CPE_DFT && hFdCngDec->flag_dtx_mode ) || !st->VAD || ( st->ini_frame < 100 && st->is_ism_format ) ) && @@ -2415,7 +2415,7 @@ void FdCngDecodeDiracMDCTStereoSID( lpc_from_spectrum( hFdCngCom, hFdCngCom->startBand, hFdCngCom->stopFFTbin, sts[ch]->preemph_fac ); } #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG - for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + for ( i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) { sts[0]->hFdCngDec->hFdCngCom->coherence[i] = 0.0f; sts[1]->hFdCngDec->hFdCngCom->coherence[i] = 0.0f; diff --git a/lib_dec/ivas_ism_metadata_dec.c b/lib_dec/ivas_ism_metadata_dec.c index d81b77de53..5f65826b5b 100644 --- a/lib_dec/ivas_ism_metadata_dec.c +++ b/lib_dec/ivas_ism_metadata_dec.c @@ -1058,7 +1058,7 @@ void ivas_ism_metadata_sid_dec( if ( ch == *sce_id_dtx ) { #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG - for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + for ( i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) { hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = 1.0f; } @@ -1071,7 +1071,7 @@ void ivas_ism_metadata_sid_dec( idx = get_next_indice( st0, nBits_coh ); #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[0] = (float) ( idx ) / (float) ( ( 1 << nBits_coh ) - 1 ); - for ( int16_t i = 1; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + for ( i = 1; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) { hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = hSCE[ch]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[0]; } @@ -1084,7 +1084,7 @@ void ivas_ism_metadata_sid_dec( if ( ism_mode == ISM_MODE_PARAM ) { #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG - for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + for ( i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) { hSCE[*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i] = hSCE[!*sce_id_dtx]->hCoreCoder[0]->hFdCngDec->hFdCngCom->coherence[i]; } diff --git a/lib_enc/fd_cng_enc.c b/lib_enc/fd_cng_enc.c index da49568c45..84282281a4 100644 --- a/lib_enc/fd_cng_enc.c +++ b/lib_enc/fd_cng_enc.c @@ -904,7 +904,7 @@ void stereoFdCngCoherence( if ( last_element_mode != IVAS_CPE_MDCT ) { #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG - for ( int16_t i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) + for ( i = 0; i < MDCT_ST_DTX_NUM_COHERENCE_BANDS; i++ ) { set_f( sts[0]->hFdCngEnc->mem_coherence[i], EPSILON, 4 ); } -- GitLab From 4b06cb88b0f5870e36a537a69a7b1de12ac3741f Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 21 Oct 2025 08:09:40 +0200 Subject: [PATCH 265/340] apply clang-format AGAIN --- lib_dec/fd_cng_dec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_dec/fd_cng_dec.c b/lib_dec/fd_cng_dec.c index a34b9f36c6..1a6d4abb50 100644 --- a/lib_dec/fd_cng_dec.c +++ b/lib_dec/fd_cng_dec.c @@ -424,7 +424,7 @@ void ApplyFdCng( /* set noise estimation inactive when we have bit errors, as no update with noise generated by corrupt frame (biterror) should be performed. */ if ( concealWholeFrame == 0 && ( timeDomainInput == NULL || - ( *timeDomainInput < FLT_MAX && *timeDomainInput > ( -FLT_MAX ) && + ( *timeDomainInput( -FLT_MAX ) && *( timeDomainInput + hFdCngCom->frameSize - 1 ) < FLT_MAX && *( timeDomainInput + hFdCngCom->frameSize - 1 ) > ( -FLT_MAX ) ) ) && ( ( ( ( st->element_mode != IVAS_CPE_TD && st->element_mode != IVAS_CPE_DFT && hFdCngDec->flag_dtx_mode ) || !st->VAD || ( st->ini_frame < 100 && st->is_ism_format ) ) && -- GitLab From c44e3a6d6d0661bd3d0af9cf337ebfca76be9d43 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 08:24:20 +0200 Subject: [PATCH 266/340] Remove duplicate encoder tests. Revert to old renderer test --- .gitlab-ci.yml | 2 +- scripts/parse_commands.py | 8 + tests/renderer_short/README.md | 44 + tests/renderer_short/__init__.py | 31 + tests/renderer_short/compare_audio.py | 105 ++ tests/renderer_short/constants.py | 421 ++++++++ tests/renderer_short/test_renderer.py | 1285 +++++++++++++++++++++++++ tests/renderer_short/utils.py | 786 +++++++++++++++ 8 files changed, 2681 insertions(+), 1 deletion(-) create mode 100644 tests/renderer_short/README.md create mode 100644 tests/renderer_short/__init__.py create mode 100644 tests/renderer_short/compare_audio.py create mode 100644 tests/renderer_short/constants.py create mode 100644 tests/renderer_short/test_renderer.py create mode 100644 tests/renderer_short/utils.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ce484f4950..a266ba9af4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1384,7 +1384,7 @@ ivas-conformance-linux: # Reference creation - python3 scripts/prepare_combined_format_inputs.py - - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py" + - TEST_SET="tests/codec_be_on_mr_nonselection tests/renderer_short/test_renderer.py tests/split_rendering/test_split_rendering.py" - python3 -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html - python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index d891f75caf..91c352d306 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -70,6 +70,14 @@ if __name__ == '__main__': cmds_rend.sort() cmds_isar_post_rend.sort() + # Remove duplicates from cmds_enc -- some decoder tests use same encoder options + i = 0 + while i + 1 < len(cmds_enc): + if " ".join(cmds_enc[i].split()[:-1]) == " ".join(cmds_enc[i+1].split()[:-1]): + del cmds_enc[i+1] + else: + i = i + 1 + with open(txt_file.replace('.','_enc.'),'w', newline='\n') as outfile: with open('scripts/enc_header.txt','r') as header: outfile.write(header.read()) diff --git a/tests/renderer_short/README.md b/tests/renderer_short/README.md new file mode 100644 index 0000000000..713821c5d3 --- /dev/null +++ b/tests/renderer_short/README.md @@ -0,0 +1,44 @@ +======================================== +THIS FOLDER WILL NOT BE PART OF DELIVERY +======================================== + +# External Renderer Tests + +See also the [contribution page](https://forge.3gpp.org/rep/ivas-codec-pc/ivas-codec/-/wikis/Contributions/2-external-renderer) for related presentations. + +### Run tests with: + +## Smoke test: + +```bash +python3 -m pytest -q -n auto tests/renderer/test_renderer.py +``` + +## Comparison test: + +```bash +python3 -m pytest -q -n auto tests/renderer/test_renderer.py --create_ref # requires IVAS_rend_ref in root! +python3 -m pytest -q -n auto tests/renderer/test_renderer.py --create_cut +``` + +### Important flags (see [pytest docs](https://docs.pytest.org/en/7.2.x/) for more information): + +- `-k` flag can filter test cases, e.g. `-k "test_ism_binaural_static"` +- `-rA` reports ALL (pass, xpass, xfail, fail) instead of the default behaviour of reporting only failed tests\ + this option will also report captured logs, **required for obtaining the commandline of testcases that pass or xfail** +- `--last-failed` re-runs only the cases that failed in the last test run +- `--collect-only` is useful when adding new testcases to check if argument parametrization is working correctly + +### Directory tree + +``` +. +├── compare_audio.py -> Python implementation of CompAudio, used for comparisons in tests +├── conftest.py -> Pytest configuration (enable commandline argument ingestion) +├── constants.py -> Important paths, formats, metadata files and commandline templates +├── cut -> Default location for output files for test conditions +├── data -> Input test vectors +├── ref -> Default location for output files for reference conditions +├── test_renderer.py -> Runs the renderer for all modes +└── utils.py -> Wrapper functions for executables for use in testcases +``` diff --git a/tests/renderer_short/__init__.py b/tests/renderer_short/__init__.py new file mode 100644 index 0000000000..2655594043 --- /dev/null +++ b/tests/renderer_short/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. +""" diff --git a/tests/renderer_short/compare_audio.py b/tests/renderer_short/compare_audio.py new file mode 100644 index 0000000000..49bfa86c06 --- /dev/null +++ b/tests/renderer_short/compare_audio.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +""" + (C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository. All Rights Reserved. + + This software is protected by copyright law and by international treaties. + The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, + Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., + Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, + Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other + contributors to this repository retain full ownership rights in their respective contributions in + the software. This notice grants no license of any kind, including but not limited to patent + license, nor is any license granted by implication, estoppel or otherwise. + + Contributors are required to enter into the IVAS codec Public Collaboration agreement before making + contributions. + + This software is provided "AS IS", without any express or implied warranties. The software is in the + development stage. It is intended exclusively for experts who have experience with such software and + solely for the purpose of inspection. All implied warranties of non-infringement, merchantability + and fitness for a particular purpose are hereby disclaimed and excluded. + + Any dispute, controversy or claim arising under or in relation to providing this software shall be + submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in + accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and + the United Nations Convention on Contracts on the International Sales of Goods. +""" + +import sys +import warnings +from typing import Tuple + +import numpy as np + +from .constants import SCRIPTS_DIR + +sys.path.append(str(SCRIPTS_DIR)) +from pyaudio3dtools.audioarray import getdelay + + +def compare_audio_arrays( + left: np.ndarray, left_fs: int, right: np.ndarray, right_fs: int +) -> Tuple[float, float]: + if left_fs != right_fs: + return ValueError(f"Differing samplerates: {left_fs} vs {right_fs}!") + + if left.shape[1] != right.shape[1]: + cmp_ch = min(left.shape[1], right.shape[1]) + warnings.warn( + f"Differing number of channels: {left.shape[1]} vs {right.shape[1]}! Comparing first {cmp_ch} channel(s)", + category=RuntimeWarning, + ) + left = left[:, :cmp_ch] + right = right[:, :cmp_ch] + + if left.shape[0] != right.shape[0]: + cmp_smp = min(left.shape[0], right.shape[0]) + warnings.warn( + f"Warning - different durations: {left.shape[0] / left_fs:.2f}s vs {right.shape[0] / right_fs:.2f}s! Comparing first {cmp_smp / left_fs : .2f} sample(s)", + category=RuntimeWarning, + ) + left = left[:cmp_smp, :] + right = right[:cmp_smp, :] + + if not np.array_equal(left, right): + delay = getdelay(left, right) + delay_abs = np.abs(delay) + # getdelay can return large values if signals are quite different + # limit any delay compensation to 20 ms + if delay != 0 and (delay_abs < left_fs / 50): + warnings.warn( + f"File B is delayed by {delay} samples ({delay*1000 / left_fs : .2f}ms)!", + category=RuntimeWarning, + ) + + # shift array + left = np.roll(left, delay, axis=0) + + # zero shifted out samples + if delay < 0: + left[-np.abs(delay) :, :] = 0 + elif delay > 0: + left[: np.abs(delay), :] = 0 + """ + http://www-mmsp.ece.mcgill.ca/Documents/Software/Packages/AFsp/AFsp/CompAudio.html + """ + num = np.sum(left * right) + den = np.sqrt(np.sum(left**2) * np.sum(right**2)) + if den > 0: + r = num / den + else: + r = np.inf + snr = 10 * np.log10(1 / (1 - (r**2))) + gain_b = num / np.sum(right**2) + max_diff = np.abs(np.max(left - right)) + else: + snr = np.inf + gain_b = 1 + max_diff = 0 + + return snr, gain_b, max_diff diff --git a/tests/renderer_short/constants.py b/tests/renderer_short/constants.py new file mode 100644 index 0000000000..766e64722b --- /dev/null +++ b/tests/renderer_short/constants.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 + +""" +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +from pathlib import Path +import platform + +""" Set up paths """ +TESTS_DIR = Path(__file__).parent +SCRIPTS_DIR = TESTS_DIR.parents[1].joinpath("scripts").resolve() +TEST_VECTOR_DIR = SCRIPTS_DIR.joinpath("testv") + +OUTPUT_PATH_REF = TESTS_DIR.joinpath("ref") +OUTPUT_PATH_CUT = TESTS_DIR.joinpath("cut") + +CUSTOM_LAYOUT_DIR = SCRIPTS_DIR.joinpath("ls_layouts") +HR_TRAJECTORY_DIR = SCRIPTS_DIR.joinpath("trajectories") +TESTV_DIR = SCRIPTS_DIR.joinpath("testv") +LTV_DIR = TESTV_DIR + +BIN_SUFFIX_MERGETARGET = "_ref" + +if platform.system() == "Windows": + EXE_SUFFIX = ".exe" +elif platform.system() in ["Linux", "Darwin"]: + EXE_SUFFIX = "" +else: + assert False, f"Unsupported platform {platform.system()}" + +""" Renderer commandline template """ +RENDERER_CMD = [ + str(TESTS_DIR.parent.parent.joinpath("IVAS_rend")), + "-i", + "", # 2 -> input file + "-if", + "", # 4 -> input format + "-o", + "/dev/null", # 6 -> output file + "-of", + "", # 8 -> output format + "-fs", + "48", # 10 -> input fs + "-no_delay_cmp", + # "-ndl", + "-q", +] + + +""" Format to file mappings """ +NCHAN_TO_FILE = { + 1: TEST_VECTOR_DIR.joinpath("spectral_test_1ch_48kHz.wav"), + 2: TEST_VECTOR_DIR.joinpath("spectral_test_2ch_48kHz.wav"), + 3: TEST_VECTOR_DIR.joinpath("spectral_test_3ch_48kHz.wav"), + 4: TEST_VECTOR_DIR.joinpath("spectral_test_4ch_48kHz.wav"), + 5: TEST_VECTOR_DIR.joinpath("spectral_test_5ch_48kHz.wav"), + 6: TEST_VECTOR_DIR.joinpath("spectral_test_6ch_48kHz.wav"), + 7: TEST_VECTOR_DIR.joinpath("spectral_test_7ch_48kHz.wav"), + 8: TEST_VECTOR_DIR.joinpath("spectral_test_8ch_48kHz.wav"), + 9: TEST_VECTOR_DIR.joinpath("spectral_test_9ch_48kHz.wav"), + 10: TEST_VECTOR_DIR.joinpath("spectral_test_10ch_48kHz.wav"), + 11: TEST_VECTOR_DIR.joinpath("spectral_test_11ch_48kHz.wav"), + 12: TEST_VECTOR_DIR.joinpath("spectral_test_12ch_48kHz.wav"), + 13: TEST_VECTOR_DIR.joinpath("spectral_test_13ch_48kHz.wav"), + 15: TEST_VECTOR_DIR.joinpath("spectral_test_15ch_48kHz.wav"), + 16: TEST_VECTOR_DIR.joinpath("spectral_test_16ch_48kHz.wav"), + 17: TEST_VECTOR_DIR.joinpath("spectral_test_17ch_48kHz.wav"), + 18: TEST_VECTOR_DIR.joinpath("spectral_test_18ch_48kHz.wav"), + 19: TEST_VECTOR_DIR.joinpath("spectral_test_19ch_48kHz.wav"), + 20: TEST_VECTOR_DIR.joinpath("spectral_test_20ch_48kHz.wav"), +} + +FORMAT_TO_FILE_SMOKETEST = { + "MONO": NCHAN_TO_FILE[1], + "STEREO": NCHAN_TO_FILE[2], + "5_1": NCHAN_TO_FILE[6], + "7_1": NCHAN_TO_FILE[8], + "5_1_2": NCHAN_TO_FILE[8], + "5_1_4": NCHAN_TO_FILE[10], + "7_1_4": NCHAN_TO_FILE[12], + "FOA": NCHAN_TO_FILE[4], + "HOA2": NCHAN_TO_FILE[9], + "HOA3": NCHAN_TO_FILE[16], + "ISM1": NCHAN_TO_FILE[1], + "ISM2": NCHAN_TO_FILE[2], + "ISM3": NCHAN_TO_FILE[3], + "ISM4": NCHAN_TO_FILE[4], + "NDP_ISM4": NCHAN_TO_FILE[4], + "MASA1": NCHAN_TO_FILE[1], + "MASA2": NCHAN_TO_FILE[2], + "OMASA_1_1": NCHAN_TO_FILE[2], + "OMASA_1_2": NCHAN_TO_FILE[3], + "OMASA_1_3": NCHAN_TO_FILE[4], + "OMASA_1_4": NCHAN_TO_FILE[5], + "OMASA_2_1": NCHAN_TO_FILE[3], + "OMASA_2_2": NCHAN_TO_FILE[4], + "OMASA_2_3": NCHAN_TO_FILE[5], + "OMASA_2_4": NCHAN_TO_FILE[6], + "OSBA_1_1": NCHAN_TO_FILE[5], + "OSBA_2_1": NCHAN_TO_FILE[6], + "OSBA_3_1": NCHAN_TO_FILE[7], + "OSBA_4_1": NCHAN_TO_FILE[8], + "OSBA_1_2": NCHAN_TO_FILE[10], + "OSBA_2_2": NCHAN_TO_FILE[11], + "OSBA_3_2": NCHAN_TO_FILE[12], + "OSBA_4_2": NCHAN_TO_FILE[13], + "OSBA_1_3": NCHAN_TO_FILE[17], + "OSBA_2_3": NCHAN_TO_FILE[18], + "OSBA_3_3": NCHAN_TO_FILE[19], + "OSBA_4_3": NCHAN_TO_FILE[20], + "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "16ch_8+4+4": NCHAN_TO_FILE[16], + "4d4": NCHAN_TO_FILE[8], + "t_design_4": NCHAN_TO_FILE[12], +} + +FORMAT_TO_FILE_COMPARETEST = { + "MONO": TESTV_DIR.joinpath("stv48c.wav"), + "STEREO": TESTV_DIR.joinpath("stvST48c.wav"), + "5_1": TESTV_DIR.joinpath("stv51MC48c.wav"), + "7_1": TESTV_DIR.joinpath("stv71MC48c.wav"), + "5_1_2": TESTV_DIR.joinpath("stv512MC48c.wav"), + "5_1_4": TESTV_DIR.joinpath("stv514MC48c.wav"), + "7_1_4": TESTV_DIR.joinpath("stv714MC48c.wav"), + "FOA": TESTV_DIR.joinpath("stvFOA48c.wav"), + "HOA2": TESTV_DIR.joinpath("stv2OA48c.wav"), + "HOA3": TESTV_DIR.joinpath("stv3OA48c.wav"), + "ISM1": TESTV_DIR.joinpath("stv1ISM48s.wav"), + "ISM2": TESTV_DIR.joinpath("stv2ISM48s.wav"), + "ISM3": TESTV_DIR.joinpath("stv3ISM48s.wav"), + "ISM4": TESTV_DIR.joinpath("stv4ISM48s.wav"), + "MASA1": TESTV_DIR.joinpath("stv1MASA1TC48c.wav"), + "MASA2": TESTV_DIR.joinpath("stv2MASA2TC48c.wav"), + "OMASA_1_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.wav"), + "OMASA_1_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.wav"), + "OMASA_1_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.wav"), + "OMASA_1_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.wav"), + "OMASA_2_1": TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.wav"), + "OMASA_2_2": TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.wav"), + "OMASA_2_3": TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.wav"), + "OMASA_2_4": TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.wav"), + "OSBA_1_1": TESTV_DIR.joinpath("stvOSBA_1ISM_FOA48c.wav"), + "OSBA_1_2": TESTV_DIR.joinpath("stvOSBA_1ISM_2OA48c.wav"), + "OSBA_1_3": TESTV_DIR.joinpath("stvOSBA_1ISM_3OA48c.wav"), + "OSBA_2_1": TESTV_DIR.joinpath("stvOSBA_2ISM_FOA48c.wav"), + "OSBA_2_2": TESTV_DIR.joinpath("stvOSBA_2ISM_2OA48c.wav"), + "OSBA_2_3": TESTV_DIR.joinpath("stvOSBA_2ISM_3OA48c.wav"), + "OSBA_3_1": TESTV_DIR.joinpath("stvOSBA_3ISM_FOA48c.wav"), + "OSBA_3_2": TESTV_DIR.joinpath("stvOSBA_3ISM_2OA48c.wav"), + "OSBA_3_3": TESTV_DIR.joinpath("stvOSBA_3ISM_3OA48c.wav"), + "OSBA_4_1": TESTV_DIR.joinpath("stvOSBA_4ISM_FOA48c.wav"), + "OSBA_4_2": TESTV_DIR.joinpath("stvOSBA_4ISM_2OA48c.wav"), + "OSBA_4_3": TESTV_DIR.joinpath("stvOSBA_4ISM_3OA48c.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "16ch_8+4+4": TESTV_DIR.joinpath("stv3OA48c.wav"), + "4d4": TESTV_DIR.joinpath("stv71MC48c.wav"), + "t_design_4": TESTV_DIR.joinpath("stv714MC48c.wav"), +} + +FORMAT_TO_FILE_LTV = { + "MONO": LTV_DIR.joinpath("ltv48_MONO.wav"), + "STEREO": LTV_DIR.joinpath("ltv48_STEREO.wav"), + "5_1": LTV_DIR.joinpath("ltv48_MC51.wav"), + "7_1": LTV_DIR.joinpath("ltv48_MC71.wav"), + "5_1_2": LTV_DIR.joinpath("ltv48_MC512.wav"), + "5_1_4": LTV_DIR.joinpath("ltv48_MC514.wav"), + "7_1_4": LTV_DIR.joinpath("ltv48_MC714.wav"), + "FOA": LTV_DIR.joinpath("ltv48_FOA.wav"), + "HOA2": LTV_DIR.joinpath("ltv48_HOA2.wav"), + "HOA3": LTV_DIR.joinpath("ltv48_HOA3.wav"), + "ISM1": LTV_DIR.joinpath("ltv48_1ISM.wav"), + "ISM2": LTV_DIR.joinpath("ltv48_2ISM.wav"), + "ISM3": LTV_DIR.joinpath("ltv48_3ISM.wav"), + "ISM4": LTV_DIR.joinpath("ltv48_4ISM.wav"), + "MASA1": LTV_DIR.joinpath("ltv48_MASA1TC.wav"), + "MASA2": LTV_DIR.joinpath("ltv48_MASA2TC.wav"), + "OMASA_1_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.wav"), + "OMASA_1_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.wav"), + "OMASA_1_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.wav"), + "OMASA_1_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.wav"), + "OMASA_2_1": LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.wav"), + "OMASA_2_2": LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.wav"), + "OMASA_2_3": LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.wav"), + "OMASA_2_4": LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.wav"), + "OSBA_1_1": LTV_DIR.joinpath("ltv48_OSBA_1ISM_FOA.wav"), + "OSBA_1_2": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA2.wav"), + "OSBA_1_3": LTV_DIR.joinpath("ltv48_OSBA_1ISM_HOA3.wav"), + "OSBA_2_1": LTV_DIR.joinpath("ltv48_OSBA_2ISM_FOA.wav"), + "OSBA_2_2": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA2.wav"), + "OSBA_2_3": LTV_DIR.joinpath("ltv48_OSBA_2ISM_HOA3.wav"), + "OSBA_3_1": LTV_DIR.joinpath("ltv48_OSBA_3ISM_FOA.wav"), + "OSBA_3_2": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA2.wav"), + "OSBA_3_3": LTV_DIR.joinpath("ltv48_OSBA_3ISM_HOA3.wav"), + "OSBA_4_1": LTV_DIR.joinpath("ltv48_OSBA_4ISM_FOA.wav"), + "OSBA_4_2": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA2.wav"), + "OSBA_4_3": LTV_DIR.joinpath("ltv48_OSBA_4ISM_HOA3.wav"), + "META": TEST_VECTOR_DIR.joinpath("mixed_scene.txt"), + "16ch_8+4+4": LTV_DIR.joinpath("ltv48_HOA3.wav"), + "4d4": LTV_DIR.joinpath("ltv48_MC71.wav"), + "t_design_4": LTV_DIR.joinpath("ltv48_MC714.wav"), +} + +FORMAT_TO_METADATA_FILES = { + "ISM1": [str(TESTV_DIR.joinpath("stvISM1.csv"))], + "ISM2": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + ], + "ISM3": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + ], + "ISM4": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvISM4.csv")), + ], + "NDP_ISM4": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2_non-diegetic-pan.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvISM4.csv")), + ], + "MASA1": [str(TESTV_DIR.joinpath("stv1MASA1TC48c.met"))], + "MASA2": [str(TESTV_DIR.joinpath("stv2MASA2TC48c.met"))], + "OMASA_1_1": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA1TC48c.met")), + ], + "OMASA_1_2": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA1TC48c.met")), + ], + "OMASA_1_3": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA1TC48c.met")), + ], + "OMASA_1_4": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvISM4.csv")), + str(TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA1TC48c.met")), + ], + "OMASA_2_1": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvOMASA_1ISM_1MASA2TC48c.met")), + ], + "OMASA_2_2": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvOMASA_2ISM_2MASA2TC48c.met")), + ], + "OMASA_2_3": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvOMASA_3ISM_1MASA2TC48c.met")), + ], + "OMASA_2_4": [ + str(TESTV_DIR.joinpath("stvISM1.csv")), + str(TESTV_DIR.joinpath("stvISM2.csv")), + str(TESTV_DIR.joinpath("stvISM3.csv")), + str(TESTV_DIR.joinpath("stvISM4.csv")), + str(TESTV_DIR.joinpath("stvOMASA_4ISM_2MASA2TC48c.met")), + ], +} + +FORMAT_TO_METADATA_FILES_LTV = { + "ISM1": [str(LTV_DIR.joinpath("ltvISM1.csv"))], + "ISM2": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + ], + "ISM3": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + ], + "ISM4": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltvISM4.csv")), + ], + "NDP_ISM4": [ # Should not be needed, because it is included in all ISM metadata files. + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltvISM4.csv")), + ], + "MASA1": [str(LTV_DIR.joinpath("ltv48_MASA1TC.met"))], + "MASA2": [str(LTV_DIR.joinpath("ltv48_MASA2TC.met"))], + "OMASA_1_1": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_1TC.met")), + ], + "OMASA_1_2": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_1TC.met")), + ], + "OMASA_1_3": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_1TC.met")), + ], + "OMASA_1_4": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltvISM4.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_4ISM_1TC.met")), + ], + "OMASA_2_1": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_1ISM_2TC.met")), + ], + "OMASA_2_2": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_2ISM_2TC.met")), + ], + "OMASA_2_3": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_3ISM_2TC.met")), + ], + "OMASA_2_4": [ + str(LTV_DIR.joinpath("ltvISM1.csv")), + str(LTV_DIR.joinpath("ltvISM2.csv")), + str(LTV_DIR.joinpath("ltvISM3.csv")), + str(LTV_DIR.joinpath("ltvISM4.csv")), + str(LTV_DIR.joinpath("ltv48_OMASA_4ISM_2TC.met")), + ], +} + +""" Input formats """ +INPUT_FORMATS_AMBI = ["FOA", "HOA2", "HOA3"] +INPUT_FORMATS_MC = ["MONO", "STEREO", "5_1", "5_1_2", "5_1_4", "7_1", "7_1_4"] +INPUT_FORMATS_ISM = ["ISM1", "ISM2", "ISM3", "ISM4"] +INPUT_FORMATS_MASA = ["MASA1", "MASA2"] + +""" Non binaural / parametric output formats """ +OUTPUT_FORMATS = [ + "MONO", + "STEREO", + "5_1", + "5_1_2", + "5_1_4", + "7_1", + "7_1_4", + "FOA", + "HOA2", + "HOA3", +] + +""" Custom loudspeaker input/output """ +CUSTOM_LS_TO_TEST = [ + "t_design_4", + "4d4", + "16ch_8+4+4", +] + +""" Mixed scene ( metadata ) rendering """ +METADATA_SCENES_TO_TEST = ["mixed_scene", "mixed_scene_simple"] +METADATA_SCENES_TO_TEST_NO_BE = ["masa_scene"] +METADATA_SCENES_TO_TEST_MASA_PREREND = ["mixed_mc714_foa_masa2_ism4"] + +""" Binaural rendering """ +OUTPUT_FORMATS_BINAURAL = ["BINAURAL", "BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"] +HR_TRAJECTORIES_TO_TEST = [ + "full_circle_in_15s", + # "rotate_yaw_pitch_roll1", +] + +""" Frame Size """ +FRAMING_TO_TEST = ["5ms", "20ms"] + +PEAQ_SUPPORTED_FMT = [ + "MONO", + "STEREO", + "BINAURAL", + "BINAURAL_ROOM_IR", + "BINAURAL_ROOM_REVERB", +] + diff --git a/tests/renderer_short/test_renderer.py b/tests/renderer_short/test_renderer.py new file mode 100644 index 0000000000..8cfda2f6db --- /dev/null +++ b/tests/renderer_short/test_renderer.py @@ -0,0 +1,1285 @@ +#!/usr/bin/env python3 + +""" +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +import pytest + +from .constants import ( + FORMAT_TO_METADATA_FILES_LTV, + OUTPUT_FORMATS, + INPUT_FORMATS_AMBI, + FRAMING_TO_TEST, + EXE_SUFFIX, + OUTPUT_FORMATS_BINAURAL, + HR_TRAJECTORIES_TO_TEST, + HR_TRAJECTORY_DIR, + INPUT_FORMATS_MC, + INPUT_FORMATS_ISM, + INPUT_FORMATS_MASA, + METADATA_SCENES_TO_TEST_MASA_PREREND, + TEST_VECTOR_DIR, + CUSTOM_LS_TO_TEST, + CUSTOM_LAYOUT_DIR, + METADATA_SCENES_TO_TEST, +) +from .utils import run_renderer, compare_renderer_args +from ..conftest import props_to_record + +############################################################################## +# Bit-exactness tests +# +# These tests are run in REF and CUT mode and the outputs are compared for +# bit-exactness +############################################################################## +""" Ambisonics """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ambisonics( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ambisonics_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ambisonics_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.skip(reason="Not supported for BASOP code currently") +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +@pytest.mark.parametrize("aeid", ["1", "0"]) +def test_dynamic_acoustic_environment( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + aeid, + split_comparison, +): + rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") + rend_config_path.with_stem(f"rend_config") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + config_file=rend_config_path, + aeid=aeid, + split_comparison=split_comparison, + ) + + +@pytest.mark.skip("MSAN errors in BASOP need to be fixed") +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL[2:]) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_dynamic_acoustic_environment_file( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + rend_config_path = TEST_VECTOR_DIR.joinpath(f"rend_config_combined.cfg") + rend_config_path.with_stem(f"rend_config") + + aeid = TEST_VECTOR_DIR.joinpath(f"aeid1.txt") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + config_file=rend_config_path, + aeid=aeid, + split_comparison=split_comparison, + ) + + +""" Multichannel """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_multichannel( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_multichannel_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_multichannel_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +""" ISM """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ism( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ism_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_ism_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +""" MASA """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_masa( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_masa_binaural_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: + pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MASA) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_masa_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + if out_fmt in ["BINAURAL_ROOM_IR", "BINAURAL_ROOM_REVERB"]: + pytest.skip("Skipping binaural room outputs for MASA as unimplemented.") + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST_MASA_PREREND) +def test_masa_prerend( + record_property, + props_to_record, + test_info, + in_fmt, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + "META", + "MASA2", + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +""" Custom loudspeaker layouts """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_custom_ls_input( + record_property, + props_to_record, + test_info, + in_layout, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + CUSTOM_LAYOUT_DIR.joinpath(f"{in_layout}.txt"), + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize( + "in_fmt", + [*INPUT_FORMATS_AMBI, *INPUT_FORMATS_MC, *INPUT_FORMATS_ISM, *INPUT_FORMATS_MASA], +) +def test_custom_ls_output( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + # TODO: revert once BASOP is brought up-to-date + if in_fmt in INPUT_FORMATS_MASA: + pytest.skip("MASA to custom LS not supported on ivas-float-update yet") + if in_fmt in INPUT_FORMATS_ISM and out_fmt == "t_design_4": + pytest.skip("ISMx + t_design_4 skipped until bug in BASOP is fixed") + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + CUSTOM_LAYOUT_DIR.joinpath(f"{out_fmt}.txt"), + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("in_fmt", CUSTOM_LS_TO_TEST) +def test_custom_ls_input_output( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + CUSTOM_LAYOUT_DIR.joinpath(f"{in_fmt}.txt"), + CUSTOM_LAYOUT_DIR.joinpath(f"{out_fmt}.txt"), + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_custom_ls_input_binaural( + record_property, + props_to_record, + test_info, + in_layout, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + CUSTOM_LAYOUT_DIR.joinpath(f"{in_layout}.txt"), + out_fmt, + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_layout", CUSTOM_LS_TO_TEST) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_custom_ls_input_binaural_headrotation( + record_property, + props_to_record, + test_info, + in_layout, + out_fmt, + trj_file, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + CUSTOM_LAYOUT_DIR.joinpath(f"{in_layout}.txt"), + out_fmt, + trj_file=HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +""" Metadata / scene description input """ + + +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS) +@pytest.mark.parametrize("in_fmt", METADATA_SCENES_TO_TEST) +@pytest.mark.parametrize("frame_size", FRAMING_TO_TEST) +def test_metadata( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + frame_size, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + "META", + out_fmt, + metadata_input=TEST_VECTOR_DIR.joinpath(f"{in_fmt}.txt"), + binary_suffix=EXE_SUFFIX, + frame_size=frame_size, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +""" non diegetic pan """ + + +@pytest.mark.parametrize("out_fmt", ["STEREO"]) +@pytest.mark.parametrize("in_fmt", ["MONO"]) +@pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +def test_non_diegetic_pan_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + non_diegetic_pan, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + non_diegetic_pan=non_diegetic_pan, + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +@pytest.mark.parametrize("out_fmt", ["STEREO"]) +@pytest.mark.parametrize("in_fmt", ["ISM1"]) +@pytest.mark.parametrize("non_diegetic_pan", ["0", "-30", "45", "90", "-90"]) +def test_non_diegetic_pan_ism_static( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + non_diegetic_pan, + get_mld, + get_mld_lim, + get_ssnr, + get_odg, + get_odg_bin, + split_comparison, +): + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + non_diegetic_pan=non_diegetic_pan, + binary_suffix=EXE_SUFFIX, + get_mld=get_mld, + mld_lim=get_mld_lim, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + split_comparison=split_comparison, + ) + + +############################################################################## +# Smoke tests +# +# These tests are run only for the smoke test and do not perform a +# bit-exactness comparison between REF and CUT, but between CUT1 and CUT2 +############################################################################## + + +# Test compares rendering with just a trajectory file against rendering with a trajectory file + a zero ref rotation. +# These should be binary equivalent. +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refrotzero( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refrotzero", + "trj_file": HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + "refrot_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# Second test compares rendering with no head rotation against rendering with equal ref and head rotation. +# These should also be binary equivalent. +# Note that reference rotation is supplied per 4 subframes; head rotation per subframe. +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refrotequal( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refrotequal", + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "azi_plus_2-ele_plus_2-every-100-frames.csv" + ), + "refrot_file": HR_TRAJECTORY_DIR.joinpath( + "azi_plus_2-ele_plus_2-every-25-rows.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: head rotation trajectory file (OTR=NONE) +# cut: identical head rotation trajectory file as ref but in addition a constant +# reference vector in the looking direction of the coordinate system (OTR=REF_VEC) +@pytest.mark.parametrize("trj_file", HR_TRAJECTORIES_TO_TEST) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refveczero( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + trj_file, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refveczero", + "trj_file": HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath(f"{trj_file}.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath("const000-Vector3.csv"), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: no head rotation (OTR=NONE) +# cut: rendering with head rotation and a ref vector which moves in the +# looking-direction of the head rotation and therefore compensates it (OTR=REF_VEC) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refvecequal( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + # TODO revert + if in_fmt == "HOA3" and out_fmt == "BINAURAL_ROOM_REVERB": + pytest.xfail("WIP : minor differences to be resolved") + else: + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvecequal", + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: a head rotation trajectory with elevation (OTR=NONE) +# cut: a static head rotation and a reference position trajectory which moves +# in a way that produces the same acoustic output as the ref head rot trajectory (OTR=REF_VEC) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refvec_rotating( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + # TODO revert + if in_fmt == "HOA2" and out_fmt == "BINAURAL_ROOM_REVERB": + pytest.xfail("WIP : minor differences to be resolved") + else: + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: a head rotation trajectory with elevation (OTR=NONE) +# cut: a static head rotation and a reference position trajectory which moves +# in a way that produces the same acoustic output as the ref head rot trajectory (OTR=REF_VEC) +# which also contains a fixed position offset between listener and reference position (which +# gets compensated in the REF_VEV OTR modes) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refvec_rotating_fixed_pos_offset( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-fixed-pos-offset-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: a reference position trajectory with elevation and REF_VEC_LEV OTR mode (OTR=REF_VEC_LEV) +# cut: a reference position trajectory without the elevation and REF_VEC OTR mode (OTR=REF_VEC) +# Since the only difference between REF_VEC_LEV and REF_VEC is that *LEV ignores +# the height difference in positions, the output must be binary equivalent. +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_AMBI) +def test_ambisonics_binaural_headrotation_refveclev_vs_refvec( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refveclevel", + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refveclev_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-Vector3.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath("full-circle-4s-Vector3.csv"), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: a head rotation trajectory with elevation (OTR=NONE) +# cut: a static head rotation and a reference position trajectory which moves +# in a way that produces the same acoustic output as the ref head rot trajectory (OTR=REF_VEC) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_MC) +def test_multichannel_binaural_headrotation_refvec_rotating( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + if in_fmt in ["MONO", "STEREO"]: + pytest.skip("MONO or STEREO to Binaural rendering unsupported") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) + + +# This test compares rendering with: +# ref: a head rotation trajectory with elevation (OTR=NONE) +# cut: a static head rotation and a reference position trajectory which moves +# in a way that produces the same acoustic output as the ref head rot trajectory (OTR=REF_VEC) +@pytest.mark.parametrize("out_fmt", OUTPUT_FORMATS_BINAURAL) +@pytest.mark.parametrize("in_fmt", INPUT_FORMATS_ISM) +def test_ism_binaural_headrotation_refvec_rotating( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + split_comparison, +): + if test_info.config.option.create_ref or test_info.config.option.create_cut: + pytest.skip("OTR tests only run for smoke test") + + compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs={ + "name_extension": "refvec_rotating", + "trj_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s.csv" + ), + "frame_size": "5", + }, + cut_kwargs={ + "trj_file": HR_TRAJECTORY_DIR.joinpath("const000.csv"), + "refvec_file": HR_TRAJECTORY_DIR.joinpath( + "full-circle-with-up-and-down-4s-ccw-Vector3.csv" + ), + "frame_size": "5", + }, + split_comparison=split_comparison, + ) diff --git a/tests/renderer_short/utils.py b/tests/renderer_short/utils.py new file mode 100644 index 0000000000..9afda68b94 --- /dev/null +++ b/tests/renderer_short/utils.py @@ -0,0 +1,786 @@ +#!/usr/bin/env python3 + +""" +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" + +import filecmp +import logging +import os +from pathlib import Path +import subprocess as sp +import sys +from typing import Dict, Optional, Union + +import numpy as np +import pytest +import re +import errno +import tempfile + +from .compare_audio import compare_audio_arrays +from .constants import ( + LTV_DIR, + SCRIPTS_DIR, + OUTPUT_PATH_REF, + OUTPUT_PATH_CUT, + FORMAT_TO_FILE_COMPARETEST, + FORMAT_TO_FILE_LTV, + FORMAT_TO_METADATA_FILES, + FORMAT_TO_METADATA_FILES_LTV, + FORMAT_TO_FILE_SMOKETEST, + RENDERER_CMD, + BIN_SUFFIX_MERGETARGET, + PEAQ_SUPPORTED_FMT, +) +from ..constants import CAT_NORMAL + +sys.path.append(SCRIPTS_DIR) +from pyaudio3dtools.audiofile import readfile +from ..cmp_pcm import cmp_pcm +from ..conftest import parse_properties, get_split_idx + + +def _run_cmd(cmd, env, test_info=None): + """ + Helper function for running some command. + Raises a SystemError if either the return code is non-zero or a USAN printout is detected + """ + proc = sp.run(cmd, capture_output=True, text=True, env=env) + stdout = proc.stdout + proc.stderr + + # check for USAN error first + if "UndefinedBehaviorSanitizer" in stdout: + error = f"USAN error detected in stdout of command: {' '.join(cmd)}\n{stdout}" + if test_info is not None: + test_info.error = error + raise SystemError(error) + + # then handle possible crash + try: + proc.check_returncode() + except sp.CalledProcessError as e: + error = f"Command returned non-zero exit status ({e.returncode}): {' '.join(e.cmd)}\n{e.stderr}\n{e.stdout}" + if test_info is not None: + test_info.error = error + raise SystemError(error) + + +def run_cmd(cmd, test_info, env=None): + logging.info(f"\nRunning command\n{' '.join(cmd)}\n") + _run_cmd(cmd, env, test_info) + + +def run_isar_ext_rend_cmd(cmd, env=None): + logging.info(f"\nRunning ISAR EXT REND command\n{' '.join(cmd)}\n") + _run_cmd(cmd, env) + + +def run_ivas_isar_enc_cmd(cmd, env=None): + logging.info(f"\nRunning IVAS ISAR encoder command\n{' '.join(cmd)}\n") + _run_cmd(cmd, env) + + +def run_ivas_isar_dec_cmd(cmd, env=None): + logging.info(f"\nDUT decoder command:\n\t{' '.join(cmd)}\n") + _run_cmd(cmd, env) + + +def run_isar_post_rend_cmd(cmd, env=None): + logging.info(f"\nRunning ISAR post renderer command\n{' '.join(cmd)}\n") + _run_cmd(cmd, env) + + +def check_BE( + test_info, + ref: np.ndarray, + ref_fs: int, + cut: np.ndarray, + cut_fs: int, + atol: int = 2, +) -> tuple: + if ref is None or np.array_equal(ref, np.zeros_like(ref)): + pytest.fail("REF signal does not exist or is zero!") + + if cut is None or np.array_equal(cut, np.zeros_like(cut)): + pytest.fail("CuT signal does not exist or is zero!") + + snr, gain_b, max_diff = compare_audio_arrays(ref, ref_fs, cut, cut_fs) + + if np.isnan(snr) or gain_b == 0: + pytest.fail("Invalid comparison result, check your signals!") + + if ref.shape[0] < cut.shape[0]: + ref = np.pad(ref, [(0, cut.shape[0] - ref.shape[0]), (0, 0)]) + elif ref.shape[0] > cut.shape[0]: + cut = np.pad(cut, [(0, ref.shape[0] - cut.shape[0]), (0, 0)]) + + # check max_diff as well, since compare_audio_arrays will try to adjust for small delay differences + diff_found = not np.allclose(ref, cut, rtol=0, atol=atol) and max_diff > atol + + return diff_found, snr, gain_b, max_diff + + +def run_renderer( + record_property, + props_to_record, + test_info, + in_fmt: str, + out_fmt: str, + metadata_input: Optional[str] = None, + in_meta_files: Optional[list] = None, + trj_file: Optional[str] = None, + non_diegetic_pan: Optional[str] = None, + name_extension: Optional[str] = None, + refrot_file: Optional[str] = None, + refvec_file: Optional[str] = None, + refveclev_file: Optional[str] = None, + config_file: Optional[str] = None, + binary_suffix: str = "", + frame_size: Optional[str] = "20ms", + hrtf_file: Optional[str] = None, + get_mld=False, + mld_lim=0, + get_mld_lim=0, + abs_tol=0, + get_ssnr=False, + get_odg=False, + get_odg_bin=False, + aeid: Optional[Union[Path, int]] = None, + in_file=None, + out_file=None, + sr=48, + render_for_peaq=False, + split_comparison=False, +) -> str: + # prepare arguments and filepaths + if trj_file is not None: + trj_name = f"_{trj_file.stem}" + else: + trj_name = "" + + if refrot_file is not None: + refrot_name = f"_{refrot_file.stem}" + else: + refrot_name = "" + + if refvec_file is not None: + refvec_name = f"_{refvec_file.stem}" + else: + refvec_name = "" + + if refveclev_file is not None: + refveclev_name = f"_{refveclev_file.stem}" + else: + refveclev_name = "" + + if config_file is not None: + config_name = f"_{config_file.stem}" + else: + config_name = "" + + if frame_size: + framing_name = f"_{frame_size}" + else: + framing_name = "" + + if aeid is not None: + if isinstance(aeid, Path): + aeid_name = f"_{aeid.stem}" + else: + aeid_name = aeid + else: + aeid_name = "" + + if not isinstance(out_fmt, str): + out_name = f"{out_fmt.stem}" + else: + out_name = out_fmt + + if hrtf_file is not None: + hrtf_file_name = f"_{hrtf_file.stem}" + else: + hrtf_file_name = "" + + if test_info.config.option.create_ref: + output_path_base = OUTPUT_PATH_REF + else: + output_path_base = OUTPUT_PATH_CUT + + # if in REF or CUT creation mode use the comparetestv + if test_info.config.option.create_ref or test_info.config.option.create_cut: + format_to_file = FORMAT_TO_FILE_COMPARETEST + else: + format_to_file = FORMAT_TO_FILE_SMOKETEST + + format_to_metadata_files = FORMAT_TO_METADATA_FILES + + if test_info.config.option.use_ltv: + if test_info.config.option.ltv_dir: + format_to_file = dict() + format_to_metadata_files = dict() + for k, v in FORMAT_TO_FILE_LTV.items(): + format_to_file[k] = str(v).replace( + str(LTV_DIR), str(test_info.config.option.ltv_dir) + ) + for k, v in FORMAT_TO_METADATA_FILES_LTV.items(): + format_to_file[k] = str(v).replace( + str(LTV_DIR), str(test_info.config.option.ltv_dir) + ) + else: + format_to_file = FORMAT_TO_FILE_LTV + format_to_metadata_files = FORMAT_TO_METADATA_FILES_LTV + + if in_file is None: + if metadata_input is not None: + in_file = metadata_input + in_name = metadata_input.stem + elif not isinstance(in_fmt, str): + in_file = format_to_file[in_fmt.stem] + in_name = in_fmt.stem + else: + in_file = format_to_file[in_fmt] + in_name = in_fmt + + if in_meta_files is None and in_fmt in format_to_metadata_files: + in_meta_files = format_to_metadata_files[in_fmt] + + if out_file is None: + out_file_stem = f"{in_name}_to_{out_name}{trj_name}{non_diegetic_pan}{refrot_name}{refvec_name}{refveclev_name}{config_name}{framing_name}{hrtf_file_name}{name_extension}{aeid_name}.wav" + out_file = str(output_path_base.joinpath(out_file_stem)) + + cmd = RENDERER_CMD[:] + cmd[2] = str(in_file) + cmd[4] = str(in_fmt) + cmd[6] = str(out_file) + cmd[8] = str(out_fmt) + cmd[10] = str(sr) + + if test_info.config.option.create_ref: + cmd[0] += BIN_SUFFIX_MERGETARGET + cmd[0] += binary_suffix + + if in_meta_files is not None: + cmd.extend(["-im", *in_meta_files]) + + if trj_file is not None: + cmd.extend(["-T", str(trj_file)]) + + if hrtf_file is not None: + cmd.extend(["-hrtf", str(hrtf_file)]) + + if non_diegetic_pan is not None: + cmd.extend(["-non_diegetic_pan", str(non_diegetic_pan)]) + if refrot_file is not None: + cmd.extend(["-rf", str(refrot_file)]) + cmd.extend(["-otr", "ref"]) + + if refvec_file is not None: + cmd.extend(["-rvf", str(refvec_file)]) + cmd.extend(["-otr", "ref_vec"]) + + if refveclev_file is not None: + cmd.extend(["-rvf", str(refveclev_file)]) + cmd.extend(["-otr", "ref_vec_lev"]) + + if config_file is not None: + cmd.extend(["-render_config", str(config_file)]) + + if frame_size: + cmd.extend(["-fr", str(frame_size.replace("ms", ""))]) + + if aeid is not None: + cmd.extend(["-aeid", str(aeid)]) + + # Set env variables for UBSAN + env = os.environ.copy() + if test_info.node.name and "UBSAN_OPTIONS" in env.keys(): + env["UBSAN_OPTIONS"] = ( + env["UBSAN_OPTIONS"] + f",log_path=usan_log_{test_info.node.name}" + ) + + testcase_props = { + "format": "Renderer", + "category": CAT_NORMAL, + } + + if record_property is not None: + for k, v in testcase_props.items(): + record_property(k, v) + + # run the renderer + run_cmd(cmd, test_info, env) + + if test_info.config.option.create_cut and not render_for_peaq: + # CUT creation mode will run a comparison with REF + out_file_ref = str(OUTPUT_PATH_REF.joinpath(out_file_stem)) + + # Check if we need to render to mono, stereo or binaural for PEAQ comparison + odg_input = None + odg_test = None + odg_ref = None + if get_odg_bin: + odg_input = out_file_ref[0:-4] + ".INPUT.BINAURAL.wav" + odg_test = str(out_file)[0:-4] + ".BINAURAL.wav" + odg_ref = out_file_ref[0:-4] + ".BINAURAL.wav" + + if out_fmt not in PEAQ_SUPPORTED_FMT: + if in_fmt in PEAQ_SUPPORTED_FMT: + new_fmt = in_fmt # MONO or STEREO + else: + # If input is META which contains stereo, new_fmt needs to be STEREO. + if in_fmt == "META": + with open(in_file, "r") as scene: + if "STEREO" in scene.read(): + new_fmt = "STEREO" + else: + new_fmt = "BINAURAL" + else: + new_fmt = "BINAURAL" + + # Render test to PEAQ supported format (MONO, STEREO or BINAURAL) + cmd2 = RENDERER_CMD[:] + cmd2[2] = str(out_file) # in_file + cmd2[4] = str(out_fmt) # in_fmt + cmd2[6] = odg_test # out_file + cmd2[8] = new_fmt # out_fmt + cmd2[10] = str(sr) + cmd2[0] += BIN_SUFFIX_MERGETARGET # Use IVAS_rend_ref for re-rendering + cmd2[0] += binary_suffix + if "MASA" in str(out_fmt): + cmd2.extend(["-im", out_file + ".met"]) + run_cmd(cmd2, test_info, env) + + # Render ref to BINAURAL with same settings as test + cmd2[2] = str(out_file_ref) # in_file + cmd2[6] = odg_ref # out_file + run_cmd(cmd2, test_info, env) + out_fmt_bin = new_fmt + else: + out_fmt_bin = out_fmt + odg_test = out_file + odg_ref = out_file_ref + + if out_fmt_bin != in_fmt: + # Render input to match out_fmt_bin using same config as input, but with IVAS_rend_ref + cmd[0] += BIN_SUFFIX_MERGETARGET + cmd[0] += binary_suffix + cmd[6] = odg_input # out_file + cmd[8] = out_fmt_bin # out_fmt + run_cmd(cmd, test_info, env) + else: + odg_input = in_file + + ### run the comparison tools + split_idx = np.empty(0) + prop_suffix = [""] + + # 1. run comparison on whole files - this is done always, regardless of the presence of --split_comparison + ref_fs = int(cmd[10]) * 1000 + output_differs_parts, reason_parts = cmp_pcm( + out_file_ref, + out_file, + out_fmt, + ref_fs, + get_mld=get_mld, + mld_lim=get_mld_lim, + abs_tol=abs_tol, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + odg_input=odg_input, + odg_test=odg_test, + odg_ref=odg_ref, + scalefac=test_info.config.option.scalefac, + split_idx=split_idx, + ) + + # 2. run comparison on split files if --split_comparison is given + # for JBM cases, comparison will fail because of length mismatch beetween split wav files and tracefiles + # -> skip split comparison for these cases + if split_comparison: + split_idx = get_split_idx(str(Path(in_file).stem), ref_fs // 1000) + + # this extra if takes care of cases where no splits are found, e.g. the "NOOP" case in the self_test_ltv prm file + # if this would not be there, then the comparison of the whole file would run twice + if len(split_idx) > 0: + output_differs_splits, reason_splits = cmp_pcm( + out_file_ref, + out_file, + out_fmt, + ref_fs, + get_mld=get_mld, + mld_lim=get_mld_lim, + abs_tol=abs_tol, + get_ssnr=get_ssnr, + get_odg=get_odg, + get_odg_bin=get_odg_bin, + odg_input=odg_input, + odg_test=odg_test, + odg_ref=odg_ref, + scalefac=test_info.config.option.scalefac, + split_idx=split_idx, + ) + output_differs_parts += output_differs_splits + reason_parts += reason_splits + + if split_comparison: + prop_suffix = ["_whole"] + [ + f"_split{i:03d}" for i in range(1, len(split_idx) + 1) + ] + + for output_differs, reason, suffix in zip( + output_differs_parts, reason_parts, prop_suffix + ): + result_props = parse_properties( + reason, output_differs, props_to_record, suffix + ) + for k, v in result_props.items(): + record_property(k, v) + + if output_differs_parts[0]: + logging.error(f"Command line was: {' '.join(cmd)}") + pytest.fail(f"Output differs: ({reason_parts[0]})") + + # compare metadata files in case of MASA prerendering + if "MASA" in str(out_fmt): + meta_file_ref = out_file_ref + ".met" + meta_file_cut = out_file + ".met" + if not filecmp.cmp(meta_file_cut, meta_file_ref): + pytest.fail("Metadata file differs from reference") + + return out_file + + +def compare_renderer_args( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + ref_kwargs: Dict, + cut_kwargs: Dict, + split_comparison=False, +): + out_file_ref = run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + **ref_kwargs, + split_comparison=split_comparison, + ) + ref, ref_fs = readfile(out_file_ref) + out_file_cut = run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + **cut_kwargs, + split_comparison=split_comparison, + ) + cut, cut_fs = readfile(out_file_cut) + [diff_found, snr, gain_b, max_diff] = check_BE(test_info, ref, ref_fs, cut, cut_fs) + if diff_found: + pytest.fail( + f"CuT not BE to REF! SNR : {snr:3.2f} dB, Gain CuT: {gain_b:1.3f}, Max Diff = {int(max_diff)}" + ) + + +def binauralize_input_and_output( + record_property, + props_to_record, + test_info, + input_file, + dut_output_file, + ref_output_file, + in_fmt, + output_config, + enc_opts, + dec_opts, + in_sr, + out_sr, +): + # Use current folder as location for temporary directory, since scene description does not handle spaces in path + with tempfile.TemporaryDirectory(dir=".") as tmp_dir: + tmp_dir = Path(tmp_dir) + scene_dut = str(tmp_dir.joinpath("scene_dut.txt")) + scene_ref = str(tmp_dir.joinpath("scene_ref.txt")) + scene_in = str(tmp_dir.joinpath("scene_in.txt")) + + # File names for binauralized signals, if needed + ref_input_file_binaural = ref_output_file[0:-4] + ".INPUT.BINAURAL.wav" + dut_output_file_binaural = dut_output_file[0:-4] + ".BINAURAL.wav" + ref_output_file_binaural = ref_output_file[0:-4] + ".BINAURAL.wav" + + # Identify metadata + in_meta_files = [ + str(SCRIPTS_DIR.joinpath(m)) if m != "NULL" else m + for m in re.findall(r"\b\S+\.csv|NULL\b", enc_opts) + ] # All .csv or NULL files in enc_opts are ISM metadata files. + n_obj = len(in_meta_files) + + # If extended metadata is not used, strip the metadata for the external renderer + extended_md_used = ( + re.search(r"-ism\s?\+[1-4]", enc_opts) + and not "OMASA" in in_fmt + and not "OSBA" in in_fmt + ) + if not extended_md_used and n_obj > 0: + truncated_meta_files = [] + for md in in_meta_files: + if md != "NULL": + md_out_file = str(tmp_dir.joinpath(os.path.basename(md))) + with open(md_out_file, "w") as fp_out, open(md, "r") as fp_in: + for line in fp_in: + fp_out.write( + ",".join(line.split(",")[:2]) + "\n" + ) # Keep only first two elements: azim, elev + else: + md_out_file = "NULL" # Cannot truncate NULL, just insert it without modification + truncated_meta_files.append(md_out_file) + in_meta_files = truncated_meta_files + + in_meta_files = in_meta_files + [ + str(SCRIPTS_DIR.joinpath(m)) for m in re.findall(r"\b\S+\.met\b", enc_opts) + ] # All .met files in enc_opts are MASA metadata files. + out_meta_files = None + if output_config == "EXT": + out_meta_files = [] + if n_obj > 0: + out_meta_files = out_meta_files + [ + f"{dut_output_file}.{i}.csv" for i in range(0, n_obj) + ] + if "MASA" in in_fmt: + out_meta_files = out_meta_files + [f"{dut_output_file}.met"] + if output_config == "EXT": + output_config = in_fmt + if output_config == "": + output_config = "MONO" # EVS mono + metadata_input = None + + if "OSBA" in in_fmt or "OMASA" in in_fmt: + scene_description_file(in_fmt, scene_in, n_obj, input_file, in_meta_files) + input_file = scene_in + in_meta_files = None + in_fmt = "META" + + if "OSBA" in output_config or "OMASA" in output_config: + if "OSBA" in output_config: + output_config = ( + output_config[:-1] + "3" + ) # Temporary fix to handle than IVAS_dec produces HOA3 for all OSBA configs. Needs to be removed when this fix is ported to BASOP. + scene_description_file( + output_config, scene_dut, n_obj, dut_output_file, out_meta_files + ) + dut_output_file = scene_dut + scene_description_file( + output_config, scene_ref, n_obj, ref_output_file, out_meta_files + ) + ref_output_file = scene_ref + out_meta_files = None + output_config = "META" + + # Identify headtracking and orientation trajectories + trj_file = findstr(r"-t\s+(\S+)", dec_opts) + non_diegetic_pan = findstr(r"-non_diegetic_pan\s+(\S+)", dec_opts) + if non_diegetic_pan is not None: + output_config = "STEREO" + name_extension = None + refrot_file = findstr(r"-rf\s+(\S+)", dec_opts) + rot_tmp_file = findstr(r"-rvf\s+(\S+)", dec_opts) + refveclev_file = None + refvec_file = None + if "-otr ref_vec_lev".upper() in dec_opts.upper(): + refveclev_file = rot_tmp_file + else: + if "-otr ref_vec".upper() in dec_opts.upper(): + refvec_file = rot_tmp_file + + # Rendering configuration + config_file = findstr(r"-render_config\s+(\S+)", dec_opts) + binary_suffix = "_ref" + frame_size = findstr(r"-fr\s+(\S+)", dec_opts) + # hrtf_file = findstr(r'-hrtf\s+(\S+)', dec_opts) + hrtf_file = None # Default HRTFs used for binaural rendering of output + + aeid = findstr(r"-aeid\s+(\S+)", dec_opts) + + if not output_config.upper() in PEAQ_SUPPORTED_FMT: + # Render output to BINAURAL + output_reformat = "BINAURAL" + + check_and_makedir(str(Path(dut_output_file_binaural).parent)) + + run_renderer( + record_property, + props_to_record, + test_info, + output_config, + output_reformat, + metadata_input, + out_meta_files, + trj_file, + non_diegetic_pan, + name_extension, + refrot_file, + refvec_file, + refveclev_file, + config_file, + binary_suffix, + frame_size, + hrtf_file, + aeid, + in_file=dut_output_file, + out_file=dut_output_file_binaural, + sr=out_sr, + render_for_peaq=True, + ) + + check_and_makedir(str(Path(ref_output_file_binaural).parent)) + + run_renderer( + record_property, + props_to_record, + test_info, + output_config, + output_reformat, + metadata_input, + out_meta_files, + trj_file, + non_diegetic_pan, + name_extension, + refrot_file, + refvec_file, + refveclev_file, + config_file, + binary_suffix, + frame_size, + hrtf_file, + aeid, + in_file=ref_output_file, + out_file=ref_output_file_binaural, + sr=out_sr, + render_for_peaq=True, + ) + + # Update output_config to rendered format + output_config = output_reformat + else: + # Signal already mono, stereo or binaural + dut_output_file_binaural = dut_output_file + ref_output_file_binaural = ref_output_file + + if in_fmt.upper() != output_config.upper(): + # Render input to match output_config + out_fmt = output_config + + check_and_makedir(str(Path(ref_input_file_binaural).parent)) + + run_renderer( + record_property, + props_to_record, + test_info, + in_fmt, + out_fmt, + metadata_input, + in_meta_files, + trj_file, + non_diegetic_pan, + name_extension, + refrot_file, + refvec_file, + refveclev_file, + config_file, + binary_suffix, + frame_size, + hrtf_file, + aeid, + in_file=input_file, + out_file=ref_input_file_binaural, + sr=in_sr, + render_for_peaq=True, + ) + else: + ref_input_file_binaural = input_file + return ( + ref_input_file_binaural, + dut_output_file_binaural, + ref_output_file_binaural, + ) + + +def findstr(exp, s, one_element=True): + result = [SCRIPTS_DIR.joinpath(x) for x in re.findall(exp, s)] + if len(result) == 0: + return None + if one_element: + return result[0] + return result + + +def check_and_makedir(dir_path): + if not os.path.exists(dir_path): + try: + os.makedirs(dir_path) + except OSError as e: + if e.errno != errno.EEXIST: + raise # raises the error again + + +def scene_description_file(in_fmt, metadata_tmp, n_obj, input_file, in_meta_files): + with open(metadata_tmp, "w") as fp_meta: + currdir = Path( + metadata_tmp + ).parent # File names must be relative to config file location + fp_meta.write(f"{os.path.relpath(input_file, currdir)}\n") # Input file + fp_meta.write(f"{n_obj+1}\n") # Number of sources + for n in range(0, n_obj): + if in_meta_files[n] == "NULL": + md_file = "1\n1,0,0" # NULL metadata position: azim=0,elev=0 for 1 frame, looped throughout all frames. + else: + md_file = os.path.relpath(in_meta_files[n], currdir) + fp_meta.write(f"ISM\n{n+1}\n{md_file}\n") # ISM metadata + if "OSBA" in in_fmt: + fp_meta.write( + "gain_dB:-6\n" + ) # Set -6 dB on all components for OSBA to match IVAS_dec + fp_meta.write(f"{in_fmt.split('_')[0][1:]}\n") # SBA or MASA + fp_meta.write(f"{n_obj+1}\n") + fp_meta.write(f"{in_fmt.split('_')[-1]}\n") # SBA or MASA parameter + if "OMASA" in in_fmt: + fp_meta.write( + f"{os.path.relpath(in_meta_files[n_obj], currdir)}\n" + ) # MASA metadata + if "OSBA" in in_fmt: + fp_meta.write( + "gain_dB:-6\n" + ) # Set -6 dB on all components for OSBA to match IVAS_dec -- GitLab From b70b69c5463974f8ed35adc7c1fd5a3393c79197 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 08:33:22 +0200 Subject: [PATCH 267/340] Add cut/ref folders in renderer_short --- tests/renderer_short/cut/.gitignore | 1 + tests/renderer_short/ref/.gitignore | 1 + 2 files changed, 2 insertions(+) create mode 100644 tests/renderer_short/cut/.gitignore create mode 100644 tests/renderer_short/ref/.gitignore diff --git a/tests/renderer_short/cut/.gitignore b/tests/renderer_short/cut/.gitignore new file mode 100644 index 0000000000..f935021a8f --- /dev/null +++ b/tests/renderer_short/cut/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/tests/renderer_short/ref/.gitignore b/tests/renderer_short/ref/.gitignore new file mode 100644 index 0000000000..f935021a8f --- /dev/null +++ b/tests/renderer_short/ref/.gitignore @@ -0,0 +1 @@ +!.gitignore -- GitLab From a06c2bda548d3d4da02ae1967546594163a9f641 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 08:46:15 +0200 Subject: [PATCH 268/340] Add back old mixed_scene_simple.txt for renderer_short --- scripts/testv/mixed_scene_simple.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 scripts/testv/mixed_scene_simple.txt diff --git a/scripts/testv/mixed_scene_simple.txt b/scripts/testv/mixed_scene_simple.txt new file mode 100644 index 0000000000..85d38b8e93 --- /dev/null +++ b/scripts/testv/mixed_scene_simple.txt @@ -0,0 +1,12 @@ +spectral_test_4ch_48kHz.wav +3 +ISM +1 +ism_0a_0e.csv +ISM +2 +1 +1,-30,0 +MC +3 +STEREO -- GitLab From ffbcbc51c6d855c04d203da7a946cc234639ee70 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 08:52:58 +0200 Subject: [PATCH 269/340] Add back old mixed_scene.txt for renderer_short --- scripts/testv/mixed_scene.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 scripts/testv/mixed_scene.txt diff --git a/scripts/testv/mixed_scene.txt b/scripts/testv/mixed_scene.txt new file mode 100644 index 0000000000..2501c75383 --- /dev/null +++ b/scripts/testv/mixed_scene.txt @@ -0,0 +1,15 @@ +spectral_test_16ch_48kHz.wav +4 +ISM +1 +ism_0a_0e.csv +ISM +2 +1 +1,-30,0 +SBA +3 +1 +MC +7 +5_1_4 -- GitLab From c8ba774452b009241e78e25f600b4cea09bde5ef Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 08:56:42 +0200 Subject: [PATCH 270/340] Fixes for ivas-conformance (Windows version) --- .gitlab-ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a266ba9af4..5044cfb6ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1284,7 +1284,7 @@ ivas-conformance: # Reference creation - python scripts/prepare_combined_format_inputs.py - - $TEST_SET = "tests/codec_be_on_mr_nonselection", "tests/renderer/test_renderer.py", "tests/split_rendering/test_split_rendering.py" + - $TEST_SET = "tests/codec_be_on_mr_nonselection", "tests/renderer_short/test_renderer.py", "tests/split_rendering/test_split_rendering.py" - python -m pytest $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files # Output creation @@ -1312,9 +1312,8 @@ ivas-conformance: - cp -r -force -ErrorAction Ignore scripts/trajectories testvec - cp -r -force -ErrorAction Ignore scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface - cp -r -force -ErrorAction Ignore tests/ref testvec/testv/ref - - cp -r -force -ErrorAction Ignore tests/dut/* testvec/testv/ref - - cp -r -force -ErrorAction Ignore tests/renderer/cut testvec/testv/renderer/ref - - cp -r -force -ErrorAction Ignore tests/split_rendering/cut testvec/testv/split_rendering/ref + - cp -r -force -ErrorAction Ignore tests/renderer/ref testvec/testv/renderer/ref + - cp -r -force -ErrorAction Ignore tests/split_rendering/ref testvec/testv/split_rendering/ref - cp -r -force -ErrorAction Ignore tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - cp -r -force -ErrorAction Ignore tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns -- GitLab From f52cf2eb35270bd809b973e4d224c63469997e7d Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 09:08:42 +0200 Subject: [PATCH 271/340] Cleanup of parse_commands.py --- scripts/parse_commands.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 91c352d306..c2cc6a4188 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -95,7 +95,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') bts = re.search(r"\s(([\S]+)(.bts|.192|.pkt|.fer))$", cmd) if bts: - outfile.write('$DIFF_BIN '+bts.group(1).replace(CUT_PATH + r'/ref',REF_PATH + r'/ref')+' '+bts.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+bts.group(1).replace(CUT_PATH, REF_PATH)+' '+bts.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) @@ -149,7 +149,7 @@ if __name__ == '__main__': for output in glob.glob(absolute_out.group(1) + '*'): output = path.relpath(output).replace('\\','/') output = re.sub('tests', CUT_PATH, output) - diff_cmds.append('$DIFF_BIN '+output.replace(CUT_PATH + r'/ref',REF_PATH + r'/ref')+' '+output+' >> $LOG_FILE 2>&1') + diff_cmds.append('$DIFF_BIN '+output.replace(CUT_PATH, REF_PATH)+' '+output+' >> $LOG_FILE 2>&1') outfile.write(('; ').join(diff_cmds)) if isar_out and "ref" in isar_out.group(1): outfile.write('$DIFF_BIN '+isar_out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+isar_out.group(1)+' >> $LOG_FILE 2>&1') @@ -214,4 +214,4 @@ if __name__ == '__main__': outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: - outfile.write(footer.read()) \ No newline at end of file + outfile.write(footer.read()) -- GitLab From c4a29865a1a200839ff074454a3b00d489a524b8 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 09:18:41 +0200 Subject: [PATCH 272/340] Cleanup of parse_command.py --- scripts/parse_commands.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index c2cc6a4188..670a94b307 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -24,6 +24,8 @@ if __name__ == '__main__': with open(input,'r') as infile: report = infile.read() + + # Try finding all command lines. May fail if the pytest-html version does not produce a multiple line output. cmds_enc = re.findall(r'REF encoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) cmds_dec = re.findall(r'REF decoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) cmds_rend = re.findall(r'Running command\s*\n\s*(\S.*)', report, re.MULTILINE) @@ -60,7 +62,7 @@ if __name__ == '__main__': elif "Running ISAR post renderer command" in line: isar_post_rend_cmd = True - # Remove HTML tags + # Remove trailing HTML tags, if any cmds_rend = [x.split('<')[0] for x in cmds_rend] cmds_isar_post_rend = [x.split('<')[0] for x in cmds_isar_post_rend] @@ -71,6 +73,7 @@ if __name__ == '__main__': cmds_isar_post_rend.sort() # Remove duplicates from cmds_enc -- some decoder tests use same encoder options + # Relies on the list being sorted i = 0 while i + 1 < len(cmds_enc): if " ".join(cmds_enc[i].split()[:-1]) == " ".join(cmds_enc[i+1].split()[:-1]): @@ -152,9 +155,9 @@ if __name__ == '__main__': diff_cmds.append('$DIFF_BIN '+output.replace(CUT_PATH, REF_PATH)+' '+output+' >> $LOG_FILE 2>&1') outfile.write(('; ').join(diff_cmds)) if isar_out and "ref" in isar_out.group(1): - outfile.write('$DIFF_BIN '+isar_out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+isar_out.group(1)+' >> $LOG_FILE 2>&1') + outfile.write('$DIFF_BIN '+isar_out.group(1).replace(CUT_PATH, REF_PATH)+' '+isar_out.group(1)+' >> $LOG_FILE 2>&1') if isar_md_out and "ref" in isar_md_out.group(1): - outfile.write('; $DIFF_BIN '+isar_md_out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+isar_md_out.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('; $DIFF_BIN '+isar_md_out.group(1).replace(CUT_PATH, REF_PATH)+' '+isar_md_out.group(1)+' >> $LOG_FILE 2>&1\n') else: outfile.write('\n') outfile.write('\n\n') @@ -202,7 +205,7 @@ if __name__ == '__main__': arg = re.sub('ISAR_post_rend_ref(.exe)?', '$CUT_ISAR_POST_REND_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) if re.search("^tests.*bit$",arg): - arg = re.sub('tests/split_rendering/ref', REF_PATH + r'/split_rendering/ref', arg) + arg = re.sub('tests', REF_PATH, arg) arg = re.sub('tests', CUT_PATH, arg) args.append(arg) cmd = ' '.join(args) @@ -211,7 +214,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) if out and "ref" in out.group(1): - outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/split_rendering/ref',REF_PATH + r'/split_rendering/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH, REF_PATH)+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) -- GitLab From 3398f7ce007b35da871f31a113edad10ab84240b Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 09:43:08 +0200 Subject: [PATCH 273/340] Add back mixed_mc714_foa_masa2_ism4.txt for old renderer test --- scripts/testv/mixed_mc714_foa_masa2_ism4.txt | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 scripts/testv/mixed_mc714_foa_masa2_ism4.txt diff --git a/scripts/testv/mixed_mc714_foa_masa2_ism4.txt b/scripts/testv/mixed_mc714_foa_masa2_ism4.txt new file mode 100644 index 0000000000..800e88ae7c --- /dev/null +++ b/scripts/testv/mixed_mc714_foa_masa2_ism4.txt @@ -0,0 +1,24 @@ +mixed_mc714_foa_masa2_ism4.wav +7 +MC +1 +7_1_4 +SBA +13 +1 +MASA +17 +2 +stv2MASA2TC48c.met +ISM +19 +stvISM1.csv +ISM +20 +stvISM2.csv +ISM +21 +stvISM3.csv +ISM +22 +stvISM4.csv -- GitLab From 374f0dc06d95d1eb73ea87b40082a0299fbc9629 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 09:54:25 +0200 Subject: [PATCH 274/340] Add back mixed_mc714_foa_masa2_ism4.wav for old renderer test --- scripts/testv/mixed_mc714_foa_masa2_ism4.wav | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 scripts/testv/mixed_mc714_foa_masa2_ism4.wav diff --git a/scripts/testv/mixed_mc714_foa_masa2_ism4.wav b/scripts/testv/mixed_mc714_foa_masa2_ism4.wav new file mode 100644 index 0000000000..c680493c22 --- /dev/null +++ b/scripts/testv/mixed_mc714_foa_masa2_ism4.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:900feffb59cec3a831c23b07e6f2f305e74d382c4d64903e65cd7cc321bd7083 +size 6336044 -- GitLab From d8058c376c05e5397bfd04f05eb375763bd4fe31 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 13:32:30 +0200 Subject: [PATCH 275/340] Fixes for using renderer_short test in ivas-conformance* --- .gitlab-ci.yml | 8 ++++---- scripts/parse_commands.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5044cfb6ab..240cad7d9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1302,7 +1302,7 @@ ivas-conformance: - mkdir testvec - mkdir testvec/binauralRenderer_interface - mkdir testvec/testv - - mkdir testvec/testv/renderer + - mkdir testvec/testv/renderer_short - mkdir testvec/testv/split_rendering - mkdir testvec/bin - cp -force -ErrorAction Ignore scripts/testv/* testvec/testv @@ -1312,7 +1312,7 @@ ivas-conformance: - cp -r -force -ErrorAction Ignore scripts/trajectories testvec - cp -r -force -ErrorAction Ignore scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface - cp -r -force -ErrorAction Ignore tests/ref testvec/testv/ref - - cp -r -force -ErrorAction Ignore tests/renderer/ref testvec/testv/renderer/ref + - cp -r -force -ErrorAction Ignore tests/renderer_short/ref testvec/testv/renderer_short/ref - cp -r -force -ErrorAction Ignore tests/split_rendering/ref testvec/testv/split_rendering/ref - cp -r -force -ErrorAction Ignore tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - cp -r -force -ErrorAction Ignore tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns @@ -1392,7 +1392,7 @@ ivas-conformance-linux: - mkdir testvec - mkdir testvec/binauralRenderer_interface - mkdir testvec/testv - - mkdir testvec/testv/renderer + - mkdir testvec/testv/renderer_short - mkdir testvec/testv/split_rendering - mkdir testvec/bin - cp -r scripts/testv/* testvec/testv @@ -1402,7 +1402,7 @@ ivas-conformance-linux: - cp -r scripts/trajectories testvec - cp -r scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface - cp -r tests/ref testvec/testv/ref - - cp -r tests/renderer/ref testvec/testv/renderer/ref + - cp -r tests/renderer_short/ref testvec/testv/renderer_short/ref - cp -r tests/split_rendering/ref testvec/testv/split_rendering/ref - cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs - cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 670a94b307..99b13da01d 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -179,7 +179,7 @@ if __name__ == '__main__': arg = path.relpath(arg).replace('\\','/') arg = re.sub('IVAS_rend_ref(.exe)?', '$CUT_REND_BIN', arg) arg = re.sub('scripts', TESTV_PATH, arg) - arg = re.sub('tests/renderer/data', TESTV_PATH + r'renderer/data/', arg) + arg = re.sub('tests/renderer_short/data', TESTV_PATH + r'renderer_short/data/', arg) arg = re.sub('tests', CUT_PATH, arg) args.append(arg) cmd = ' '.join(args) @@ -188,7 +188,7 @@ if __name__ == '__main__': outfile.write(cmd+'\n') out = re.search(r"-o\s(([\S]+)(.wav|.raw|.pcm))", cmd) if out and "ref" in out.group(1): - outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/renderer/ref',REF_PATH + r'/renderer/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') + outfile.write('$DIFF_BIN '+out.group(1).replace(CUT_PATH + r'/renderer_short/ref',REF_PATH + r'/renderer_short/ref')+' '+out.group(1)+' >> $LOG_FILE 2>&1\n') outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) -- GitLab From f24ab356cba263671dc27d60c0057bb392f16d80 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 21 Oct 2025 14:47:47 +0300 Subject: [PATCH 276/340] Rearrange PI ext out cases, add dynamic audio suppression indication PI type --- lib_util/ivas_rtp_file.c | 160 ++++++++++++++++++++---------------- lib_util/ivas_rtp_pi_data.c | 3 +- lib_util/ivas_rtp_pi_data.h | 9 +- tests/rtp/ivasrtp.py | 14 ++-- tests/rtp/test_rtp.py | 2 +- 5 files changed, 101 insertions(+), 87 deletions(-) diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 384bdd57e2..aa272489bf 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -139,9 +139,9 @@ static ivas_error IvasRtpFile_Read( static const char *const PiDataNames[IVAS_PI_MAX_ID] = { "SCENE_ORIENTATION", "DEVICE_ORIENTATION_COMPENSATED", "DEVICE_ORIENTATION_UNCOMPENSATED", "ACOUSTIC_ENVIRONMENT", "AUDIO_DESCRIPTION", "ISM_NUM", "ISM_ID", "ISM_GAIN", "ISM_ORIENTATION", - "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "RESERVED13", + "ISM_POSITION", "ISM_DISTANCE_ATTENUATION", "ISM_DIRECTIVITY", "DIEGETIC_TYPE", "DYNAMIC_AUDIO_SUPPRESSION_INDICATION", "AUDIO_FOCUS_INDICATION", "RESERVED15", "PLAYBACK_DEVICE_ORIENTATION", "HEAD_ORIENTATION", "LISTENER_POSITION", - "DYNAMIC_AUDIO_SUPPRESSION", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", + "DYNAMIC_AUDIO_SUPPRESSION_REQUEST", "AUDIO_FOCUS_REQUEST", "PI_LATENCY", "R_ISM_ID", "R_ISM_GAIN", "R_ISM_ORIENTATION", "R_ISM_POSITION", "R_ISM_DIRECTION", "RESERVED27", "RESERVED28", "RESERVED29", "RESERVED30", "NO_DATA" }; @@ -273,9 +273,20 @@ void IVAS_RTP_LogPiData( fprintf( f_piDataOut, "\n\t\t}" ); } break; - case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST: { - const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppressionRequest; + fprintf( f_piDataOut, "{\n" ); + fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"preferAmbiance\": %s,\n", das->ambiance ? "true" : "false" ); + fprintf( f_piDataOut, "\t\t\t\"level\": %d", das->sli ); + fprintf( f_piDataOut, "\n\t\t}" ); + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION: + { + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppressionIndication; fprintf( f_piDataOut, "{\n" ); fprintf( f_piDataOut, "\t\t\t\"preferSpeech\": %s,\n", das->speech ? "true" : "false" ); fprintf( f_piDataOut, "\t\t\t\"preferMusic\": %s,\n", das->music ? "true" : "false" ); @@ -284,7 +295,6 @@ void IVAS_RTP_LogPiData( fprintf( f_piDataOut, "\n\t\t}" ); } break; - case IVAS_PI_RESERVED13: case IVAS_PI_AUDIO_FOCUS_REQUEST: { fprintf( f_piDataOut, "{" ); @@ -375,18 +385,6 @@ void IVAS_RTP_WriteExtPiData( fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.deviceUnCompensated.orientation.w, cur->data.deviceUnCompensated.orientation.x, cur->data.deviceUnCompensated.orientation.y, cur->data.deviceUnCompensated.orientation.z ); } break; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: - { - fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.playbackOrientation.orientation.w, cur->data.playbackOrientation.orientation.x, cur->data.playbackOrientation.orientation.y, cur->data.playbackOrientation.orientation.z ); - } - break; - case IVAS_PI_HEAD_ORIENTATION: - { - fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.headOrientation.orientation.w, cur->data.headOrientation.orientation.x, cur->data.headOrientation.orientation.y, cur->data.headOrientation.orientation.z ); - } - break; -#endif case IVAS_PI_ACOUSTIC_ENVIRONMENT: { fprintf( f_piDataOut, "%d", cur->data.acousticEnv.aeid ); @@ -403,11 +401,6 @@ void IVAS_RTP_WriteExtPiData( } break; #ifdef RTP_S4_251135_CR26253_0016_REV1 - case IVAS_PI_LISTENER_POSITION: - { - fprintf( f_piDataOut, "%f,%f,%f", cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); - } - break; case IVAS_PI_AUDIO_DESCRIPTION: { uint32_t nEntries = cur->data.audioDesc.nValidEntries; @@ -420,57 +413,6 @@ void IVAS_RTP_WriteExtPiData( } } break; - case IVAS_PI_DIEGETIC_TYPE: - { - const bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; - fprintf( f_piDataOut, "%d,%d,%d,%d,%d", isDiegetic[0], isDiegetic[1], isDiegetic[2], isDiegetic[3], isDiegetic[4] ); - } - break; - case IVAS_PI_AUDIO_FOCUS_INDICATION: - { - if ( cur->data.focusIndication.availDirection ) - { - fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); - if ( cur->data.focusIndication.availLevel ) - { - fprintf( f_piDataOut, "," ); - } - } - if ( cur->data.focusIndication.availLevel ) - { - fprintf( f_piDataOut, "%d", cur->data.focusIndication.flvl ); - } - } - break; - case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION: - { - const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppression; - fprintf( f_piDataOut, "%d,%d,%d,%d", das->speech, das->music, das->ambiance, das->sli ); - } - break; - case IVAS_PI_RESERVED13: - case IVAS_PI_AUDIO_FOCUS_REQUEST: - { - - if ( cur->data.focusRequest.availDirection ) - { - fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); - if ( cur->data.focusRequest.availLevel ) - { - fprintf( f_piDataOut, "," ); - } - } - if ( cur->data.focusRequest.availLevel ) - { - fprintf( f_piDataOut, "%d", cur->data.focusRequest.flvl ); - } - } - break; - case IVAS_PI_RESERVED15: - case IVAS_PI_RESERVED27: - case IVAS_PI_RESERVED28: - case IVAS_PI_RESERVED29: - case IVAS_PI_RESERVED30: case IVAS_PI_ISM_NUM: { fprintf( f_piDataOut, "%d", cur->data.ismNum.numObjects ); @@ -548,6 +490,73 @@ void IVAS_RTP_WriteExtPiData( } } break; + case IVAS_PI_DIEGETIC_TYPE: + { + const bool *isDiegetic = cur->data.digeticIndicator.isDiegetic; + fprintf( f_piDataOut, "%d,%d,%d,%d,%d", isDiegetic[0], isDiegetic[1], isDiegetic[2], isDiegetic[3], isDiegetic[4] ); + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION: + { + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppressionIndication; + fprintf( f_piDataOut, "%d,%d,%d,%d", das->speech, das->music, das->ambiance, das->sli ); + } + break; + case IVAS_PI_AUDIO_FOCUS_INDICATION: + { + if ( cur->data.focusIndication.availDirection ) + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusIndication.direction.w, cur->data.focusIndication.direction.x, cur->data.focusIndication.direction.y, cur->data.focusIndication.direction.z ); + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusIndication.availLevel ) + { + fprintf( f_piDataOut, "%d", cur->data.focusIndication.flvl ); + } + } + break; + case IVAS_PI_RESERVED15: + case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.playbackOrientation.orientation.w, cur->data.playbackOrientation.orientation.x, cur->data.playbackOrientation.orientation.y, cur->data.playbackOrientation.orientation.z ); + } + break; + case IVAS_PI_HEAD_ORIENTATION: + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.headOrientation.orientation.w, cur->data.headOrientation.orientation.x, cur->data.headOrientation.orientation.y, cur->data.headOrientation.orientation.z ); + } + break; + case IVAS_PI_LISTENER_POSITION: + { + fprintf( f_piDataOut, "%f,%f,%f", cur->data.listnerPosition.position.x, cur->data.listnerPosition.position.y, cur->data.listnerPosition.position.z ); + } + break; + case IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST: + { + const IVAS_PIDATA_DYNAMIC_SUPPRESSION *das = &cur->data.dynSuppressionRequest; + fprintf( f_piDataOut, "%d,%d,%d,%d", das->speech, das->music, das->ambiance, das->sli ); + } + break; + case IVAS_PI_AUDIO_FOCUS_REQUEST: + { + + if ( cur->data.focusRequest.availDirection ) + { + fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.focusRequest.direction.w, cur->data.focusRequest.direction.x, cur->data.focusRequest.direction.y, cur->data.focusRequest.direction.z ); + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "," ); + } + } + if ( cur->data.focusRequest.availLevel ) + { + fprintf( f_piDataOut, "%d", cur->data.focusRequest.flvl ); + } + } + break; case IVAS_PI_PI_LATENCY: { fprintf( f_piDataOut, "%d", cur->data.piLatency.latency ); @@ -578,6 +587,11 @@ void IVAS_RTP_WriteExtPiData( fprintf( f_piDataOut, "%f,%f", cur->data.ismEditDirection.azimuth, cur->data.ismEditDirection.elevation ); } break; + case IVAS_PI_RESERVED27: + case IVAS_PI_RESERVED28: + case IVAS_PI_RESERVED29: + case IVAS_PI_RESERVED30: + break; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ } fprintf( f_piDataOut, "\n" ); diff --git a/lib_util/ivas_rtp_pi_data.c b/lib_util/ivas_rtp_pi_data.c index 7150cfe4a8..8db9d41f9d 100644 --- a/lib_util/ivas_rtp_pi_data.c +++ b/lib_util/ivas_rtp_pi_data.c @@ -396,7 +396,7 @@ static ivas_error packDynamicSuppression( const IVAS_PIDATA_GENERIC *piData, uin return IVAS_ERROR( IVAS_ERR_RTP_INSUFFICIENT_OUTPUT_SIZE, "Insufficient space in DAS PI data buffer" ); } - buffer[nBytes++] = ( IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION ); /* PF/PM populated during final packing */ + buffer[nBytes++] = ( das->piDataType & MASK_5BIT ); /* PF/PM populated during final packing */ buffer[nBytes++] = 2u; buffer[nBytes++] = ( das->speech ? PI_AD_SPEECH_INDICATED : 0 ) | @@ -419,7 +419,6 @@ static ivas_error unpackDynamicSuppression( const uint8_t *buffer, uint32_t numD } das->size = sizeof( IVAS_PIDATA_AUDIO_DESC ); - das->piDataType = IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION; das->speech = ( buffer[0] & PI_AD_SPEECH_INDICATED ) != 0; das->music = ( buffer[0] & PI_AD_MUSIC_INDICATED ) != 0; das->ambiance = ( buffer[0] & PI_AD_AMBIANCE_INDICATED ) != 0; diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 3fc95f8d8d..8e8f90ee4e 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -57,7 +57,7 @@ typedef enum IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ - IVAS_PI_RESERVED13, /* reserved */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION, /* audio suppression indication */ IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ IVAS_PI_RESERVED15, /* reserved */ @@ -65,7 +65,7 @@ typedef enum IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ - IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION, /* receiver’s preference with respect to audio suppression */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST, /* receiver’s preference with respect to audio suppression */ IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ IVAS_PI_R_ISM_ID, /* id of an object for editing */ @@ -356,7 +356,7 @@ typedef enum typedef struct { size_t size; /* sizeof(IVAS_PIDATA_DYNAMIC_SUPPRESSION) */ - uint32_t piDataType; /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION */ + uint32_t piDataType; /* IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST or IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION */ bool speech; /* receiver's preference is voice/speech */ bool music; /* receiver's preference is music */ bool ambiance; /* receiver's preference is background ambiance */ @@ -443,12 +443,13 @@ typedef union IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; IVAS_PIDATA_DIEGETIC digeticIndicator; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionIndication; IVAS_PIDATA_AUDIO_FOCUS focusIndication; IVAS_PIDATA_ORIENTATION playbackOrientation; IVAS_PIDATA_ORIENTATION headOrientation; IVAS_PIDATA_LISTENER_POSITION listnerPosition; - IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppression; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionRequest; IVAS_PIDATA_AUDIO_FOCUS focusRequest; IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; IVAS_PIDATA_ISM_EDIT_ID ismEditId; diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index bd13acaf56..f15b9aa3f6 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -177,13 +177,13 @@ class PIDATAS(str, Enum): ISM_DISTANCE_ATTENUATION = "ISM_DISTANCE_ATTENUATION" ISM_DIRECTIVITY = "ISM_DIRECTIVITY" DIEGETIC_TYPE = "DIEGETIC_TYPE" - RESERVED13 = "RESERVED13" + DYNAMIC_AUDIO_SUPPRESSION_INDICATION = "DYNAMIC_AUDIO_SUPPRESSION_INDICATION" AUDIO_FOCUS_INDICATION = "AUDIO_FOCUS_INDICATION" RESERVED15 = "RESERVED15" PLAYBACK_DEVICE_ORIENTATION = "PLAYBACK_DEVICE_ORIENTATION" HEAD_ORIENTATION = "HEAD_ORIENTATION" LISTENER_POSITION = "LISTENER_POSITION" - DYNAMIC_AUDIO_SUPPRESSION = "DYNAMIC_AUDIO_SUPPRESSION" + DYNAMIC_AUDIO_SUPPRESSION_REQUEST = "DYNAMIC_AUDIO_SUPPRESSION_REQUEST" AUDIO_FOCUS_REQUEST = "AUDIO_FOCUS_REQUEST" PI_LATENCY = "PI_LATENCY" R_ISM_ID = "R_ISM_ID" @@ -938,13 +938,13 @@ PIDataUnpacker = [ unpackUnsupported, # ISM_DISTANCE_ATTENUATION unpackUnsupported, # ISM_DIRECTIVITY unpackDiegetic, # DIEGETIC_TYPE - unpackUnsupported, # RESERVED13 + unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION_INDICATION unpackAudioFocus, # AUDIO_FOCUS_INDICATION unpackUnsupported, # RESERVED15 unpackOrientation, # PLAYBACK_DEVICE_ORIENTATION unpackOrientation, # HEAD_ORIENTATION unpackPosition, # LISTENER_POSITION - unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION + unpackDAS, # DYNAMIC_AUDIO_SUPPRESSION_REQUEST unpackAudioFocus, # AUDIO_FOCUS_REQUEST unpackUnsupported, # PI_LATENCY unpackUnsupported, # R_ISM_ID @@ -973,13 +973,13 @@ PIDataPacker = [ packUnsupported, # ISM_DISTANCE_ATTENUATION packUnsupported, # ISM_DIRECTIVITY packDiegetic, # DIEGETIC_TYPE - packUnsupported, # RESERVED13 + packDAS, # DYNAMIC_AUDIO_SUPPRESSION_INDICATION packAudioFocus, # AUDIO_FOCUS_INDICATION packUnsupported, # RESERVED15 packOrientation, # PLAYBACK_DEVICE_ORIENTATION packOrientation, # HEAD_ORIENTATION packPosition, # LISTENER_POSITION - packDAS, # DYNAMIC_AUDIO_SUPPRESSION + packDAS, # DYNAMIC_AUDIO_SUPPRESSION_REQUEST packAudioFocus, # AUDIO_FOCUS_DIRECTION packUnsupported, # PI_LATENCY packUnsupported, # R_ISM_ID @@ -1487,7 +1487,7 @@ class IvasRtp: or piTypes == PIDATAS.R_ISM_POSITION ): data = POSITION(**dataDict) - elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: + elif "DYNAMIC_AUDIO_SUPPRESSION" in piTypes: data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) else: assert False, "Unhandled PI Data" diff --git a/tests/rtp/test_rtp.py b/tests/rtp/test_rtp.py index 69b85dc9af..b5ecfd850a 100644 --- a/tests/rtp/test_rtp.py +++ b/tests/rtp/test_rtp.py @@ -237,7 +237,7 @@ def generatePiData(startTs: int, endTs: int) -> dict: pidata["HEAD_ORIENTATION"] = someOrientation() pidata["AUDIO_FOCUS_REQUEST"] = random.choice(someAuFocusList)() pidata["LISTENER_POSITION"] = somePosition() - pidata["DYNAMIC_AUDIO_SUPPRESSION"] = someDAS() + pidata["DYNAMIC_AUDIO_SUPPRESSION_REQUEST"] = someDAS() pidata["AUDIO_DESCRIPTION"] = [someDesc() for n in range(random.randint(1, 5))] pidata["DIEGETIC_TYPE"] = someDIG() pidata["ACOUSTIC_ENVIRONMENT"] = ACOUSTIC_ENVIRONMENT( -- GitLab From 9e0a74dbdc17baf7ce6ea8e545fefca551fca64e Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 13:50:57 +0200 Subject: [PATCH 277/340] Another fix for using renderer_short test in ivas-conformance* --- scripts/rend_header.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/rend_header.txt b/scripts/rend_header.txt index a83b0a17bc..ce2e274c66 100644 --- a/scripts/rend_header.txt +++ b/scripts/rend_header.txt @@ -15,7 +15,7 @@ LOG_FILE=Readme_IVAS_rend_log.txt rm -rf tmp rm -rf $CUT_PATH -mkdir -p $CUT_PATH/renderer/ref -mkdir -p $CUT_PATH/renderer/data +mkdir -p $CUT_PATH/renderer_short/ref +mkdir -p $CUT_PATH/renderer_short/data -- GitLab From a41939636af3b033e56869442f804e2f6f878249 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 21 Oct 2025 14:52:13 +0300 Subject: [PATCH 278/340] Correct reader handling for audio focus pi --- tests/rtp/ivasrtp.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index bd13acaf56..94584e901b 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -1469,10 +1469,7 @@ class IvasRtp: data = list() for orientation in dataDict: data.append(ORIENTATION(**orientation)) - elif ( - "ORIENTATION" in piTypes - or piTypes == PIDATAS.AUDIO_FOCUS_DIRECTION - ): + elif "ORIENTATION" in piTypes: data = ORIENTATION(**dataDict) elif piTypes == PIDATAS.ACOUSTIC_ENVIRONMENT: data = ACOUSTIC_ENVIRONMENT(**dataDict) @@ -1489,6 +1486,8 @@ class IvasRtp: data = POSITION(**dataDict) elif piTypes == PIDATAS.DYNAMIC_AUDIO_SUPPRESSION: data = DYNAMIC_AUDIO_SUPPRESSION(**dataDict) + elif "AUDIO_FOCUS" in piTypes: + data = AUDIO_FOCUS(**dataDict) else: assert False, "Unhandled PI Data" piDataList.append( -- GitLab From c519ac94dce5462540eccc733c7ae266f8e4616f Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 21 Oct 2025 15:00:50 +0300 Subject: [PATCH 279/340] Re-organize reserved PI type in ext writing --- lib_util/ivas_rtp_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index aa272489bf..29aff673d3 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -518,7 +518,6 @@ void IVAS_RTP_WriteExtPiData( } } break; - case IVAS_PI_RESERVED15: case IVAS_PI_PLAYBACK_DEVICE_ORIENTATION: { fprintf( f_piDataOut, "%f,%f,%f,%f", cur->data.playbackOrientation.orientation.w, cur->data.playbackOrientation.orientation.x, cur->data.playbackOrientation.orientation.y, cur->data.playbackOrientation.orientation.z ); @@ -587,6 +586,7 @@ void IVAS_RTP_WriteExtPiData( fprintf( f_piDataOut, "%f,%f", cur->data.ismEditDirection.azimuth, cur->data.ismEditDirection.elevation ); } break; + case IVAS_PI_RESERVED15: case IVAS_PI_RESERVED27: case IVAS_PI_RESERVED28: case IVAS_PI_RESERVED29: -- GitLab From 658e766803422c65517732aa5155ebd17af15ca3 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 14:47:38 +0200 Subject: [PATCH 280/340] Revert to old html parsing in parse_commands.py. Seems to work better on Windows runners --- scripts/parse_commands.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 99b13da01d..aaafed1e1d 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -24,12 +24,10 @@ if __name__ == '__main__': with open(input,'r') as infile: report = infile.read() - - # Try finding all command lines. May fail if the pytest-html version does not produce a multiple line output. - cmds_enc = re.findall(r'REF encoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) - cmds_dec = re.findall(r'REF decoder command:\s*\n\s*(\S.*)', report, re.MULTILINE) - cmds_rend = re.findall(r'Running command\s*\n\s*(\S.*)', report, re.MULTILINE) - cmds_isar_post_rend = re.findall(r'Running ISAR post renderer command\s*\n\s*(\S.*)', report, re.MULTILINE) + cmds_enc = [] + cmds_dec = [] + cmds_rend = [] + cmds_isar_post_rend = [] # Depending on pytest-html version, the data may be in a json-blob. If so, the code below should handle this format. if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): @@ -62,10 +60,22 @@ if __name__ == '__main__': elif "Running ISAR post renderer command" in line: isar_post_rend_cmd = True + # If the above reading failed, try this variant instead. + if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): + with open(input,'r') as infile: + for line in infile.readlines(): + cmds_enc.extend(re.findall(r"DUT encoder command:\\n\\t(.*?)\\n", line)) + cmds_dec.extend(re.findall(r"DUT decoder command:\\n\\t(.*?)\\n", line)) + cmds_rend.extend(re.findall(r"Running command\\n(.*?)\\n", line)) + cmds_isar_post_rend.extend(re.findall(r"Running ISAR post renderer command\\n(.*?)\\n", line)) + + # Remove trailing HTML tags, if any cmds_rend = [x.split('<')[0] for x in cmds_rend] cmds_isar_post_rend = [x.split('<')[0] for x in cmds_isar_post_rend] + + # Sort lists to keep deterministic order between runs cmds_enc.sort() cmds_dec.sort() -- GitLab From 328391ba395174734d2e38ad15460dbd9a16ed4b Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 15:12:33 +0200 Subject: [PATCH 281/340] Revert html parsing --- scripts/parse_commands.py | 88 +++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index aaafed1e1d..370e18cbdf 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -2,7 +2,6 @@ import argparse import re -import json from os import path from pathlib import Path import glob @@ -22,59 +21,56 @@ if __name__ == '__main__': here = Path(__file__).parent.resolve() - with open(input,'r') as infile: - report = infile.read() - cmds_enc = [] - cmds_dec = [] - cmds_rend = [] - cmds_isar_post_rend = [] + cmds_enc=[] + cmds_dec=[] + cmds_rend=[] + cmds_isar_post_rend=[] - # Depending on pytest-html version, the data may be in a json-blob. If so, the code below should handle this format. - if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): - lines = re.split(r'\\n', report) - enc_cmd = False - dec_cmd = False - rend_cmd = False - isar_post_rend_cmd = False - for line in lines: - line = line.split("
    ")[0].replace('\\t','') # Remove trailing html tags - if enc_cmd: - cmds_enc.append(line) - enc_cmd = False - elif dec_cmd: - cmds_dec.append(line) - dec_cmd = False - elif rend_cmd: - cmds_rend.append(line) - rend_cmd = False - elif isar_post_rend_cmd: - cmds_isar_post_rend.append(line) - isar_post_rend_cmd = False - else: - if "REF encoder command" in line: - enc_cmd = True - elif "REF decoder command" in line: - dec_cmd = True - elif "Running command" in line: - rend_cmd = True - elif "Running ISAR post renderer command" in line: - isar_post_rend_cmd = True - # If the above reading failed, try this variant instead. - if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): - with open(input,'r') as infile: + if path.isdir(input): + input = Path(input).rglob('*.html') + else: + input = [input] + for html_report in input: + + with open(html_report,'r') as infile: for line in infile.readlines(): cmds_enc.extend(re.findall(r"DUT encoder command:\\n\\t(.*?)\\n", line)) cmds_dec.extend(re.findall(r"DUT decoder command:\\n\\t(.*?)\\n", line)) cmds_rend.extend(re.findall(r"Running command\\n(.*?)\\n", line)) cmds_isar_post_rend.extend(re.findall(r"Running ISAR post renderer command\\n(.*?)\\n", line)) - - # Remove trailing HTML tags, if any - cmds_rend = [x.split('<')[0] for x in cmds_rend] - cmds_isar_post_rend = [x.split('<')[0] for x in cmds_isar_post_rend] - - + # If pytest-html < v4 is used, the parsing will fail and render empty lists. This is a work-around in case that happens. + if all(not x for x in [cmds_enc, cmds_dec, cmds_rend, cmds_isar_post_rend]): + for html_report in input: + with open(html_report,'r') as infile: + enc_cmd = False + dec_cmd = False + rend_cmd = False + isar_post_rend_cmd = False + for line in infile.readlines(): + line = line.split("
    ")[0] # Remove trailing html tags + if enc_cmd: + cmds_enc.append(line) + enc_cmd = False + elif dec_cmd: + cmds_dec.append(line) + dec_cmd = False + elif rend_cmd: + cmds_rend.append(line) + rend_cmd = False + elif isar_post_rend_cmd: + cmds_isar_post_rend.append(line) + isar_post_rend_cmd = False + else: + if "DUT encoder command" in line: + enc_cmd = True + elif "DUT decoder command" in line: + dec_cmd = True + elif "Running command" in line: + rend_cmd = True + elif "Running ISAR post renderer command" in line: + isar_post_rend_cmd = True # Sort lists to keep deterministic order between runs cmds_enc.sort() -- GitLab From 3dfd25c664b1594ef6893020cdc28b714cfbf76c Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 21 Oct 2025 15:34:26 +0200 Subject: [PATCH 282/340] remove comment and move variable declaration to block start --- lib_dec/fd_cng_dec.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib_dec/fd_cng_dec.c b/lib_dec/fd_cng_dec.c index 1a6d4abb50..adca10685d 100644 --- a/lib_dec/fd_cng_dec.c +++ b/lib_dec/fd_cng_dec.c @@ -2235,7 +2235,9 @@ void FdCngDecodeMDCTStereoSID( int16_t is_out_ms; float *invTrfMatrix; float tmpRAM[FDCNG_VQ_MAX_LEN][FDCNG_VQ_DCT_MAXTRUNC]; - +#ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG + int32_t tmp; +#endif invTrfMatrix = (float *) tmpRAM; create_IDCT_N_Matrix( invTrfMatrix, FDCNG_VQ_MAX_LEN, FDCNG_VQ_DCT_MAXTRUNC, sizeof( tmpRAM ) / ( sizeof( float ) ) ); @@ -2286,9 +2288,6 @@ void FdCngDecodeMDCTStereoSID( } #ifdef NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG - /* TODO: temporary hack, need to decide what to do with core-coder bitrate */ - int32_t tmp; - tmp = sts[1]->total_brate; sts[1]->total_brate = sts[1]->total_brate + 16 * FRAMES_PER_SEC; /* read the four additional coherence values */ -- GitLab From 9e4e3c20bf2fddc8287356ccef6739489e3d2daa Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 21 Oct 2025 16:36:16 +0300 Subject: [PATCH 283/340] Align DAS and audio focus levels to match CR --- lib_util/ivas_rtp_pi_data.h | 12 ++++++------ tests/rtp/ivasrtp.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 8e8f90ee4e..724af49023 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -305,9 +305,9 @@ typedef enum IVAS_FLVL_FOCUS_LEVEL_LEVEL_10, /* Audio focus level 10 */ IVAS_FLVL_FOCUS_LEVEL_LEVEL_11, /* Audio focus level 11 */ IVAS_FLVL_FOCUS_LEVEL_LEVEL_12, /* Audio focus level 12 */ - IVAS_FLVL_FOCUS_LEVEL_LEVEL_13, /* Audio focus level 13 */ - IVAS_FLVL_DEFAULT_AUDIO_FOCUS, /* Default audio focus */ IVAS_FLVL_MAX_AUDIO_FOCUS, /* Apply max audio focus */ + IVAS_FLVL_DEFAULT_AUDIO_FOCUS, /* Default audio focus */ + IVAS_FLVL_NO_PREFERENCE, /* No preference / No indication */ } IVAS_FLVL; typedef struct @@ -335,7 +335,7 @@ typedef struct */ typedef enum { - IVAS_SLI_NO_SUPPRESSION = 0, /* Apply no suppression */ + IVAS_SLI_MIN_SUPPRESSION = 0, /* Apply min suppression */ IVAS_SLI_SUPPRESSION_LEVEL_1, /* Suppression level 1 */ IVAS_SLI_SUPPRESSION_LEVEL_2, /* Suppression level 2 */ IVAS_SLI_SUPPRESSION_LEVEL_3, /* Suppression level 3 */ @@ -347,10 +347,10 @@ typedef enum IVAS_SLI_SUPPRESSION_LEVEL_9, /* Suppression level 9 */ IVAS_SLI_SUPPRESSION_LEVEL_10, /* Suppression level 10 */ IVAS_SLI_SUPPRESSION_LEVEL_11, /* Suppression level 11 */ - IVAS_SLI_SUPPRESSION_LEVEL_12, /* Suppression level 12 */ - IVAS_SLI_SUPPRESSION_LEVEL_13, /* Suppression level 13 */ - IVAS_SLI_SUPPRESSION_LEVEL_14, /* Suppression level 14 */ IVAS_SLI_MAX_SUPPRESSION, /* Apply max suppression */ + IVAS_SLI_NO_SUPPRESSION, /* Apply no suppression */ + IVAS_SLI_DEFAULT_SUPPRESSION, /* Apply default suppression */ + IVAS_SLI_NO_PREFERENCE, /* No preference / No indication */ } IVAS_SLI; typedef struct diff --git a/tests/rtp/ivasrtp.py b/tests/rtp/ivasrtp.py index f15b9aa3f6..2724226676 100644 --- a/tests/rtp/ivasrtp.py +++ b/tests/rtp/ivasrtp.py @@ -199,7 +199,7 @@ class PIDATAS(str, Enum): class SUPPRESSION_LEVEL(int, Enum): - SUPPRESSION_LEVEL_NONE = 0 + SUPPRESSION_LEVEL_MIN = 0 SUPPRESSION_LEVEL_1 = 1 SUPPRESSION_LEVEL_2 = 2 SUPPRESSION_LEVEL_3 = 3 @@ -211,10 +211,10 @@ class SUPPRESSION_LEVEL(int, Enum): SUPPRESSION_LEVEL_9 = 9 SUPPRESSION_LEVEL_10 = 10 SUPPRESSION_LEVEL_11 = 11 - SUPPRESSION_LEVEL_12 = 12 - SUPPRESSION_LEVEL_13 = 13 - SUPPRESSION_LEVEL_14 = 14 - SUPPRESSION_LEVEL_MAX = 15 + SUPPRESSION_LEVEL_MAX = 12 + SUPPRESSION_LEVEL_NONE = 13 + SUPPRESSION_LEVEL_DEFAULT = 14 + SUPPRESSION_LEVEL_NO_PREFERENCE = 15 class AUDIO_FOCUS_LEVEL(int, Enum): @@ -231,7 +231,7 @@ class AUDIO_FOCUS_LEVEL(int, Enum): AUDIO_FOCUS_LEVEL_10 = 10 AUDIO_FOCUS_LEVEL_11 = 11 AUDIO_FOCUS_LEVEL_12 = 12 - AUDIO_FOCUS_LEVEL_13 = 13 + AUDIO_FOCUS_LEVEL_MAX = 13 AUDIO_FOCUS_LEVEL_DEFAULT = 14 AUDIO_FOCUS_LEVEL_NO_PREFERENCE = 15 -- GitLab From 4ec6d74ff957bc8e4343b41bae96620d7a57619c Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 21 Oct 2025 16:55:02 +0300 Subject: [PATCH 284/340] Skip future use subsequent E bytes instead of assert --- lib_util/ivas_rtp_payload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index e759f674bc..c9d26f20ee 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1388,7 +1388,7 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t break; #endif default: /* Reserved for future use - unhandled atm */ - assert( 0 ); + break; } } -- GitLab From 2df5bf8e5d6ec1709bee9e112b919967db3ca444 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 21 Oct 2025 20:32:57 +0200 Subject: [PATCH 285/340] Restore REF in command line pattern --- scripts/parse_commands.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 370e18cbdf..c5f4cd71fd 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -13,7 +13,7 @@ if __name__ == '__main__': parser.add_argument('txt_file',type=str,help='Output txt file, e.g. output.txt') args = parser.parse_args() input = args.input - txt_file = args.txt_file + txt_file = args.txt_file TESTV_PATH='$TESTV_PATH' REF_PATH='$REF_PATH' @@ -26,6 +26,7 @@ if __name__ == '__main__': cmds_rend=[] cmds_isar_post_rend=[] + all_args = set() if path.isdir(input): input = Path(input).rglob('*.html') @@ -35,8 +36,8 @@ if __name__ == '__main__': with open(html_report,'r') as infile: for line in infile.readlines(): - cmds_enc.extend(re.findall(r"DUT encoder command:\\n\\t(.*?)\\n", line)) - cmds_dec.extend(re.findall(r"DUT decoder command:\\n\\t(.*?)\\n", line)) + cmds_enc.extend(re.findall(r"REF encoder command:\\n\\t(.*?)\\n", line)) + cmds_dec.extend(re.findall(r"REF decoder command:\\n\\t(.*?)\\n", line)) cmds_rend.extend(re.findall(r"Running command\\n(.*?)\\n", line)) cmds_isar_post_rend.extend(re.findall(r"Running ISAR post renderer command\\n(.*?)\\n", line)) @@ -63,9 +64,9 @@ if __name__ == '__main__': cmds_isar_post_rend.append(line) isar_post_rend_cmd = False else: - if "DUT encoder command" in line: + if "REF encoder command" in line: enc_cmd = True - elif "DUT decoder command" in line: + elif "REF decoder command" in line: dec_cmd = True elif "Running command" in line: rend_cmd = True @@ -87,6 +88,9 @@ if __name__ == '__main__': else: i = i + 1 + # Filter out networkSimulator_g192 commands from cmds_rend + cmds_rend = [cmd for cmd in cmds_rend if not "networkSimulator_g192" in cmd] + with open(txt_file.replace('.','_enc.'),'w', newline='\n') as outfile: with open('scripts/enc_header.txt','r') as header: outfile.write(header.read()) @@ -224,3 +228,4 @@ if __name__ == '__main__': outfile.write('\n') with open('scripts/script_footer.txt','r') as footer: outfile.write(footer.read()) + -- GitLab From 4a48e012a1d098bf3bf6cdc60b4569587f5f55df Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Wed, 22 Oct 2025 08:27:54 +0200 Subject: [PATCH 286/340] Fix for ivas-conformance job --- .gitlab-ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 240cad7d9c..c46b57b5e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1285,10 +1285,7 @@ ivas-conformance: # Reference creation - python scripts/prepare_combined_format_inputs.py - $TEST_SET = "tests/codec_be_on_mr_nonselection", "tests/renderer_short/test_renderer.py", "tests/split_rendering/test_split_rendering.py" - - python -m pytest $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files - - # Output creation - - python -m pytest $TEST_SET -v -n auto --keep_files --create_cut --html=report_cmd.html --self-contained-html + - python -m pytest -q $TEST_SET -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html - python scripts/parse_commands.py report_cmd.html Readme_IVAS.txt # Copy input data and output ref data -- GitLab From 9f33c64059618960544829e940cadced89063c35 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Wed, 22 Oct 2025 13:22:40 +1100 Subject: [PATCH 287/340] Fix for PI data writting assert failure in debug when no PI data is provided Signed-off-by: Ripinder Singh --- lib_util/ivas_rtp_file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 29aff673d3..a0c0193f10 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -782,7 +782,7 @@ ivas_error IVAS_RTP_WriteNextFrame( return error; } - while ( rtp->nWrittenPiData-- > 0 ) + while ( rtp->nWrittenPiData > 0 ) { PIDATA_TS *piDataTs = &rtp->piData[nProcPiData++]; if ( ( error = IVAS_RTP_PACK_PushPiData( rtp->hPack, (const IVAS_PIDATA_GENERIC *) &piDataTs->data ) ) != IVAS_ERR_OK ) @@ -790,6 +790,7 @@ ivas_error IVAS_RTP_WriteNextFrame( fprintf( stderr, "\nError %s while pushing scene orientation\n", ivas_error_to_string( error ) ); return error; } + rtp->nWrittenPiData--; } if ( forcePacket || IVAS_RTP_PACK_GetNumFrames( rtp->hPack ) == rtp->packCfg.maxFramesPerPacket ) -- GitLab From 3135153771cff9130521460b52cb167172f65388 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Wed, 22 Oct 2025 13:22:40 +1100 Subject: [PATCH 288/340] Introduce Split Rendering RTP packed output from decoder Signed-off-by: Ripinder Singh --- apps/decoder.c | 131 ++++++++++++++++++++++++++++++++++++ apps/encoder.c | 4 ++ lib_util/ivas_rtp_api.h | 3 +- lib_util/ivas_rtp_file.c | 9 ++- lib_util/ivas_rtp_file.h | 4 ++ lib_util/ivas_rtp_payload.c | 14 ++-- 6 files changed, 154 insertions(+), 11 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 4996122e70..411bf0a218 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -135,6 +135,7 @@ typedef struct #ifdef IVAS_RTPDUMP bool applyPiData; char *piOutputFilename; + bool rtpOutSR; #endif #ifdef DEBUGGING #ifdef DEBUG_FOA_AGC @@ -448,6 +449,9 @@ int main( asked_frame_size = arg.renderFramesize; uint16_t aeID = arg.aeSequence.count > 0 ? arg.aeSequence.pID[0] : 65535; +#ifdef IVAS_RTPDUMP + arg.enableHeadRotation = arg.enableHeadRotation || arg.outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED || arg.outputConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM; +#endif if ( ( error = IVAS_DEC_Configure( hIvasDec, arg.output_Fs, arg.outputConfig, arg.renderFramesize, arg.customLsOutputEnabled, arg.hrtfReaderEnabled, arg.enableHeadRotation, arg.enableExternalOrientation, arg.orientation_tracking, arg.renderConfigEnabled, arg.non_diegetic_pan_enabled, arg.non_diegetic_pan_gain, arg.dpidEnabled, aeID, arg.objEditEnabled, arg.delayCompensationEnabled ) ) != IVAS_ERR_OK ) @@ -486,7 +490,9 @@ int main( goto cleanup; } +#ifndef IVAS_RTPDUMP arg.enableHeadRotation = true; +#endif } /*------------------------------------------------------------------------------------------* @@ -979,6 +985,7 @@ static bool parseCmdlIVAS_dec( #ifdef IVAS_RTPDUMP arg->applyPiData = false; arg->piOutputFilename = NULL; + arg->rtpOutSR = false; #endif #ifdef SUPPORT_JBM_TRACEFILE @@ -1084,6 +1091,11 @@ static bool parseCmdlIVAS_dec( arg->applyPiData = true; i++; } + else if ( strcmp( argv_to_upper, "-RTPOUTSR" ) == 0 ) + { + arg->rtpOutSR = true; + i++; + } #endif #ifdef SUPPORT_JBM_TRACEFILE else if ( strcmp( argv_to_upper, "-TRACEFILE" ) == 0 ) @@ -1677,6 +1689,7 @@ static void usage_dec( void ) fprintf( stdout, " Reading RFC4867 AMR/AMR-WB RTP payload format is not supported.\n" ); fprintf( stdout, "-PiDataFile PF Log the timestampped PI data.\n" ); fprintf( stdout, "-ApplyPiData Apply the PI data found in the rtp packet.\n" ); + fprintf( stdout, "-rtpOutSR : Split Rendering bitstream RTPDump output \n" ); #else fprintf( stdout, "-VOIP_hf_only=1 : VoIP mode: EVS RTP Payload Format hf_only=1 in rtpdump\n" ); fprintf( stdout, " The decoder may read rtpdump files containing TS26.445 Annex A.2.2\n" ); @@ -1785,6 +1798,9 @@ static ivas_error initOnFirstGoodFrame( IsmFileWriter *ismWriters[IVAS_MAX_NUM_OBJECTS], /* o : */ int16_t *pNumOutChannels, /* o : */ uint16_t *pNumObj, /* o : */ +#ifdef IVAS_RTPDUMP + IVAS_RTP *srRtp, /* o : */ +#endif SplitFileReadWrite **splitRendWriter ) { int16_t isSplitRend, isSplitCoded; @@ -1849,7 +1865,20 @@ static ivas_error initOnFirstGoodFrame( return error; } +#ifdef IVAS_RTPDUMP + if ( arg.rtpOutSR && srRtp != NULL ) + { + /* Split Rendering RTPDump Output file */ + if ( ( error = IVAS_RTP_WRITER_Init( srRtp, arg.outputWavFilename, 1000 / ( IVAS_NUM_FRAMES_PER_SEC * splitRendCodecFrameSizeMs ) ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError: Can't open SR output bitstream file for RTP output %s \n\n", arg.outputWavFilename ); + return error; + } + } + else if ( isSplitCoded ) +#else if ( isSplitCoded ) +#endif { if ( ( error = split_rend_writer_open( splitRendWriter, arg.outputWavFilename, delayNumSamples_temp[0], delayTimeScale_temp, splitRendCodec, poseCorrection, splitRendCodecFrameSizeMs, splitRendIsarFrameSizeMs, arg.output_Fs, lc3plusHighRes ) ) != IVAS_ERR_OK ) { @@ -1883,6 +1912,74 @@ static ivas_error initOnFirstGoodFrame( } } +#ifdef IVAS_RTPDUMP + if ( !arg.rtpOutSR ) + { + int16_t pcmFrameSize; + if ( ( error = IVAS_DEC_GetOutputBufferSize( hIvasDec, &pcmFrameSize ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError in IVAS_DEC_GetOutputBufferSize, error code: %d\n", error ); + return error; + } + + /* Write zeros to the output audio buffer */ + int16_t *zeroBuf = calloc( pcmFrameSize, sizeof( int16_t ) ); + if ( zeroBuf == NULL ) + { + fprintf( stdout, "Error: Unable to allocate memory for output buffer.\n" ); + return IVAS_ERR_FAILED_ALLOC; + } + + for ( int16_t i = 0; i < numInitialBadFrames; ++i ) + { +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( isSplitRend ) +#else + if ( *splitRendWriter != NULL ) +#endif + { + ISAR_SPLIT_REND_BITS_DATA splitRendBitsZero; + splitRendBitsZero.bits_buf = NULL; + splitRendBitsZero.bits_read = 0; + splitRendBitsZero.bits_written = 0; + splitRendBitsZero.buf_len = 0; + splitRendBitsZero.codec = ISAR_SPLIT_REND_CODEC_DEFAULT; + splitRendBitsZero.pose_correction = ISAR_SPLIT_REND_POSE_CORRECTION_MODE_NONE; + splitRendBitsZero.codec_frame_size_ms = 0; + splitRendBitsZero.isar_frame_size_ms = 20; + + if ( split_rend_write_bitstream_to_file( *splitRendWriter, splitRendBitsZero.bits_buf, &splitRendBitsZero.bits_read, &splitRendBitsZero.bits_written ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nUnable to write to bitstream file!\n" ); + return error; + } + } + +#ifdef FIX_1119_SPLIT_RENDERING_VOIP + if ( !isSplitCoded ) +#else + else +#endif + { + if ( *pRemainingDelayNumSamples < *numOutSamples ) + { + if ( ( error = AudioFileWriter_write( *ppAfWriter, zeroBuf, *numOutSamples * *pNumOutChannels - ( *pRemainingDelayNumSamples * *pNumOutChannels ) ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nOutput audio file writer error\n" ); + return error; + } + *pRemainingDelayNumSamples = 0; + } + else + { + *pRemainingDelayNumSamples -= *numOutSamples; + } + } + } + + free( zeroBuf ); + } +#else int16_t pcmFrameSize; if ( ( error = IVAS_DEC_GetOutputBufferSize( hIvasDec, &pcmFrameSize ) ) != IVAS_ERR_OK ) { @@ -1947,6 +2044,8 @@ static ivas_error initOnFirstGoodFrame( free( zeroBuf ); +#endif + /* Open other output files if EXT output config - now details about ISM or MASA are known */ if ( arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL ) { @@ -2039,7 +2138,11 @@ static ivas_error initOnFirstGoodFrame( } } +#ifdef IVAS_RTPDUMP + if ( arg.rtpOutSR || *splitRendWriter != NULL ) +#else if ( *splitRendWriter != NULL ) +#endif { if ( numOutSamples == NULL || vec_pos_len == NULL ) { @@ -2583,7 +2686,11 @@ static ivas_error decodeG192( /* Once good frame decoded, catch up */ if ( decodedGoodFrame ) { +#ifdef IVAS_RTPDUMP + if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, &vec_pos_len, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, NULL, &splitRendWriter ) ) != IVAS_ERR_OK ) +#else if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, &vec_pos_len, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &splitRendWriter ) ) != IVAS_ERR_OK ) +#endif { goto cleanup; } @@ -3098,6 +3205,8 @@ static ivas_error decodeVoIP( int16_t i; #ifdef IVAS_RTPDUMP IVAS_RTP ivasRtp = { 0 }; + IVAS_RTP srRtp = { 0 }; + IVAS_RTP_SR_INFO srInfo = { true, false, 0, IVAS_SR_TRANSPORT_LCLD }; int32_t initialTsOffsetSystemAndRTP = 0; #else FILE *f_rtpstream = NULL; @@ -3607,7 +3716,11 @@ static ivas_error decodeVoIP( #else if ( ( error = initOnFirstGoodFrame( hIvasDec, arg, numInitialBadFrames, &nOutSamples, NULL, delayNumSamples_orig, &delayNumSamples, &delayTimeScale, #endif +#ifdef IVAS_RTPDUMP + &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &srRtp, &splitRendWriter ) ) != IVAS_ERR_OK ) +#else &bsFormat, &afWriter, &masaWriter, ismWriters, &nOutChannels, &numObj, &splitRendWriter ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "Error in initOnFirstGoodFrame(): %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; @@ -3620,7 +3733,11 @@ static ivas_error decodeVoIP( } /* Write current frame */ +#ifdef IVAS_RTPDUMP + if ( !srRtp.hPack && decodedGoodFrame ) +#else if ( decodedGoodFrame ) +#endif { #ifdef FIX_1119_SPLIT_RENDERING_VOIP if ( isSplitRend ) @@ -3709,6 +3826,19 @@ static ivas_error decodeVoIP( IVAS_RTP_WriteExtPiData( ivasRtp.f_piExtOut, ivasRtp.piData, ivasRtp.nReadPiData, numObj ); } } +#ifdef IVAS_RTPDUMP + else if ( decodedGoodFrame ) + { + srInfo.bitrateKbps = splitRendBits->bits_written * 1000 / splitRendBits->codec_frame_size_ms; + srInfo.codec = ( splitRendBits->codec == ISAR_SPLIT_REND_CODEC_LC3PLUS ) ? IVAS_SR_TRANSPORT_LC3PLUS : IVAS_SR_TRANSPORT_LCLD; + if ( ( error = IVAS_RTP_WriteNextFrame( &srRtp, splitRendBits->bits_buf, &srInfo, (int16_t) splitRendBits->bits_written, false, false ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "\nError %s while pushing SR audio bitstream to RTP pack\n", ivas_error_to_string( error ) ); + goto cleanup; + } + } +#endif + vec_pos_update = ( vec_pos_update + 1 ) % vec_pos_len; if ( vec_pos_update == 0 ) @@ -3875,6 +4005,7 @@ static ivas_error decodeVoIP( cleanup: #ifdef IVAS_RTPDUMP + IVAS_RTP_Term( &srRtp ); IVAS_RTP_Term( &ivasRtp ); #else EVS_RTPDUMP_DEPACKER_close( &rtpdumpDepacker ); diff --git a/apps/encoder.c b/apps/encoder.c index 44fda79373..2bbd131a61 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -874,7 +874,11 @@ int main( goto cleanup; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = IVAS_RTP_WriteNextFrame( &ivasRtp, au, NULL, numBits, isMono, forcePacket ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_RTP_WriteNextFrame( &ivasRtp, au, numBits, isMono, forcePacket ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "\nError %s while pushing audio frame to RTP pack\n", IVAS_ENC_GetErrorMessage( error ) ); goto cleanup; diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index d2b2f5fbc8..06f50ef9e3 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -274,6 +274,7 @@ typedef struct { bool valid; /* Valid Split Rendering Info for/in the ToC */ bool diegetic; /* SR content digetic */ + uint32_t bitrateKbps; /* SR bitrate in kbps */ IVAS_RTP_SR_TRANSPORT codec; /* SR Transport Codec used*/ } IVAS_RTP_SR_INFO; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ @@ -344,7 +345,7 @@ ivas_error IVAS_RTP_PACK_PushFrame( IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o : IVAS rtp packer handle */ IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info (NULL if absent) */ + const IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info (NULL if absent) */ #endif /* RTP_S4_251135_CR26253_0016_REV1 */ const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ ); diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index a0c0193f10..3df5c27d79 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -754,8 +754,11 @@ ivas_error IVAS_RTP_READER_Init( } ivas_error IVAS_RTP_WriteNextFrame( - IVAS_RTP *rtp, /* i/o : IVAS RTP File writer handle */ - uint8_t *au, /* i : IVAS Compressed AU (Packed frame) */ + IVAS_RTP *rtp, /* i/o : IVAS RTP File writer handle */ + uint8_t *au, /* i : IVAS Compressed AU (Packed frame) */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + const IVAS_RTP_SR_INFO *srInfo, /* i : Split rendering info in present, else NULL */ +#endif int16_t auSizeBits, /* i : Frame size in bits */ bool isMono, /* i : input was evs(true) or ivas(false) */ bool forcePacket /* i : force packets with whatever frames pushed so far */ @@ -774,7 +777,7 @@ ivas_error IVAS_RTP_WriteNextFrame( error = IVAS_RTP_PACK_PushFrame( rtp->hPack, isMono ? IVAS_RTP_EVS : IVAS_RTP_IVAS, #ifdef RTP_S4_251135_CR26253_0016_REV1 - NULL, + srInfo, #endif /* RTP_S4_251135_CR26253_0016_REV1 */ &packedFrame ); if ( error != IVAS_ERR_OK ) diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 062b29c5c4..bd8d265873 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -73,7 +73,11 @@ typedef struct ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ); ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename, bool isExtOutput, const char *outputWavFilename ); void IVAS_RTP_Term( IVAS_RTP *rtp ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 +ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, const IVAS_RTP_SR_INFO *srInfo, int16_t auSizeBits, bool isMono, bool forcePacket ); +#else ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ); +#endif ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ); void IVAS_RTP_LogPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent ); void IVAS_RTP_WriteExtPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent, uint16_t numObj ); diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index c9d26f20ee..97a532022e 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -835,18 +835,17 @@ static void packEBytes( #ifdef RTP_S4_251135_CR26253_0016_REV1 static ivas_error getSRToCByte( - IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ - uint32_t bitrateKbps, /* i : Bitrate in kbps */ - uint8_t *tocByte /* o : toc byte 2 */ + const IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ + uint8_t *tocByte /* o : toc byte 2 */ ) { uint8_t bitIdx, codecId, digetic; - if ( bitrateKbps < 256000 || bitrateKbps > 512000 ) + if ( srInfo->bitrateKbps < 256000 || srInfo->bitrateKbps > 512000 ) { return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bitrate for SR" ); } - bitIdx = ( ( bitrateKbps / 128000u ) - 1 ) & MASK_2BIT; + bitIdx = ( ( srInfo->bitrateKbps / 128000u ) - 1 ) & MASK_2BIT; codecId = (uint8_t) srInfo->codec; digetic = srInfo->diegetic ? 1 : 0; @@ -882,7 +881,7 @@ ivas_error IVAS_RTP_PACK_PushFrame( IVAS_RTP_PACK_HANDLE hPack, /* i/o : IVAS rtp packer handle */ IVAS_RTP_CODEC codecId, /* i : Codec type (IVAS/EVS) */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ + const IVAS_RTP_SR_INFO *srInfo, /* i : Split Rendering Info */ #endif /* RTP_S4_251135_CR26253_0016_REV1 */ const IVAS_DATA_BUFFER *frameBuffer /* i : packed frame bitstream for IVAS/EVS */ ) @@ -907,7 +906,8 @@ ivas_error IVAS_RTP_PACK_PushFrame( else if ( srInfo != NULL && srInfo->valid ) { tocByte = TOC_INDICATE_SR; - error = getSRToCByte( srInfo, bitrate, &tocByte1 ); + frameLengthInBits = frameBuffer->length * 8; + error = getSRToCByte( srInfo, &tocByte1 ); ERR_CHECK_RETURN( error ); } #endif -- GitLab From 5d3fac18d3d28e02e111d53b375dbbefc6aab889 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 23 Oct 2025 19:46:11 +1100 Subject: [PATCH 289/340] Update legal third party notice for bitstring library * Add licenses for bitstring which is dependent on bitarray * bitstring licence is MIT License * bitarray license is PSF-2.0 Signed-off-by: Ripinder Singh --- .../thirdPartyLegalNotices.txt | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/tests/thirdPartyLegalNotices/thirdPartyLegalNotices.txt b/tests/thirdPartyLegalNotices/thirdPartyLegalNotices.txt index abff6f2cde..14815692f4 100755 --- a/tests/thirdPartyLegalNotices/thirdPartyLegalNotices.txt +++ b/tests/thirdPartyLegalNotices/thirdPartyLegalNotices.txt @@ -101,7 +101,7 @@ exceptiongroup MIT License The MIT License (MIT) -Copyright (c) 2022 Alex Grönholm +Copyright (c) 2022 Alex Grönholm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -2244,4 +2244,81 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. + + +bitstring +4.3.1 +MIT License +The MIT License + +Copyright (c) 2006 Scott Griffiths (dr.scottgriffiths@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +bitarray +3.7.2 +PSF-2.0 +PYTHON SOFTWARE FOUNDATION LICENSE +---------------------------------- + +1. This LICENSE AGREEMENT is between Ilan Schnell, and the Individual or +Organization ("Licensee") accessing and otherwise using this software +("bitarray") in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, Ilan Schnell +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use bitarray +alone or in any derivative version, provided, however, that Ilan Schnell's +License Agreement and Ilan Schnell's notice of copyright, i.e., "Copyright (c) +2008 - 2025 Ilan Schnell; All Rights Reserved" are retained in bitarray +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates bitarray or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to bitarray. + +4. Ilan Schnell is making bitarray available to Licensee on an "AS IS" +basis. ILAN SCHNELL MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, ILAN SCHNELL MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF BITARRAY WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. ILAN SCHNELL SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF BITARRAY +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING BITARRAY, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between Ilan Schnell +and Licensee. This License Agreement does not grant permission to use Ilan +Schnell trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using bitarray, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. -- GitLab From 74175058e0bf5ccf33803100614088b6b4c3ea76 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Thu, 23 Oct 2025 20:03:49 +0200 Subject: [PATCH 290/340] Filter out rtpdump tests from cmds_enc in parse_commands.py --- scripts/parse_commands.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index c5f4cd71fd..984374f66a 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -91,6 +91,9 @@ if __name__ == '__main__': # Filter out networkSimulator_g192 commands from cmds_rend cmds_rend = [cmd for cmd in cmds_rend if not "networkSimulator_g192" in cmd] + # Filter out rtpdump tests from cmds_enc. The rtpdump bitstreams differ between each run. + cmds_enc = [cmd for cmd in cmds_enc if not "rtpdump" in cmd] + with open(txt_file.replace('.','_enc.'),'w', newline='\n') as outfile: with open('scripts/enc_header.txt','r') as header: outfile.write(header.read()) -- GitLab From 903898d994c0cf3876546c951d4eb3be3ad022f8 Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Fri, 24 Oct 2025 14:48:53 +0200 Subject: [PATCH 291/340] Fix for encoder tests missing in test_26252.py --- scripts/parse_commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 984374f66a..78f0ba2747 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -50,7 +50,6 @@ if __name__ == '__main__': rend_cmd = False isar_post_rend_cmd = False for line in infile.readlines(): - line = line.split("
    ")[0] # Remove trailing html tags if enc_cmd: cmds_enc.append(line) enc_cmd = False -- GitLab From b2216304a3762d88065cedaf6263eba26a731b2d Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Fri, 24 Oct 2025 18:00:04 +0200 Subject: [PATCH 292/340] Fix html tag cleanup in command line in parse_commands.py --- scripts/parse_commands.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index 78f0ba2747..ea32898c91 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -50,17 +50,18 @@ if __name__ == '__main__': rend_cmd = False isar_post_rend_cmd = False for line in infile.readlines(): + line_trim = line.split("
    ")[0] # Remove trailing html tags if enc_cmd: - cmds_enc.append(line) + cmds_enc.append(line_trim) enc_cmd = False elif dec_cmd: - cmds_dec.append(line) + cmds_dec.append(line_trim) dec_cmd = False elif rend_cmd: - cmds_rend.append(line) + cmds_rend.append(line_trim) rend_cmd = False elif isar_post_rend_cmd: - cmds_isar_post_rend.append(line) + cmds_isar_post_rend.append(line_trim) isar_post_rend_cmd = False else: if "REF encoder command" in line: -- GitLab From 21869e4e5ff1dde157b10cde7d44f729bd441130 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Mon, 27 Oct 2025 15:20:09 +1100 Subject: [PATCH 293/340] Format fixes Signed-off-by: Ripinder Singh --- lib_util/ivas_rtp_file.c | 2 +- lib_util/ivas_rtp_payload.c | 2 +- lib_util/ivas_rtp_pi_data.h | 60 ++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 3df5c27d79..eab863a8cb 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -591,7 +591,7 @@ void IVAS_RTP_WriteExtPiData( case IVAS_PI_RESERVED28: case IVAS_PI_RESERVED29: case IVAS_PI_RESERVED30: - break; + break; #endif /* RTP_S4_251135_CR26253_0016_REV1 */ } fprintf( f_piDataOut, "\n" ); diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 97a532022e..7345222f0c 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1388,7 +1388,7 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t break; #endif default: /* Reserved for future use - unhandled atm */ - break; + break; } } diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index 724af49023..dc4c7f8baf 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -48,38 +48,38 @@ typedef enum IVAS_PI_DEVICE_ORIENTATION_UNCOMPENSATED, /* orientation of device in unit quaternions (un-compensated) */ IVAS_PI_ACOUSTIC_ENVIRONMENT, /* describe the acoustic environment */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ - IVAS_PI_ISM_NUM, /* Number of objects */ - IVAS_PI_ISM_ID, /* id of each object */ - IVAS_PI_ISM_GAIN, /* gain of each object */ - IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ - IVAS_PI_ISM_POSITION, /* position of each object */ - IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ - IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ - IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ - IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION, /* audio suppression indication */ - IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ - IVAS_PI_RESERVED15, /* reserved */ + IVAS_PI_AUDIO_DESCRIPTION, /* audio content description (voice/music/ambiance) */ + IVAS_PI_ISM_NUM, /* Number of objects */ + IVAS_PI_ISM_ID, /* id of each object */ + IVAS_PI_ISM_GAIN, /* gain of each object */ + IVAS_PI_ISM_ORIENTATION, /* orientation of each object */ + IVAS_PI_ISM_POSITION, /* position of each object */ + IVAS_PI_ISM_DISTANCE_ATTENUATION, /* distance attenuation for each object */ + IVAS_PI_ISM_DIRECTIVITY, /* directivity of each object */ + IVAS_PI_DIEGETIC_TYPE, /* digetic audio indication */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_INDICATION, /* audio suppression indication */ + IVAS_PI_AUDIO_FOCUS_INDICATION, /* audio focus indication (direction in Quaternions and/or level) */ + IVAS_PI_RESERVED15, /* reserved */ /* Reverse direction PI types */ - IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ - IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ - IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ - IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST, /* receiver’s preference with respect to audio suppression */ - IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ - IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ - IVAS_PI_R_ISM_ID, /* id of an object for editing */ - IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ - IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ - IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ - IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ - IVAS_PI_RESERVED27, /* reserved */ - IVAS_PI_RESERVED28, /* reserved */ - IVAS_PI_RESERVED29, /* reserved */ - IVAS_PI_RESERVED30, /* reserved */ -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ - IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ + IVAS_PI_PLAYBACK_DEVICE_ORIENTATION, /* orientation of the playback device in quaternions */ + IVAS_PI_HEAD_ORIENTATION, /* head orientation of the listener in Quaternions */ + IVAS_PI_LISTENER_POSITION, /* position of the listener in 3D space */ + IVAS_PI_DYNAMIC_AUDIO_SUPPRESSION_REQUEST, /* receiver’s preference with respect to audio suppression */ + IVAS_PI_AUDIO_FOCUS_REQUEST, /* direction of interest for the listener in Quaternions and/or audio focus level */ + IVAS_PI_PI_LATENCY, /* round-trip latency for PI frames */ + IVAS_PI_R_ISM_ID, /* id of an object for editing */ + IVAS_PI_R_ISM_GAIN, /* editing request for gain factor for received object */ + IVAS_PI_R_ISM_ORIENTATION, /* editing request for orientation for received object */ + IVAS_PI_R_ISM_POSITION, /* editing request for position for received object */ + IVAS_PI_R_ISM_DIRECTION, /* editing request for direction for received object */ + IVAS_PI_RESERVED27, /* reserved */ + IVAS_PI_RESERVED28, /* reserved */ + IVAS_PI_RESERVED29, /* reserved */ + IVAS_PI_RESERVED30, /* reserved */ +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + IVAS_PI_NO_DATA = 31, /* Indicates an empty PI data frame */ + IVAS_PI_MAX_ID /* Max number of PI data IDs supprted */ } IVAS_PI_TYPE; /* cartesian coordinates (X,Y,Z) in 3D space */ -- GitLab From 30254a5db536e360329db4b833d048e090cd7eec Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Fri, 17 Oct 2025 22:17:56 +1100 Subject: [PATCH 294/340] Initial implementation of the conformance script for frame based MLD Signed-off-by: Ripinder Singh --- scripts/ivas_conformance/runConformance.py | 558 +++++++++++++++++++++ 1 file changed, 558 insertions(+) create mode 100644 scripts/ivas_conformance/runConformance.py diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py new file mode 100644 index 0000000000..9df23416c8 --- /dev/null +++ b/scripts/ivas_conformance/runConformance.py @@ -0,0 +1,558 @@ +#!/usr/bin/env python3 + +""" +(C) 2022-2025 IVAS codec Public Collaboration with portions copyright Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository. All Rights Reserved. + +This software is protected by copyright law and by international treaties. +The IVAS codec Public Collaboration consisting of Dolby International AB, Ericsson AB, +Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V., Huawei Technologies Co. LTD., +Koninklijke Philips N.V., Nippon Telegraph and Telephone Corporation, Nokia Technologies Oy, Orange, +Panasonic Holdings Corporation, Qualcomm Technologies, Inc., VoiceAge Corporation, and other +contributors to this repository retain full ownership rights in their respective contributions in +the software. This notice grants no license of any kind, including but not limited to patent +license, nor is any license granted by implication, estoppel or otherwise. + +Contributors are required to enter into the IVAS codec Public Collaboration agreement before making +contributions. + +This software is provided "AS IS", without any express or implied warranties. The software is in the +development stage. It is intended exclusively for experts who have experience with such software and +solely for the purpose of inspection. All implied warranties of non-infringement, merchantability +and fitness for a particular purpose are hereby disclaimed and excluded. + +Any dispute, controversy or claim arising under or in relation to providing this software shall be +submitted to and settled by the final, binding jurisdiction of the courts of Munich, Germany in +accordance with the laws of the Federal Republic of Germany excluding its conflict of law rules and +the United Nations Convention on Contracts on the International Sales of Goods. +""" +import argparse +import os +import platform +import re +import numpy as np +import subprocess +import tempfile +import sys +from typing import Optional +from multiprocessing import Process, Value + +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from pyaudio3dtools.audiofile import readfile, writefile +from pyaudio3dtools.audioarray import resample + + +class MLDConformance: + IVAS_Bins = { + "ENC": "IVAS_cod", + "DEC": "IVAS_dec", + "REND": "IVAS_rend", + "ISAR": "ISAR_post_rend", + } + + def __init__(self, args) -> None: + self.RefBins = dict() + self.CutBins = dict() + self.Commands = dict() + self.multiprocessing = not args.no_multi_processing + self.dryrun = args.dryrun + self.verbose = args.verbose + self.executedTests = Value("i", 0) + self.failedTests = Value("i", 0) + self.testvecDir = args.testvecDir + self.ref_build_path = args.ref_build_path + self.cut_build_path = args.cut_build_path + self.filter = args.filter + self.EncoderToDecoderCmdMap = dict() + + self.scriptsDir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + self.outputDir = os.path.join(self.scriptsDir, "CUT_OUTPUTS") + self.toolsdir = os.path.join(self.scriptsDir, "tools") + self.testvDir = os.path.join(self.testvecDir, "testv") + self.refDir = self.testvDir + self.mldbin = os.path.join(self.toolsdir, platform.system(), "mld") + + self.logFile = os.path.join(self.outputDir, "runlog.txt") + self.failedCmdsFile = os.path.join(self.outputDir, "failedCmds.txt") + open(self.logFile, "w").close() + open(self.failedCmdsFile, "w").close() + + self.mldcsv = dict() + self.sampleStats = dict() + for tag in MLDConformance.IVAS_Bins.keys(): + self.mldcsv[tag] = os.path.join(self.outputDir, f"mld_{tag}.csv") + self.sampleStats[tag] = os.path.join(self.outputDir, f"sampleStats_{tag}.csv") + + self.setup() + + def createDirs(self): + os.makedirs(self.outputDir, exist_ok=True) + subdirs = ["enc", "dec", "renderer", "split_rendering"] + for odir in subdirs: + os.makedirs(os.path.join(self.outputDir, "ref", odir), exist_ok=True) + os.makedirs(os.path.join(self.outputDir, "dut", odir), exist_ok=True) + + def setup(self): + self.createDirs() + for tag in MLDConformance.IVAS_Bins.keys(): + self.RefBins[tag] = os.path.join( + self.ref_build_path, MLDConformance.IVAS_Bins[tag] + ) + self.CutBins[tag] = os.path.join( + self.cut_build_path, MLDConformance.IVAS_Bins[tag] + ) + self.Commands[tag] = list() + + def accumulateCommands(self): + for root, _, files in os.walk(self.testvecDir): + for file_name in files: + basename, ext = os.path.splitext(file_name) + if ( + ("Readme_IVAS_" in basename) + and ext == ".txt" + and not ("ISAR" in basename) + ): + print(f"Accumulating commands from {file_name}") + file = os.path.join(root, file_name) + self.parseCommandsFile(file) + self.mapEncoderToDecoderCommands() + print("No of tests :") + for key in self.Commands.keys(): + print(f" {key} : {len(self.Commands[key])}") + + def parseCommandsFile(self, filePath): + with open(filePath) as fp: + for line in fp.readlines(): + m = re.search(r"^\$(CUT_.+_BIN) ", line) + if m: + tag = m.group(1).split("_")[1] + if tag in self.Commands.keys(): + self.Commands[tag].append(line) + + def getPcmPytestTag(self, command: str) -> str: + decInput = ( + os.path.basename(command.split()[-2]) + .replace(".fer", "") + .replace("_cut", "") + ) + return decInput.split(".")[-2] + + def getEncPytestTag(self, command: str) -> str: + return os.path.basename(command.split()[-1]).split(".")[-2] + + def mapEncoderToDecoderCommands(self): + decoderPyTestTags = dict() + encoderPyTestTags = dict() + for idx, command in enumerate(self.Commands["DEC"]): + decoderPyTestTags[self.getPcmPytestTag(command)] = idx + for idx, command in enumerate(self.Commands["ENC"]): + encoderPyTestTags[self.getEncPytestTag(command)] = idx + + for encTag in encoderPyTestTags.keys(): + if encTag in decoderPyTestTags.keys(): + self.EncoderToDecoderCmdMap[encoderPyTestTags[encTag]] = ( + decoderPyTestTags[encTag] + ) + if self.verbose: + print( + f"{encTag} {encoderPyTestTags[encTag]} -> {decoderPyTestTags[encTag]}" + ) + print(f"{self.Commands['ENC'][encoderPyTestTags[encTag]]}") + print(f"{self.Commands['DEC'][decoderPyTestTags[encTag]]}") + else: + print(f"{encTag} not fount in decoder") + print( + f"Mapped decoder tests for {len(self.EncoderToDecoderCmdMap)} encoder tests out of {len(self.Commands['ENC'])} tests" + ) + assert len(self.EncoderToDecoderCmdMap) == len( + self.Commands["ENC"] + ), "Failed to Map Encoder Commands to Decoder Commands" + + def runOneEncoderTest(self, command: str): + encPytestTag = self.getEncPytestTag(command) + refCommand = self.reformatCommand(command=command, ref=True) + refEncOutput = self.getOutputFile(refCommand) + refCommand = self.setCommandExec(tag="ENC", command=refCommand, ref=True) + self.process(command=refCommand) + + # Run reference Encoder + encCommandIdx = self.Commands["ENC"].index(command) + command = self.reformatCommand(command=command, ref=False) + command = self.setCommandExec(tag="ENC", command=command, ref=False) + dutEncOutput = self.getOutputFile(command) + self.process(command=command) + assert ".192" in dutEncOutput, "Output file not identified" + + # Decode the encoded output with Reference decoder + dutDecOutputFile = dutEncOutput.replace(".192", "_CUT_REFDECODED.wav") + refDecOutputFile = dutEncOutput.replace(".192", "_REF_REFDECODED.wav") + decCommandIdx = self.EncoderToDecoderCmdMap[encCommandIdx] + command = self.reformatCommand( + command=self.Commands["DEC"][decCommandIdx], ref=False + ) + command = command.replace("-VOIP", "") + dutDecCmd = ( + [self.RefBins["DEC"]] + + command.split()[1:-2] + + [dutEncOutput, dutDecOutputFile] + ) + refDecCmd = ( + [self.RefBins["DEC"]] + + command.split()[1:-2] + + [refEncOutput, refDecOutputFile] + ) + self.process(command=" ".join(dutDecCmd)) + self.process(command=" ".join(refDecCmd)) + self.mld("ENC", encPytestTag, refFile=refDecOutputFile, dutFile=dutDecOutputFile) + + def runOneDecoderTest(self, tag: str, command: str): + dutPytestTag = self.getPcmPytestTag(command) + refInputFile = command.split()[-2].replace( + "$REF_PATH/ref", f"{self.testvDir}/ref" + ) + refInputFile = refInputFile.replace("_cut.192.fer", ".192") + # refInputFile = refInputFile.replace(".fer.192", ".192").replace(".192.fer", ".192").replace("_cut.192.fer", ".192").replace("_cut.192", ".192") + refDecOutput = self.getOutputFile(command).replace( + "$CUT_PATH/dut", f"{self.testvDir}/ref" + ) + command = self.reformatCommand(command=command, ref=False) + # command = command.replace("-VOIP", "") + dutDecOutputFile = self.getOutputFile(command) + dutDecCmd = ( + [self.CutBins["DEC"]] + + command.split()[1:-2] + + [refInputFile, dutDecOutputFile] + ) + self.process(command=" ".join(dutDecCmd)) + self.mld("DEC", dutPytestTag, refFile=refDecOutput, dutFile=dutDecOutputFile) + + def getRendOutputFile(self, command: str): + cmds = command.split() + for idx, cmd in enumerate(cmds): + if cmd == "-o" and (idx + 1) < len(cmds): + return cmds[idx + 1] + assert False, "Outputname not found" + + def runOneRendererTest(self, tag: str, command: str): + refRendOutputFile = self.getRendOutputFile(command).replace( + "$CUT_PATH/renderer", f"{self.testvDir}/renderer" + ) + rendPytestTag = os.path.basename(refRendOutputFile).split(".")[-2] + command = self.reformatCommand(command=command, ref=False) + dutRendCmd = " ".join([self.CutBins["REND"]] + command.split()[1:]) + dutRendOutputFile = self.getRendOutputFile(dutRendCmd) + self.process(command=dutRendCmd) + self.mld("REND", rendPytestTag, refFile=refRendOutputFile, dutFile=dutRendOutputFile) + + def getOutputFile(self, command: str): + return command.split()[-1] + + def setCommandExec(self, tag: str, command, ref: bool): + exec = self.RefBins[tag] if ref else self.CutBins[tag] + commands = command.split() + return " ".join([exec, *commands[1:]]) + + def reformatCommand(self, command: str, ref: bool) -> str: + command = command.replace("$TESTV_PATH", self.testvecDir) + command = command.replace( + "$REF_PATH/split_rendering", f"{self.testvecDir}/testv/split_rendering" + ) + + ################ HACKS ######################### + command = command.replace("_cut.192.fer", ".192") + command = command.replace("_cut.192", ".192") + command = command.replace(".fer.192", ".192") + command = command.replace(".192.fer", ".192") + ################################################## + + command = command.replace( + "$REF_PATH/ref/param_file/enc/", f"{self.outputDir}/ref/enc/" + ) + command = command.replace( + "$REF_PATH/ref/param_file/dec/", f"{self.outputDir}/ref/dec/" + ) + command = command.replace( + "$REF_PATH/ref/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" + ) + + if ref: + command = command.replace( + "$CUT_PATH/dut/param_file/enc/", f"{self.outputDir}/ref/enc/" + ) + command = command.replace( + "$CUT_PATH/dut/param_file/dec/", f"{self.outputDir}/ref/dec/" + ) + command = command.replace( + "$CUT_PATH/renderer/cut/", f"{self.outputDir}/ref/renderer/" + ) + command = command.replace( + "$CUT_PATH/split_rendering/cut/", + f"{self.outputDir}/ref/split_rendering/", + ) + command = command.replace( + "$CUT_PATH/dut/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" + ) + command = command.replace( + "$CUT_PATH/dut/sba_bs/raw/", f"{self.outputDir}/ref/dec/" + ) + else: + command = command.replace( + "$CUT_PATH/dut/param_file/enc/", f"{self.outputDir}/dut/enc/" + ) + command = command.replace( + "$CUT_PATH/dut/param_file/dec/", f"{self.outputDir}/dut/dec/" + ) + command = command.replace( + "$CUT_PATH/renderer/cut/", f"{self.outputDir}/dut/renderer/" + ) + command = command.replace( + "$CUT_PATH/split_rendering/cut/", + f"{self.outputDir}/dut/split_rendering/", + ) + command = command.replace( + "$CUT_PATH/dut/sba_bs/pkt/", f"{self.outputDir}/dut/enc/" + ) + command = command.replace( + "$CUT_PATH/dut/sba_bs/raw/", f"{self.outputDir}/dut/dec/" + ) + + return command + + def runOneCommand(self, tag: str, command: str, ref: bool): + if tag == "ENC": + self.runOneEncoderTest(command) + elif tag == "DEC": + self.runOneDecoderTest(tag, command) + elif tag == "REND": + self.runOneRendererTest(tag, command) + else: + assert False, f"Un-implemented Tag {tag}" + self.executedTests.value += 1 + self.stats() + + def runTag(self, tag: str, ref: bool = False): + self.executedTests.value = 0 + self.failedTests.value = 0 + # reset MLD, Sample Stats + open(self.mldcsv[tag], "w").close() + with open(self.sampleStats[tag], "w") as f: + f.write(f"PYTESTTAG, MAXDIFF, RMSdB, BEFRAMES_PERCENT, MAX_MLD\n") + + processes = list() # Multiprocess list + commands = list() + if self.filter: + for command in self.Commands[tag]: + if self.filter in command: + commands.append(command) + else: + commands = self.Commands[tag] + + self.totalTests = len(commands) + print( + f"Executing tests for {tag} {'Filter='+self.filter if self.filter else ''} ({self.totalTests} tests)" + ) + if self.multiprocessing: + for command in commands: + p = Process( + target=self.runOneCommand, + args=(tag, command, ref), + ) + processes.append(p) + p.start() + for p in processes: + p.join() + else: + for command in commands: + self.runOneCommand(tag, command, ref) + + def process(self, command) -> int: + if self.verbose: + print(command) + with open(self.logFile, "a") as fd: + fd.write(command + "\n") + + with open(self.logFile, "a") as fd: + if not self.dryrun: + c = subprocess.run( + command, stdout=fd, stderr=subprocess.STDOUT, text=True, shell=True + ) + if c.returncode: + with open(self.failedCmdsFile, "a") as f: + f.write(command + "\n") + self.failedTests.value += 1 + # c.check_returncode() + return 0 + + def stats(self): + print( + f"Executed: {self.executedTests.value} / {self.totalTests} Failed: {self.failedTests.value}", + end="\r", + ) + + def getSampleStats(self, refSamples: np.ndarray, dutSamples: np.ndarray): + nSamples = min(refSamples.shape[0], dutSamples.shape[0]) + diff = (refSamples[:nSamples] / 32768.0) - (dutSamples[:nSamples] / 32768.0) + beSamplesPercent = ( + 100.0 * np.sum(diff == 0) / refSamples.shape[0] / refSamples.shape[1] + ) + maxDiff = np.abs(diff).max() + rmsdB = int(10.0 * np.log10(np.average(diff**2) + np.finfo(np.float64).eps) * 10) / 10.0 + return (maxDiff, rmsdB, beSamplesPercent) + + + def mld(self, tag, pytestTag, refFile, dutFile): + mldThisFile = np.zeros(0) + with tempfile.TemporaryDirectory() as tmpdir: + refSamples, fsR = readfile(refFile, outdtype="float") + dutSamples, fsD = readfile(dutFile, outdtype="float") + assert ( + refSamples.shape[1] == dutSamples.shape[1] + ), "No of channels mismatch if ref vs cut" + maxDiff, rmsdB, beSamplesPercent = self.getSampleStats(refSamples, dutSamples) + + nChans = refSamples.shape[1] + + if fsR != 48000: + refSamples = np.clip(resample(refSamples, fsR, 48000), -32768, 32767) + if fsD != 48000: + dutSamples = np.clip(resample(dutSamples, fsD, 48000), -32768, 32767) + + for ch in range(nChans): + mldFile = os.path.join( + tmpdir, f"{tempfile.gettempprefix()}_ch{ch}_MLD.csv" + ) + refFileMono = os.path.join( + tmpdir, os.path.basename(refFile).replace(".wav", f"_ch{ch}.wav") + ) + dutFileMono = os.path.join( + tmpdir, os.path.basename(dutFile).replace(".wav", f"_ch{ch}.wav") + ) + writefile(refFileMono, refSamples[:, ch], 48000) + writefile(dutFileMono, dutSamples[:, ch], 48000) + command = [ + self.mldbin, + "-f", + "20", + "-o", + mldFile, + "-s", + refFileMono, + dutFileMono, + ] + self.process(" ".join(command)) + mldThisChan = np.loadtxt(mldFile, delimiter=" ", dtype=float) + if ch == 0: + mldThisFile = mldThisChan + else: + mldThisFile = np.maximum(mldThisFile, mldThisChan) + + if mldThisFile.size > 0: + with open(self.mldcsv[tag], "ab") as f: + np.savetxt(f, mldThisFile, delimiter=",") + with open(self.sampleStats[tag], "a") as f: + f.write(f"{pytestTag}, {maxDiff}, {rmsdB}, {beSamplesPercent}, {mldThisFile.max()}\n") + + + def doAnalysis(self, selectTag = "all"): + keys = MLDConformance.IVAS_Bins.keys() if selectTag == "all" else [ selectTag ] + for tag in keys: + if os.path.exists(self.mldcsv[tag]): + mdlValues = np.loadtxt(self.mldcsv[tag], delimiter=" ", dtype=float) + N = mdlValues.shape[0] + if N == 0: + continue + m0 = np.sum(mdlValues == 0) + m1 = np.sum(mdlValues <= 1.0) + m2 = np.sum(mdlValues <= 2.0) + m5 = np.sum(mdlValues <= 5.0) + + PCNT = lambda num: int(1000 * num / N) / 10.0 + print(f"\n##########################################################") + print(f"<{tag}> Total Frames: {N}") + print(f"<{tag}> MAX MLD across all frames : {mdlValues.max()}") + print(f"<{tag}> Frames with MLD == 0 : {m0} frames ({PCNT(m0)}%)") + print(f"<{tag}> Frames with MLD <= 1 : {m1} frames ({PCNT(m1)}%)") + print(f"<{tag}> Frames with MLD <= 2 : {m2} frames ({PCNT(m2)}%)") + print(f"<{tag}> Frames with MLD <= 5 : {m5} frames ({PCNT(m5)}%)") + print("##########################################################\n") + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Compare .wav files in two folders using mld per frame" + ) + + parser.add_argument( + "--testvecDir", + type=str, + required=True, + help="Base folder containing a set of conformance scripts named Readme_IVAS_xxxx.txt", + ) + parser.add_argument( + "--ref_build_path", + type=str, + required=True, + help="Path to the reference build folder containing IVAS Encoder, Decoder, Renderer and Post Render binaries", + ) + parser.add_argument( + "--cut_build_path", + type=str, + required=True, + help="Path to the CUT build folder containing IVAS Encoder, Decoder, Renderer and Post Render binaries", + ) + parser.add_argument( + "--verbose", + default=False, + action="store_true", + help="Enable verbose printing", + ) + parser.add_argument( + "--dryrun", + default=False, + action="store_true", + help="Do not run any processing, just dump command lines to runlog", + ) + + parser.add_argument( + "--filter", + type=str, + default=None, + help="Filter test based on text provided", + ) + parser.add_argument( + "--test-mode", + type=str, + default="ALL", + help='Choose tests to run ["ENC", "DEC", "REND", "ISAR", "ALL"]', + ) + parser.add_argument( + "--no-multi-processing", + default=False, + action="store_true", + help="Disable multi-processing for sequential test run (debugging)", + ) + parser.add_argument( + "--analyse-only", + default=False, + action="store_true", + help="Do not run DUT, use existing mld and bitdiff stats files to generate analysis only", + ) + + args = parser.parse_args() + + conformance = MLDConformance(args) + + conformance.accumulateCommands() + # import sys + # sys.exit(0) + testTags = MLDConformance.IVAS_Bins.keys() if args.test_mode == "ALL" else [args.test_mode] + for tag in testTags: + if tag == "ISAR": + # Not implemented yet + continue + if not args.analyse_only: + conformance.runTag(tag, ref=True) + conformance.doAnalysis(selectTag=tag) -- GitLab From b6fc1ed7e651b8146ba3693553a7a628ae7ae8a8 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Mon, 27 Oct 2025 10:58:04 +0200 Subject: [PATCH 295/340] Unify print stacktrace options also to float CI. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4c830957a9..71b9496761 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -145,7 +145,7 @@ workflow: - make clean - make -j CLANG=$CLANG_NUM - testcase_timeout=$SELFTEST_SANITY_TIMEOUT - - export UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1 + - export UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1,print_stacktrace=1 - exit_code20=0 - exit_code10=0 @@ -608,7 +608,7 @@ renderer-usan: variables: SANITIZER_BUILD_STRING: "usan" script: - - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1 python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout + - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1,print_stacktrace=1 python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --testcase_timeout=$testcase_timeout artifacts: expose_as: "renderer usan result" -- GitLab From f615ef39d7bbc94793c933ad07fd01f3657354f5 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 27 Oct 2025 13:07:37 +0200 Subject: [PATCH 296/340] Increase PI data pointer after each appliance --- lib_dec/lib_dec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 7ab22259ad..1e34dcd1b0 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5666,6 +5666,7 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui { return error; } + piData++; } return error; } -- GitLab From e29a4e1446add5cd5462710c672fe1e7aa831784 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Mon, 27 Oct 2025 13:22:02 +0200 Subject: [PATCH 297/340] Fix issue 1380 by changing panning gains for OMASA encoding to use lookup-table approach similar to BASOP. --- lib_com/ivas_cnst.h | 3 + lib_com/ivas_omasa_com.c | 64 ++++++++++++++++++++ lib_com/ivas_rom_com.c | 127 +++++++++++++++++++++++++++++++++++++++ lib_com/ivas_rom_com.h | 3 + lib_com/options.h | 2 + 5 files changed, 199 insertions(+) diff --git a/lib_com/ivas_cnst.h b/lib_com/ivas_cnst.h index e16553e85a..3e8521f47d 100755 --- a/lib_com/ivas_cnst.h +++ b/lib_com/ivas_cnst.h @@ -1226,6 +1226,9 @@ enum #define OMASA_GAIN_EDIT_THR 0.06f /* OMASA gain change threshold */ #define OMASA_AZI_EDIT_THR 1.0f /* OMASA-DISC azimuth change threshold */ #define OMASA_ELE_EDIT_THR 2.0f /* OMASA-DISC elevation change threshold */ +#ifdef NONBE_1380_OMASA_BUILD_DIFF +#define OMASA_PAN_TBL_LEN 601 +#endif #define MASA_JBM_RINGBUFFER_FRAMES 3 diff --git a/lib_com/ivas_omasa_com.c b/lib_com/ivas_omasa_com.c index 4042dd959b..2908ffd31c 100644 --- a/lib_com/ivas_omasa_com.c +++ b/lib_com/ivas_omasa_com.c @@ -61,7 +61,12 @@ #define GAMMA_ISM_MEDIUM_IMP4 1.0f #define GAMMA_ISM_HIGH_IMP4 1.2f +#ifdef NONBE_1380_OMASA_BUILD_DIFF +#define PAN_MAX_DEG 30.0f +#define ONE_OVER_PAN_STEP_DEG 10.0f +#define SIN_LS_ANGLE 0.5f /* sinf( 30.0f * PI_OVER_180 ) */ +#endif /*--------------------------------------------------------------- * ivas_omasa_ism_mode_select() * @@ -496,6 +501,64 @@ void ivas_get_stereo_panning_gains( const float eleDeg, float panningGains[2] ) { +#ifdef NONBE_1380_OMASA_BUILD_DIFF + float aziPlusEle, aziMinusEle, y; + + /* use identity sin(A+B) + sin(A−B) = 2 sinA cosB */ + aziPlusEle = aziDeg + eleDeg; + aziMinusEle = aziDeg - eleDeg; + + /* wrap into -180..+180 */ + while ( aziPlusEle > 180.0f ) + { + aziPlusEle -= 360.0f; + } + while ( aziPlusEle < -180.0f ) + { + aziPlusEle += 360.0f; + } + while ( aziMinusEle > 180.0f ) + { + aziMinusEle -= 360.0f; + } + while ( aziMinusEle < -180.0f ) + { + aziMinusEle += 360.0f; + } + + /* compute Y-coordinate corresponding to the azimuth and elevation: y = sin(azi) * cos(ele) = (sin(azi+ele) + sin(azi-ele)) / 2 */ + y = ( sinf( aziPlusEle * PI_OVER_180 ) + sinf( aziMinusEle * PI_OVER_180 ) ) * 0.5f; + + if ( y >= SIN_LS_ANGLE ) + { /* Left side */ + panningGains[0] = 1.0f; + panningGains[1] = 0.0f; + } + else if ( y <= -SIN_LS_ANGLE ) + { /* Right side */ + panningGains[0] = 0.0f; + panningGains[1] = 1.0f; + } + else /* Tangent panning law */ + { + /* from sin(angle) to index assuming range -30..+30 degrees with 0.1-degree spacing */ + float angleDeg, pos; + int16_t idx; + + /* Convert azi and ele to an azi value of the cone of confusion */ + angleDeg = asinf( y ) * _180_OVER_PI; + angleDeg = fmaxf( fminf( angleDeg, PAN_MAX_DEG ), -PAN_MAX_DEG ); + + /* compute the panning gains from the mapped azimuth using a look-up table */ + pos = ( angleDeg + PAN_MAX_DEG ) * ONE_OVER_PAN_STEP_DEG; /* ideal floating index */ + idx = (int16_t) roundf( pos ); + + idx = max( 0, min( idx, OMASA_PAN_TBL_LEN - 1 ) ); + + panningGains[0] = ivas_tan_panning_gain_tbl[idx]; + panningGains[1] = ivas_tan_panning_gain_tbl[OMASA_PAN_TBL_LEN - 1 - idx]; + } +#else float aziRad, eleRad; float y, mappedX, aziRadMapped, A, A2, A3; const float LsAngleRad = 30.0f * PI_OVER_180; @@ -524,6 +587,7 @@ void ivas_get_stereo_panning_gains( panningGains[0] = sqrtf( A3 ); panningGains[1] = sqrtf( 1.0f - A3 ); } +#endif return; } diff --git a/lib_com/ivas_rom_com.c b/lib_com/ivas_rom_com.c index 75098e78f5..039dee13c5 100644 --- a/lib_com/ivas_rom_com.c +++ b/lib_com/ivas_rom_com.c @@ -2869,6 +2869,133 @@ const float dct12[12*12]= 0.2887f, -0.4048f, 0.3943f, -0.3772f, 0.3536f, -0.3239f, 0.2887f, -0.2485f, 0.2041f, -0.1562f, 0.1057f, -0.0533f }; +#ifdef NONBE_1380_OMASA_BUILD_DIFF +const float ivas_tan_panning_gain_tbl[OMASA_PAN_TBL_LEN] = +{ + 0.0000000f, 0.0020142f, 0.0040283f, 0.0060425f, 0.0080872f, + 0.0101013f, 0.0121460f, 0.0141907f, 0.0162354f, 0.0182800f, + 0.0203552f, 0.0223999f, 0.0244751f, 0.0265198f, 0.0285950f, + 0.0306702f, 0.0327454f, 0.0348206f, 0.0369263f, 0.0390015f, + 0.0411072f, 0.0432129f, 0.0453186f, 0.0474243f, 0.0495300f, + 0.0516357f, 0.0537415f, 0.0558777f, 0.0580139f, 0.0601196f, + 0.0622559f, 0.0643921f, 0.0665588f, 0.0686951f, 0.0708313f, + 0.0729980f, 0.0751648f, 0.0773315f, 0.0794983f, 0.0816650f, + 0.0838318f, 0.0859985f, 0.0881958f, 0.0903625f, 0.0925598f, + 0.0947571f, 0.0969543f, 0.0991516f, 0.1013489f, 0.1035767f, + 0.1057739f, 0.1080017f, 0.1102295f, 0.1124573f, 0.1146851f, + 0.1169128f, 0.1191406f, 0.1213684f, 0.1236267f, 0.1258850f, + 0.1281128f, 0.1303711f, 0.1326294f, 0.1348877f, 0.1371765f, + 0.1394348f, 0.1416931f, 0.1439819f, 0.1462708f, 0.1485596f, + 0.1508484f, 0.1531372f, 0.1554260f, 0.1577148f, 0.1600342f, + 0.1623230f, 0.1646423f, 0.1669617f, 0.1692505f, 0.1715698f, + 0.1739197f, 0.1762390f, 0.1785583f, 0.1809082f, 0.1832275f, + 0.1855774f, 0.1879272f, 0.1902466f, 0.1925964f, 0.1949768f, + 0.1973267f, 0.1996765f, 0.2020569f, 0.2044067f, 0.2067871f, + 0.2091370f, 0.2115173f, 0.2138977f, 0.2162781f, 0.2186584f, + 0.2210693f, 0.2234497f, 0.2258301f, 0.2282410f, 0.2306519f, + 0.2330322f, 0.2354431f, 0.2378540f, 0.2402649f, 0.2426758f, + 0.2450867f, 0.2475281f, 0.2499390f, 0.2523499f, 0.2547913f, + 0.2572327f, 0.2596436f, 0.2620850f, 0.2645264f, 0.2669678f, + 0.2694092f, 0.2718506f, 0.2742920f, 0.2767639f, 0.2792053f, + 0.2816772f, 0.2841187f, 0.2865906f, 0.2890320f, 0.2915039f, + 0.2939758f, 0.2964478f, 0.2989197f, 0.3013916f, 0.3038635f, + 0.3063354f, 0.3088074f, 0.3113098f, 0.3137817f, 0.3162537f, + 0.3187561f, 0.3212280f, 0.3237305f, 0.3262329f, 0.3287048f, + 0.3312073f, 0.3337097f, 0.3362122f, 0.3387146f, 0.3412170f, + 0.3437195f, 0.3462219f, 0.3487244f, 0.3512268f, 0.3537292f, + 0.3562317f, 0.3587646f, 0.3612671f, 0.3637695f, 0.3663025f, + 0.3688049f, 0.3713074f, 0.3738403f, 0.3763428f, 0.3788757f, + 0.3814087f, 0.3839111f, 0.3864441f, 0.3889465f, 0.3914795f, + 0.3940125f, 0.3965149f, 0.3990479f, 0.4015808f, 0.4041138f, + 0.4066162f, 0.4091492f, 0.4116821f, 0.4142151f, 0.4167175f, + 0.4192505f, 0.4217834f, 0.4243164f, 0.4268188f, 0.4293518f, + 0.4318848f, 0.4344177f, 0.4369202f, 0.4394531f, 0.4419861f, + 0.4445190f, 0.4470215f, 0.4495544f, 0.4520874f, 0.4545898f, + 0.4571228f, 0.4596558f, 0.4621582f, 0.4646912f, 0.4671936f, + 0.4697266f, 0.4722290f, 0.4747620f, 0.4772644f, 0.4797668f, + 0.4822998f, 0.4848022f, 0.4873047f, 0.4898071f, 0.4923096f, + 0.4948425f, 0.4973450f, 0.4998474f, 0.5023193f, 0.5048218f, + 0.5073242f, 0.5098267f, 0.5123291f, 0.5148010f, 0.5173035f, + 0.5197754f, 0.5222778f, 0.5247498f, 0.5272217f, 0.5297241f, + 0.5321960f, 0.5346680f, 0.5371399f, 0.5396118f, 0.5420837f, + 0.5445251f, 0.5469971f, 0.5494385f, 0.5519104f, 0.5543518f, + 0.5568237f, 0.5592651f, 0.5617065f, 0.5641479f, 0.5665894f, + 0.5690002f, 0.5714417f, 0.5738831f, 0.5762939f, 0.5787048f, + 0.5811462f, 0.5835571f, 0.5859680f, 0.5883789f, 0.5907593f, + 0.5931702f, 0.5955505f, 0.5979614f, 0.6003418f, 0.6027222f, + 0.6051025f, 0.6074829f, 0.6098633f, 0.6122131f, 0.6145935f, + 0.6169434f, 0.6192932f, 0.6216431f, 0.6239929f, 0.6263428f, + 0.6286621f, 0.6309814f, 0.6333313f, 0.6356506f, 0.6379700f, + 0.6402588f, 0.6425781f, 0.6448669f, 0.6471863f, 0.6494751f, + 0.6517639f, 0.6540527f, 0.6563110f, 0.6585999f, 0.6608582f, + 0.6631165f, 0.6653748f, 0.6676025f, 0.6698608f, 0.6720886f, + 0.6743164f, 0.6765442f, 0.6787720f, 0.6809998f, 0.6831970f, + 0.6853943f, 0.6875916f, 0.6897888f, 0.6919861f, 0.6941528f, + 0.6963196f, 0.6984863f, 0.7006531f, 0.7027893f, 0.7049561f, + 0.7070923f, 0.7092285f, 0.7113647f, 0.7134705f, 0.7155762f, + 0.7177124f, 0.7197876f, 0.7218933f, 0.7239685f, 0.7260742f, + 0.7281494f, 0.7301941f, 0.7322693f, 0.7343140f, 0.7363586f, + 0.7384033f, 0.7404480f, 0.7424622f, 0.7444763f, 0.7464905f, + 0.7485046f, 0.7504883f, 0.7524719f, 0.7544556f, 0.7564392f, + 0.7583923f, 0.7603455f, 0.7622986f, 0.7642517f, 0.7662048f, + 0.7681274f, 0.7700500f, 0.7719421f, 0.7738647f, 0.7757568f, + 0.7776489f, 0.7795410f, 0.7814026f, 0.7832642f, 0.7851257f, + 0.7869873f, 0.7888184f, 0.7906494f, 0.7924805f, 0.7943115f, + 0.7961121f, 0.7979126f, 0.7997131f, 0.8015137f, 0.8032837f, + 0.8050537f, 0.8068237f, 0.8085632f, 0.8103027f, 0.8120422f, + 0.8137817f, 0.8154907f, 0.8171997f, 0.8189087f, 0.8206177f, + 0.8222961f, 0.8239746f, 0.8256531f, 0.8273010f, 0.8289795f, + 0.8306274f, 0.8322449f, 0.8338928f, 0.8355103f, 0.8370972f, + 0.8387146f, 0.8403015f, 0.8418884f, 0.8434753f, 0.8450317f, + 0.8465881f, 0.8481445f, 0.8497009f, 0.8512268f, 0.8527527f, + 0.8542786f, 0.8557739f, 0.8572693f, 0.8587646f, 0.8602600f, + 0.8617249f, 0.8631897f, 0.8646545f, 0.8660889f, 0.8675232f, + 0.8689575f, 0.8703918f, 0.8717957f, 0.8731995f, 0.8746033f, + 0.8759766f, 0.8773804f, 0.8787231f, 0.8800964f, 0.8814392f, + 0.8827820f, 0.8841248f, 0.8854675f, 0.8867798f, 0.8880920f, + 0.8893738f, 0.8906860f, 0.8919678f, 0.8932190f, 0.8945007f, + 0.8957520f, 0.8970032f, 0.8982544f, 0.8994751f, 0.9006958f, + 0.9019165f, 0.9031067f, 0.9042969f, 0.9054871f, 0.9066772f, + 0.9078369f, 0.9089966f, 0.9101562f, 0.9113159f, 0.9124451f, + 0.9135742f, 0.9147034f, 0.9158020f, 0.9169006f, 0.9179993f, + 0.9190979f, 0.9201660f, 0.9212341f, 0.9223022f, 0.9233398f, + 0.9243774f, 0.9254150f, 0.9264526f, 0.9274597f, 0.9284973f, + 0.9294739f, 0.9304810f, 0.9314575f, 0.9324341f, 0.9334106f, + 0.9343872f, 0.9353333f, 0.9362793f, 0.9371948f, 0.9381409f, + 0.9390564f, 0.9399719f, 0.9408569f, 0.9417725f, 0.9426575f, + 0.9435425f, 0.9443970f, 0.9452820f, 0.9461365f, 0.9469910f, + 0.9478149f, 0.9486389f, 0.9494629f, 0.9502869f, 0.9511108f, + 0.9519043f, 0.9526978f, 0.9534912f, 0.9542542f, 0.9550171f, + 0.9557800f, 0.9565430f, 0.9573059f, 0.9580383f, 0.9587708f, + 0.9595032f, 0.9602051f, 0.9609070f, 0.9616089f, 0.9623108f, + 0.9630127f, 0.9636841f, 0.9643555f, 0.9650269f, 0.9656677f, + 0.9663391f, 0.9669800f, 0.9676208f, 0.9682312f, 0.9688721f, + 0.9694824f, 0.9700928f, 0.9707031f, 0.9712830f, 0.9718628f, + 0.9724426f, 0.9730225f, 0.9735718f, 0.9741516f, 0.9747009f, + 0.9752502f, 0.9757690f, 0.9763184f, 0.9768372f, 0.9773560f, + 0.9778748f, 0.9783630f, 0.9788818f, 0.9793701f, 0.9798279f, + 0.9803162f, 0.9808044f, 0.9812622f, 0.9817200f, 0.9821777f, + 0.9826050f, 0.9830627f, 0.9834900f, 0.9839172f, 0.9843445f, + 0.9847412f, 0.9851379f, 0.9855652f, 0.9859619f, 0.9863281f, + 0.9867249f, 0.9870911f, 0.9874573f, 0.9878235f, 0.9881897f, + 0.9885559f, 0.9888916f, 0.9892273f, 0.9895630f, 0.9898987f, + 0.9902039f, 0.9905396f, 0.9908447f, 0.9911499f, 0.9914551f, + 0.9917297f, 0.9920349f, 0.9923096f, 0.9925842f, 0.9928589f, + 0.9931335f, 0.9933777f, 0.9936523f, 0.9938965f, 0.9941406f, + 0.9943848f, 0.9945984f, 0.9948425f, 0.9950562f, 0.9952698f, + 0.9954834f, 0.9956970f, 0.9958801f, 0.9960938f, 0.9962769f, + 0.9964600f, 0.9966431f, 0.9968262f, 0.9969788f, 0.9971619f, + 0.9973145f, 0.9974670f, 0.9976196f, 0.9977722f, 0.9978943f, + 0.9980469f, 0.9981689f, 0.9982910f, 0.9984131f, 0.9985352f, + 0.9986572f, 0.9987488f, 0.9988708f, 0.9989624f, 0.9990540f, + 0.9991455f, 0.9992371f, 0.9992981f, 0.9993896f, 0.9994507f, + 0.9995117f, 0.9995728f, 0.9996338f, 0.9996948f, 0.9997253f, + 0.9997864f, 0.9998169f, 0.9998474f, 0.9998779f, 0.9999084f, + 0.9999390f, 0.9999390f, 0.9999695f, 0.9999695f, 0.9999695f, + 0.9999695f +}; +#endif + /*----------------------------------------------------------------------------------* * ISM ROM tables *----------------------------------------------------------------------------------*/ diff --git a/lib_com/ivas_rom_com.h b/lib_com/ivas_rom_com.h index b90a30699f..9ef4737c59 100644 --- a/lib_com/ivas_rom_com.h +++ b/lib_com/ivas_rom_com.h @@ -336,6 +336,9 @@ extern const float dct4[]; extern const float dct5[]; extern const float dct8[]; extern const float dct12[]; +#ifdef NONBE_1380_OMASA_BUILD_DIFF +extern const float ivas_tan_panning_gain_tbl[]; +#endif /*----------------------------------------------------------------------------------* * ISM ROM tables diff --git a/lib_com/options.h b/lib_com/options.h index 4c31c1480c..560ab58b0a 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -186,6 +186,8 @@ #define NONBE_1412_AVOID_ROUNDING_AZ_ELEV /* FhG: Avoid rounding when passing azimuth and elevation to efap_determine_gains() */ #define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ +#define NONBE_1380_OMASA_BUILD_DIFF /* Nokia: Fix for issue #1380: Large differences in OMASA output between Debug and Release builds */ + /* ##################### End NON-BE switches ########################### */ /* ################## End DEVELOPMENT switches ######################### */ -- GitLab From cd0d4f93db294d420d9d12e1516faf19f416b4f6 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Mon, 27 Oct 2025 13:31:17 +0200 Subject: [PATCH 298/340] Satisfy clang format --- lib_com/ivas_omasa_com.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib_com/ivas_omasa_com.c b/lib_com/ivas_omasa_com.c index 2908ffd31c..a66e0e5c6b 100644 --- a/lib_com/ivas_omasa_com.c +++ b/lib_com/ivas_omasa_com.c @@ -64,7 +64,6 @@ #ifdef NONBE_1380_OMASA_BUILD_DIFF #define PAN_MAX_DEG 30.0f #define ONE_OVER_PAN_STEP_DEG 10.0f - #define SIN_LS_ANGLE 0.5f /* sinf( 30.0f * PI_OVER_180 ) */ #endif /*--------------------------------------------------------------- -- GitLab From 1b1e364dadeee7a77f3e414aba727300a1cb3843 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Mon, 27 Oct 2025 16:49:22 +0200 Subject: [PATCH 299/340] Save PI data and apply the saved data --- apps/decoder.c | 6 ++- lib_dec/lib_dec.c | 57 +++++++++++++++++------ lib_dec/lib_dec.h | 3 +- lib_util/ivas_rtp_file.c | 74 +++++++++++++++++++++++++++++ lib_util/ivas_rtp_file.h | 1 + lib_util/ivas_rtp_pi_data.h | 93 +++++++++++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 17 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 411bf0a218..c58c7a572f 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3591,9 +3591,11 @@ static ivas_error decodeVoIP( numPiData++; } - if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData ) ) != IVAS_ERR_OK ) + IVAS_RTP_SavePiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData, &ivasRtp.piDataSaved ); + + if ( ( error = IVAS_RTP_ApplySavedPiData( hIvasDec, &ivasRtp.piDataSaved ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + fprintf( stderr, "\nError in IVAS_RTP_ApplySavedPiData: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 1e34dcd1b0..b24d23bb9d 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -43,10 +43,15 @@ #include "jbm_pcmdsp_fifo.h" #include #include +#ifdef IVAS_RTPDUMP +#include +#endif #ifdef DEBUGGING #include "debug.h" +#ifndef IVAS_RTPDUMP #include #endif +#endif #include "wmc_auto.h" /*---------------------------------------------------------------------* @@ -5623,9 +5628,8 @@ static void IVAS_DEC_setDiegeticInputPI( } #endif -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) +void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData, PIDATA_SAVED *piDataSaved ) { - ivas_error error = IVAS_ERR_OK; while ( numPiData-- ) { uint32_t piDataType = piData->data.noPiData.piDataType; @@ -5633,25 +5637,23 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui { case IVAS_PI_SCENE_ORIENTATION: { - IVAS_QUATERNION *quat = &piData->data.scene.orientation; - DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + memcpy( &piDataSaved->scene, &piData->data.scene, piData->data.scene.size ); + piDataSaved->sceneSaved = true; } break; case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: { - IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; - DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + memcpy( &piDataSaved->deviceCompensated, &piData->data.deviceCompensated, piData->data.deviceCompensated.size ); + piDataSaved->deviceCompensatedSaved = true; } break; #ifdef RTP_S4_251135_CR26253_0016_REV1 case IVAS_PI_DIEGETIC_TYPE: { - DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); - IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); + memcpy( &piDataSaved->digeticIndicator, &piData->data.digeticIndicator, piData->data.digeticIndicator.size ); + piDataSaved->digeticIndicatorSaved = true; } break; #endif @@ -5662,12 +5664,39 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui } break; } - if ( error != IVAS_ERR_OK ) - { - return error; - } piData++; } +} + +ivas_error IVAS_RTP_ApplySavedPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_SAVED *piDataSaved ) +{ + ivas_error error = IVAS_ERR_OK; + + if ( piDataSaved->sceneSaved && !piDataSaved->sceneApplied ) + { + IVAS_QUATERNION *quat = &piDataSaved->scene.orientation; + DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + piDataSaved->sceneApplied = true; + } + + if ( piDataSaved->deviceCompensatedSaved && !piDataSaved->deviceCompensatedApplied ) + { + IVAS_QUATERNION *quat = &piDataSaved->deviceCompensated.orientation; + DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); + piDataSaved->deviceCompensatedApplied = true; + } + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( piDataSaved->digeticIndicatorSaved && !piDataSaved->digeticIndicatorApplied ) + { + DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piDataSaved->digeticIndicator.isDiegetic[0], piDataSaved->digeticIndicator.isDiegetic[1], piDataSaved->digeticIndicator.isDiegetic[2], piDataSaved->digeticIndicator.isDiegetic[3], piDataSaved->digeticIndicator.isDiegetic[4] ); + IVAS_DEC_setDiegeticInputPI( hIvasDec, piDataSaved->digeticIndicator.isDiegetic ); + piDataSaved->digeticIndicatorApplied = true; + } +#endif + return error; } #endif diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 6331f647f7..ef3e135798 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -559,7 +559,8 @@ void IVAS_DEC_PrintDisclaimer( #ifdef IVAS_RTPDUMP #include "ivas_rtp_pi_data.h" -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); +void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData, PIDATA_SAVED *piDataSaved ); +ivas_error IVAS_RTP_ApplySavedPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_SAVED *piDataSaved ); #endif /* clang-format on */ diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index eab863a8cb..77e6c37577 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -146,6 +146,78 @@ static const char *const PiDataNames[IVAS_PI_MAX_ID] = { "RESERVED30", "NO_DATA" }; +static void resetSavedPiDataAppliance( + PIDATA_SAVED *piDataSaved +) +{ + piDataSaved->sceneApplied = false; + piDataSaved->deviceCompensatedApplied = false; + piDataSaved->deviceUnCompensatedApplied = false; + piDataSaved->acousticEnvApplied = false; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + piDataSaved->audioDescApplied = false; + piDataSaved->ismNumApplied = false; + piDataSaved->ismIdApplied = false; + piDataSaved->ismGainApplied = false; + piDataSaved->ismOrientationApplied = false; + piDataSaved->ismPositionApplied = false; + piDataSaved->ismAttenuationApplied = false; + piDataSaved->ismDirectivityApplied = false; + piDataSaved->digeticIndicatorApplied = false; + piDataSaved->dynSuppressionIndicationApplied = false; + piDataSaved->focusIndicationApplied = false; + + piDataSaved->playbackOrientationApplied = false; + piDataSaved->headOrientationApplied = false; + piDataSaved->listnerPositionApplied = false; + piDataSaved->dynSuppressionRequestApplied = false; + piDataSaved->focusRequestApplied = false; + piDataSaved->piLatencyApplied = false; + piDataSaved->ismEditIdApplied = false; + piDataSaved->ismEditGainApplied = false; + piDataSaved->ismEditOrientationApplied = false; + piDataSaved->ismEditPositionApplied = false; + piDataSaved->ismEditDirectionApplied = false; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ +} + +static void initSavedPiData( + PIDATA_SAVED *piDataSaved +) +{ + piDataSaved->sceneSaved = false; + piDataSaved->deviceCompensatedSaved = false; + piDataSaved->deviceUnCompensatedSaved = false; + piDataSaved->acousticEnvSaved = false; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + piDataSaved->audioDescSaved = false; + piDataSaved->ismNumSaved = false; + piDataSaved->ismIdSaved = false; + piDataSaved->ismGainSaved = false; + piDataSaved->ismOrientationSaved = false; + piDataSaved->ismPositionSaved = false; + piDataSaved->ismAttenuationSaved = false; + piDataSaved->ismDirectivitySaved = false; + piDataSaved->digeticIndicatorSaved = false; + piDataSaved->dynSuppressionIndicationSaved = false; + piDataSaved->focusIndicationSaved = false; + + piDataSaved->playbackOrientationSaved = false; + piDataSaved->headOrientationSaved = false; + piDataSaved->listnerPositionSaved = false; + piDataSaved->dynSuppressionRequestSaved = false; + piDataSaved->focusRequestSaved = false; + piDataSaved->piLatencySaved = false; + piDataSaved->ismEditIdSaved = false; + piDataSaved->ismEditGainSaved = false; + piDataSaved->ismEditOrientationSaved = false; + piDataSaved->ismEditPositionSaved = false; + piDataSaved->ismEditDirectionSaved = false; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + + resetSavedPiDataAppliance( piDataSaved ); +} + void IVAS_RTP_LogPiData( FILE *f_piDataOut, /* i/o : Output json file handle to dump PI data for debug/test */ const PIDATA_TS *piData, /* i : PI Data + Timestamp array containing all PI data in current packet */ @@ -749,6 +821,7 @@ ivas_error IVAS_RTP_READER_Init( } } } + initSavedPiData( &rtp->piDataSaved ); return error; } @@ -853,6 +926,7 @@ ivas_error IVAS_RTP_ReadNextFrame( rtp->nReadPiData = 0; rtp->nProcPiData = 0; + resetSavedPiDataAppliance( &rtp->piDataSaved ); /* Pre-read all PI data */ while ( rtp->numPiDataInPacket != 0 ) diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index bd8d265873..5e4472e7d7 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -43,6 +43,7 @@ typedef struct { uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; + PIDATA_SAVED piDataSaved; IVAS_RTP_FILE_HANDLE hRtpFile; FILE *f_piDataOut; diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index dc4c7f8baf..ca493abc63 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -467,6 +467,99 @@ typedef struct uint32_t timestamp; } PIDATA_TS; +typedef struct +{ + bool sceneSaved; + bool deviceCompensatedSaved; + bool deviceUnCompensatedSaved; + bool acousticEnvSaved; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool audioDescSaved; + bool ismNumSaved; + bool ismIdSaved; + bool ismGainSaved; + bool ismOrientationSaved; + bool ismPositionSaved; + bool ismAttenuationSaved; + bool ismDirectivitySaved; + bool digeticIndicatorSaved; + bool dynSuppressionIndicationSaved; + bool focusIndicationSaved; + + bool playbackOrientationSaved; + bool headOrientationSaved; + bool listnerPositionSaved; + bool dynSuppressionRequestSaved; + bool focusRequestSaved; + bool piLatencySaved; + bool ismEditIdSaved; + bool ismEditGainSaved; + bool ismEditOrientationSaved; + bool ismEditPositionSaved; + bool ismEditDirectionSaved; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + + bool sceneApplied; + bool deviceCompensatedApplied; + bool deviceUnCompensatedApplied; + bool acousticEnvApplied; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool audioDescApplied; + bool ismNumApplied; + bool ismIdApplied; + bool ismGainApplied; + bool ismOrientationApplied; + bool ismPositionApplied; + bool ismAttenuationApplied; + bool ismDirectivityApplied; + bool digeticIndicatorApplied; + bool dynSuppressionIndicationApplied; + bool focusIndicationApplied; + + bool playbackOrientationApplied; + bool headOrientationApplied; + bool listnerPositionApplied; + bool dynSuppressionRequestApplied; + bool focusRequestApplied; + bool piLatencyApplied; + bool ismEditIdApplied; + bool ismEditGainApplied; + bool ismEditOrientationApplied; + bool ismEditPositionApplied; + bool ismEditDirectionApplied; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + + IVAS_PIDATA_ORIENTATION scene; + IVAS_PIDATA_ORIENTATION deviceCompensated; + IVAS_PIDATA_ORIENTATION deviceUnCompensated; + IVAS_PIDATA_ACOUSTIC_ENV acousticEnv; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_PIDATA_AUDIO_DESC audioDesc; + IVAS_PIDATA_ISM_NUM ismNum; + IVAS_PIDATA_ISM_ID ismId; + IVAS_PIDATA_ISM_GAIN ismGain; + IVAS_PIDATA_ISM_ORIENTATION ismOrientation; + IVAS_PIDATA_ISM_POSITION ismPosition; + IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; + IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; + IVAS_PIDATA_DIEGETIC digeticIndicator; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionIndication; + IVAS_PIDATA_AUDIO_FOCUS focusIndication; + + IVAS_PIDATA_ORIENTATION playbackOrientation; + IVAS_PIDATA_ORIENTATION headOrientation; + IVAS_PIDATA_LISTENER_POSITION listnerPosition; + IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionRequest; + IVAS_PIDATA_AUDIO_FOCUS focusRequest; + IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; + IVAS_PIDATA_ISM_EDIT_ID ismEditId; + IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; + IVAS_PIDATA_ISM_EDIT_ORIENTATION ismEditOrientation; + IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; + IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ +} PIDATA_SAVED; + #endif /* IVAS_RTPDUMP */ #endif /* IVAS_RTP_PI_DATA_H */ -- GitLab From 6492937c823602058f174f29aa3aa51d6a316cae Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 27 Oct 2025 15:51:15 +0100 Subject: [PATCH 300/340] prevent optimizations around trig functions to preserve BE for -O0/-O2 --- lib_com/options.h | 1 + lib_enc/ivas_stereo_dft_enc.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index 4c31c1480c..c33b9eba21 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -185,6 +185,7 @@ #define NONBE_1412_AVOID_ROUNDING_AZ_ELEV /* FhG: Avoid rounding when passing azimuth and elevation to efap_determine_gains() */ #define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ +#define NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS /* FhG: fix non-BE in DFT stereo encoder between optimization levels */ /* ##################### End NON-BE switches ########################### */ diff --git a/lib_enc/ivas_stereo_dft_enc.c b/lib_enc/ivas_stereo_dft_enc.c index 0ae36724d8..b583690cf3 100755 --- a/lib_enc/ivas_stereo_dft_enc.c +++ b/lib_enc/ivas_stereo_dft_enc.c @@ -1316,12 +1316,24 @@ void stereo_dft_enc_process( if ( hStereoDft->hItd->deltaItd[k_offset] != 0 && hStereoDft->hConfig->dmx_active ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS + /* Note: this variable is used as an in-between step when passing the angle to the trig functions. + * this keeps the compilter from applying optimizations at higher optimizaton levels which can break bit-exactness */ + volatile float alpha_tmp; +#endif /*time shift channels*/ alpha = -2.0f * EVS_PI * hStereoDft->hItd->deltaItd[k_offset] / hStereoDft->NFFT; c = 1.f; /*cos(0)*/ s = 0.f; /*sin(0)*/ +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS + + alpha_tmp = alpha; + c1 = cosf( alpha_tmp ); + s1 = sinf( alpha_tmp ); +#else c1 = cosf( alpha ); s1 = sinf( alpha ); +#endif if ( alpha >= 0 ) { -- GitLab From acabc815b17689b0a78e0bfdbce93b889913b9c0 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Mon, 27 Oct 2025 16:46:38 +0100 Subject: [PATCH 301/340] change md sync delay in ext renderer to integer type --- apps/renderer.c | 11 +++++++++++ lib_com/options.h | 1 + lib_rend/lib_rend.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- lib_rend/lib_rend.h | 4 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/apps/renderer.c b/apps/renderer.c index 69c01c28c6..e434c0ca2a 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -33,6 +33,9 @@ #include "lib_rend.h" #include #include +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER +#include +#endif #include #include "audio_file_reader.h" #include "audio_file_writer.h" @@ -189,7 +192,11 @@ typedef struct float lfeConfigElevation; bool lfeCustomRoutingEnabled; char inLfePanningMatrixFile[RENDERER_MAX_CLI_ARG_LENGTH]; +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + int16_t syncMdDelay; +#else float syncMdDelay; +#endif IVAS_RENDER_FRAMESIZE render_framesize; uint16_t directivityPatternId[RENDERER_MAX_ISM_INPUTS]; AcousticEnvironmentSequence aeSequence; @@ -2844,7 +2851,11 @@ static void parseOption( case CmdLnOptionId_syncMdDelay: assert( numOptionValues == 1 ); /* Metadata Delay to sync with audio delay in ms */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + args->syncMdDelay = (int16_t) strtol( optionValues[0], NULL, 10 ); +#else args->syncMdDelay = strtof( optionValues[0], NULL ); +#endif break; default: assert( 0 && "This should be unreachable - all command line options should be explicitly handled." ); diff --git a/lib_com/options.h b/lib_com/options.h index 4c31c1480c..b92b01a6f0 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -169,6 +169,7 @@ #define FIX_1119_SPLIT_RENDERING_VOIP /* FhG: Add split rendering support to decoder in VoIP mode */ #define TMP_1342_WORKAROUND_DEC_FLUSH_BROKEN_IN_SR /* FhG: Temporary workaround for incorrect implementation of decoder flush with split rendering */ #define FIX_1413_IGF_INIT_PRINTOUT /* FhG: use correct variable for IGF initiliazation */ +#define RENDERER_MD_SYNC_DELAY_TO_INTEGER /* FhG: change data type of metadata sync delay in ext renderer to int16_t for better BASOP portability (and nicer code) */ /* #################### End BE switches ################################## */ diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index bba3fbb187..5df2ea9230 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -44,6 +44,9 @@ #include #include #include "wmc_auto.h" +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER +#include +#endif /*-------------------------------------------------------------------* @@ -123,7 +126,11 @@ typedef struct OMASA_ANA_HANDLE hOMasa; uint16_t total_num_objects; int16_t object_id; +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + int16_t ism_metadata_delay_ms; +#else float ism_metadata_delay_ms; +#endif } input_ism; typedef struct @@ -1517,7 +1524,11 @@ static ivas_error alignInputDelay( if ( getAudioConfigType( inputBase->inConfig ) == IVAS_REND_AUDIO_CONFIG_TYPE_OBJECT_BASED ) { inputIsm = (input_ism *) inputBase; +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + inputIsm->ism_metadata_delay_ms = (int16_t) roundf( inputIsm->ism_metadata_delay_ms + maxGlobalDelayNs / 1e6f / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#else inputIsm->ism_metadata_delay_ms = maxGlobalDelayNs / 1e6f; +#endif } } } @@ -5481,14 +5492,22 @@ static ivas_error renderIsmToBinaural( { float tmpTDRendBuffer[MAX_OUTPUT_CHANNELS][L_FRAME48k]; ivas_error error; +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; +#endif push_wmops( "renderIsmToBinaural" ); +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#endif copyBufferTo2dArray( ismInput->base.inputBuffer, tmpTDRendBuffer ); +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, ismInput->ism_metadata_delay_ms, +#else if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, ism_md_subframe_update_ext, +#endif *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpTDRendBuffer ) ) != IVAS_ERR_OK ) { return error; @@ -5684,17 +5703,25 @@ static ivas_error renderIsmToBinauralReverb( { float tmpRendBuffer[MAX_OUTPUT_CHANNELS][L_FRAME48k]; ivas_error error; +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; +#endif push_wmops( "renderIsmToBinauralRoom" ); +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#endif copyBufferTo2dArray( ismInput->base.inputBuffer, tmpRendBuffer ); if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + ismInput->ism_metadata_delay_ms, *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpRendBuffer ) ) != IVAS_ERR_OK ) +#else ism_md_subframe_update_ext, *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpRendBuffer ) ) != IVAS_ERR_OK ) +#endif { return error; } @@ -5861,16 +5888,20 @@ static ivas_error renderIsmToSplitBinaural( float tmpBinaural_CldfbIm[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; int16_t output_frame = ismInput->base.inputBuffer.config.numSamplesPerChannel; COMBINED_ORIENTATION_HANDLE pCombinedOrientationData; +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; +#endif push_wmops( "renderIsmToSplitBinaural" ); pSplitRendWrapper = ismInput->base.ctx.pSplitRendWrapper; pMultiBinPoseData = &pSplitRendWrapper->multiBinPoseData; +#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#endif pCombinedOrientationData = *ismInput->base.ctx.pCombinedOrientationData; if ( pMultiBinPoseData->poseCorrectionMode == ISAR_SPLIT_REND_POSE_CORRECTION_MODE_CLDFB ) @@ -5920,7 +5951,11 @@ static ivas_error renderIsmToSplitBinaural( /* Render */ if ( ( error = ivas_td_binaural_renderer_ext( ( pos_idx == 0 ) ? &ismInput->tdRendWrapper : &ismInput->splitTdRendWrappers[pos_idx - 1], ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + NULL, ismInput->ism_metadata_delay_ms, *ismInput->base.ctx.pOutSampleRate, output_frame, tmpProcessing ) ) != IVAS_ERR_OK ) +#else NULL, ism_md_subframe_update_ext, *ismInput->base.ctx.pOutSampleRate, output_frame, tmpProcessing ) ) != IVAS_ERR_OK ) +#endif { return error; } @@ -7685,7 +7720,11 @@ ivas_error IVAS_REND_SetTotalNumberOfObjects( ivas_error IVAS_REND_SetIsmMetadataDelay( IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ - const float sync_md_delay /* i : ISM Metadata Delay in ms to sync with audio delay */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + const int16_t sync_md_delay /* i : ISM Metadata Delay in ms to sync with audio delay */ +#else + const float sync_md_delay /* i : ISM Metadata Delay in ms to sync with audio delay */ +#endif ) { int16_t i; @@ -7697,7 +7736,11 @@ ivas_error IVAS_REND_SetIsmMetadataDelay( for ( i = 0; i < RENDERER_MAX_ISM_INPUTS; ++i ) { +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + hIvasRend->inputsIsm[i].ism_metadata_delay_ms = (int16_t) roundf( sync_md_delay / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); +#else hIvasRend->inputsIsm[i].ism_metadata_delay_ms = sync_md_delay; +#endif } return IVAS_ERR_OK; diff --git a/lib_rend/lib_rend.h b/lib_rend/lib_rend.h index 2e635ad479..4d84f194f9 100644 --- a/lib_rend/lib_rend.h +++ b/lib_rend/lib_rend.h @@ -371,7 +371,11 @@ ivas_error IVAS_REND_SetTotalNumberOfObjects( ivas_error IVAS_REND_SetIsmMetadataDelay( IVAS_REND_HANDLE hIvasRend, /* i/o: IVAS renderer handle */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + const int16_t sync_md_delay /* i : Metadata Delay in ms to sync with audio delay */ +#else const float sync_md_delay /* i : Metadata Delay in ms to sync with audio delay */ +#endif ); ivas_error IVAS_REND_GetNumAllObjects( -- GitLab From 139ee0c774669b706af654556133fa7a5c8b6396 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Mon, 27 Oct 2025 16:19:17 +1100 Subject: [PATCH 302/340] Add option to regenerate encoder references * Format with black Signed-off-by: Ripinder Singh --- scripts/ivas_conformance/runConformance.py | 61 ++++++++++++++++------ 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 9df23416c8..7e03f3239d 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -59,6 +59,7 @@ class MLDConformance: self.CutBins = dict() self.Commands = dict() self.multiprocessing = not args.no_multi_processing + self.regenEncRefs = args.regenerate_enc_refs self.dryrun = args.dryrun self.verbose = args.verbose self.executedTests = Value("i", 0) @@ -85,7 +86,9 @@ class MLDConformance: self.sampleStats = dict() for tag in MLDConformance.IVAS_Bins.keys(): self.mldcsv[tag] = os.path.join(self.outputDir, f"mld_{tag}.csv") - self.sampleStats[tag] = os.path.join(self.outputDir, f"sampleStats_{tag}.csv") + self.sampleStats[tag] = os.path.join( + self.outputDir, f"sampleStats_{tag}.csv" + ) self.setup() @@ -173,11 +176,21 @@ class MLDConformance: ), "Failed to Map Encoder Commands to Decoder Commands" def runOneEncoderTest(self, command: str): + encPytestTag = self.getEncPytestTag(command) - refCommand = self.reformatCommand(command=command, ref=True) - refEncOutput = self.getOutputFile(refCommand) - refCommand = self.setCommandExec(tag="ENC", command=refCommand, ref=True) - self.process(command=refCommand) + + if self.regenEncRefs: + refCommand = self.reformatCommand(command=command, ref=True) + refEncOutput = self.getOutputFile(refCommand) + self.process( + command=self.setCommandExec(tag="ENC", command=refCommand, ref=True) + ) + else: + refEncOutput = self.getOutputFile(command) + refEncOutput = refEncOutput.replace( + "$CUT_PATH/dut/param_file/enc/", + f"{self.testvecDir}/testv/ref/param_file/enc/", + ) # Run reference Encoder encCommandIdx = self.Commands["ENC"].index(command) @@ -207,7 +220,9 @@ class MLDConformance: ) self.process(command=" ".join(dutDecCmd)) self.process(command=" ".join(refDecCmd)) - self.mld("ENC", encPytestTag, refFile=refDecOutputFile, dutFile=dutDecOutputFile) + self.mld( + "ENC", encPytestTag, refFile=refDecOutputFile, dutFile=dutDecOutputFile + ) def runOneDecoderTest(self, tag: str, command: str): dutPytestTag = self.getPcmPytestTag(command) @@ -246,7 +261,9 @@ class MLDConformance: dutRendCmd = " ".join([self.CutBins["REND"]] + command.split()[1:]) dutRendOutputFile = self.getRendOutputFile(dutRendCmd) self.process(command=dutRendCmd) - self.mld("REND", rendPytestTag, refFile=refRendOutputFile, dutFile=dutRendOutputFile) + self.mld( + "REND", rendPytestTag, refFile=refRendOutputFile, dutFile=dutRendOutputFile + ) def getOutputFile(self, command: str): return command.split()[-1] @@ -400,10 +417,12 @@ class MLDConformance: 100.0 * np.sum(diff == 0) / refSamples.shape[0] / refSamples.shape[1] ) maxDiff = np.abs(diff).max() - rmsdB = int(10.0 * np.log10(np.average(diff**2) + np.finfo(np.float64).eps) * 10) / 10.0 + rmsdB = ( + int(10.0 * np.log10(np.average(diff**2) + np.finfo(np.float64).eps) * 10) + / 10.0 + ) return (maxDiff, rmsdB, beSamplesPercent) - def mld(self, tag, pytestTag, refFile, dutFile): mldThisFile = np.zeros(0) with tempfile.TemporaryDirectory() as tmpdir: @@ -412,7 +431,9 @@ class MLDConformance: assert ( refSamples.shape[1] == dutSamples.shape[1] ), "No of channels mismatch if ref vs cut" - maxDiff, rmsdB, beSamplesPercent = self.getSampleStats(refSamples, dutSamples) + maxDiff, rmsdB, beSamplesPercent = self.getSampleStats( + refSamples, dutSamples + ) nChans = refSamples.shape[1] @@ -454,11 +475,12 @@ class MLDConformance: with open(self.mldcsv[tag], "ab") as f: np.savetxt(f, mldThisFile, delimiter=",") with open(self.sampleStats[tag], "a") as f: - f.write(f"{pytestTag}, {maxDiff}, {rmsdB}, {beSamplesPercent}, {mldThisFile.max()}\n") - + f.write( + f"{pytestTag}, {maxDiff}, {rmsdB}, {beSamplesPercent}, {mldThisFile.max()}\n" + ) - def doAnalysis(self, selectTag = "all"): - keys = MLDConformance.IVAS_Bins.keys() if selectTag == "all" else [ selectTag ] + def doAnalysis(self, selectTag="all"): + keys = MLDConformance.IVAS_Bins.keys() if selectTag == "all" else [selectTag] for tag in keys: if os.path.exists(self.mldcsv[tag]): mdlValues = np.loadtxt(self.mldcsv[tag], delimiter=" ", dtype=float) @@ -480,6 +502,7 @@ class MLDConformance: print(f"<{tag}> Frames with MLD <= 5 : {m5} frames ({PCNT(m5)}%)") print("##########################################################\n") + if __name__ == "__main__": parser = argparse.ArgumentParser( description="Compare .wav files in two folders using mld per frame" @@ -503,6 +526,12 @@ if __name__ == "__main__": required=True, help="Path to the CUT build folder containing IVAS Encoder, Decoder, Renderer and Post Render binaries", ) + parser.add_argument( + "--regenerate_enc_refs", + default=False, + action="store_true", + help="Enable verbose printing", + ) parser.add_argument( "--verbose", default=False, @@ -548,7 +577,9 @@ if __name__ == "__main__": conformance.accumulateCommands() # import sys # sys.exit(0) - testTags = MLDConformance.IVAS_Bins.keys() if args.test_mode == "ALL" else [args.test_mode] + testTags = ( + MLDConformance.IVAS_Bins.keys() if args.test_mode == "ALL" else [args.test_mode] + ) for tag in testTags: if tag == "ISAR": # Not implemented yet -- GitLab From dc04bedd7edded390fe16318c4def3c51fa90301 Mon Sep 17 00:00:00 2001 From: rtyag Date: Tue, 28 Oct 2025 17:23:16 +1100 Subject: [PATCH 303/340] minor fix to delete cut output and recreate with every run, remove -f option with mld --- scripts/ivas_conformance/runConformance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 7e03f3239d..18b8f02ff8 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -39,6 +39,7 @@ import tempfile import sys from typing import Optional from multiprocessing import Process, Value +import shutil sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) @@ -72,6 +73,9 @@ class MLDConformance: self.scriptsDir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) self.outputDir = os.path.join(self.scriptsDir, "CUT_OUTPUTS") + if os.path.exists(self.outputDir): + shutil.rmtree(self.outputDir, ignore_errors=False) + os.mkdir(self.outputDir) self.toolsdir = os.path.join(self.scriptsDir, "tools") self.testvDir = os.path.join(self.testvecDir, "testv") self.refDir = self.testvDir @@ -456,8 +460,6 @@ class MLDConformance: writefile(dutFileMono, dutSamples[:, ch], 48000) command = [ self.mldbin, - "-f", - "20", "-o", mldFile, "-s", -- GitLab From 1e8c0e6b4f18fdc1d434ce6129cf83fb75ac3133 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Tue, 28 Oct 2025 08:46:17 +0200 Subject: [PATCH 304/340] Revert "Save PI data and apply the saved data" This reverts commit 1b1e364dadeee7a77f3e414aba727300a1cb3843. --- apps/decoder.c | 6 +-- lib_dec/lib_dec.c | 57 ++++++----------------- lib_dec/lib_dec.h | 3 +- lib_util/ivas_rtp_file.c | 74 ----------------------------- lib_util/ivas_rtp_file.h | 1 - lib_util/ivas_rtp_pi_data.h | 93 ------------------------------------- 6 files changed, 17 insertions(+), 217 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index c58c7a572f..411bf0a218 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3591,11 +3591,9 @@ static ivas_error decodeVoIP( numPiData++; } - IVAS_RTP_SavePiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData, &ivasRtp.piDataSaved ); - - if ( ( error = IVAS_RTP_ApplySavedPiData( hIvasDec, &ivasRtp.piDataSaved ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData ) ) != IVAS_ERR_OK ) { - fprintf( stderr, "\nError in IVAS_RTP_ApplySavedPiData: %s\n", IVAS_DEC_GetErrorMessage( error ) ); + fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; } diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index b24d23bb9d..1e34dcd1b0 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -43,15 +43,10 @@ #include "jbm_pcmdsp_fifo.h" #include #include -#ifdef IVAS_RTPDUMP -#include -#endif #ifdef DEBUGGING #include "debug.h" -#ifndef IVAS_RTPDUMP #include #endif -#endif #include "wmc_auto.h" /*---------------------------------------------------------------------* @@ -5628,8 +5623,9 @@ static void IVAS_DEC_setDiegeticInputPI( } #endif -void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData, PIDATA_SAVED *piDataSaved ) +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) { + ivas_error error = IVAS_ERR_OK; while ( numPiData-- ) { uint32_t piDataType = piData->data.noPiData.piDataType; @@ -5637,23 +5633,25 @@ void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t { case IVAS_PI_SCENE_ORIENTATION: { - memcpy( &piDataSaved->scene, &piData->data.scene, piData->data.scene.size ); - piDataSaved->sceneSaved = true; + IVAS_QUATERNION *quat = &piData->data.scene.orientation; + DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: { - memcpy( &piDataSaved->deviceCompensated, &piData->data.deviceCompensated, piData->data.deviceCompensated.size ); - piDataSaved->deviceCompensatedSaved = true; + IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; + DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); + error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; #ifdef RTP_S4_251135_CR26253_0016_REV1 case IVAS_PI_DIEGETIC_TYPE: { - memcpy( &piDataSaved->digeticIndicator, &piData->data.digeticIndicator, piData->data.digeticIndicator.size ); - piDataSaved->digeticIndicatorSaved = true; + DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); + IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); } break; #endif @@ -5664,39 +5662,12 @@ void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t } break; } + if ( error != IVAS_ERR_OK ) + { + return error; + } piData++; } -} - -ivas_error IVAS_RTP_ApplySavedPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_SAVED *piDataSaved ) -{ - ivas_error error = IVAS_ERR_OK; - - if ( piDataSaved->sceneSaved && !piDataSaved->sceneApplied ) - { - IVAS_QUATERNION *quat = &piDataSaved->scene.orientation; - DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - piDataSaved->sceneApplied = true; - } - - if ( piDataSaved->deviceCompensatedSaved && !piDataSaved->deviceCompensatedApplied ) - { - IVAS_QUATERNION *quat = &piDataSaved->deviceCompensated.orientation; - DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); - error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); - piDataSaved->deviceCompensatedApplied = true; - } - -#ifdef RTP_S4_251135_CR26253_0016_REV1 - if ( piDataSaved->digeticIndicatorSaved && !piDataSaved->digeticIndicatorApplied ) - { - DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piDataSaved->digeticIndicator.isDiegetic[0], piDataSaved->digeticIndicator.isDiegetic[1], piDataSaved->digeticIndicator.isDiegetic[2], piDataSaved->digeticIndicator.isDiegetic[3], piDataSaved->digeticIndicator.isDiegetic[4] ); - IVAS_DEC_setDiegeticInputPI( hIvasDec, piDataSaved->digeticIndicator.isDiegetic ); - piDataSaved->digeticIndicatorApplied = true; - } -#endif - return error; } #endif diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index ef3e135798..6331f647f7 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -559,8 +559,7 @@ void IVAS_DEC_PrintDisclaimer( #ifdef IVAS_RTPDUMP #include "ivas_rtp_pi_data.h" -void IVAS_RTP_SavePiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData, PIDATA_SAVED *piDataSaved ); -ivas_error IVAS_RTP_ApplySavedPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_SAVED *piDataSaved ); +ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); #endif /* clang-format on */ diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 77e6c37577..eab863a8cb 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -146,78 +146,6 @@ static const char *const PiDataNames[IVAS_PI_MAX_ID] = { "RESERVED30", "NO_DATA" }; -static void resetSavedPiDataAppliance( - PIDATA_SAVED *piDataSaved -) -{ - piDataSaved->sceneApplied = false; - piDataSaved->deviceCompensatedApplied = false; - piDataSaved->deviceUnCompensatedApplied = false; - piDataSaved->acousticEnvApplied = false; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - piDataSaved->audioDescApplied = false; - piDataSaved->ismNumApplied = false; - piDataSaved->ismIdApplied = false; - piDataSaved->ismGainApplied = false; - piDataSaved->ismOrientationApplied = false; - piDataSaved->ismPositionApplied = false; - piDataSaved->ismAttenuationApplied = false; - piDataSaved->ismDirectivityApplied = false; - piDataSaved->digeticIndicatorApplied = false; - piDataSaved->dynSuppressionIndicationApplied = false; - piDataSaved->focusIndicationApplied = false; - - piDataSaved->playbackOrientationApplied = false; - piDataSaved->headOrientationApplied = false; - piDataSaved->listnerPositionApplied = false; - piDataSaved->dynSuppressionRequestApplied = false; - piDataSaved->focusRequestApplied = false; - piDataSaved->piLatencyApplied = false; - piDataSaved->ismEditIdApplied = false; - piDataSaved->ismEditGainApplied = false; - piDataSaved->ismEditOrientationApplied = false; - piDataSaved->ismEditPositionApplied = false; - piDataSaved->ismEditDirectionApplied = false; -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ -} - -static void initSavedPiData( - PIDATA_SAVED *piDataSaved -) -{ - piDataSaved->sceneSaved = false; - piDataSaved->deviceCompensatedSaved = false; - piDataSaved->deviceUnCompensatedSaved = false; - piDataSaved->acousticEnvSaved = false; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - piDataSaved->audioDescSaved = false; - piDataSaved->ismNumSaved = false; - piDataSaved->ismIdSaved = false; - piDataSaved->ismGainSaved = false; - piDataSaved->ismOrientationSaved = false; - piDataSaved->ismPositionSaved = false; - piDataSaved->ismAttenuationSaved = false; - piDataSaved->ismDirectivitySaved = false; - piDataSaved->digeticIndicatorSaved = false; - piDataSaved->dynSuppressionIndicationSaved = false; - piDataSaved->focusIndicationSaved = false; - - piDataSaved->playbackOrientationSaved = false; - piDataSaved->headOrientationSaved = false; - piDataSaved->listnerPositionSaved = false; - piDataSaved->dynSuppressionRequestSaved = false; - piDataSaved->focusRequestSaved = false; - piDataSaved->piLatencySaved = false; - piDataSaved->ismEditIdSaved = false; - piDataSaved->ismEditGainSaved = false; - piDataSaved->ismEditOrientationSaved = false; - piDataSaved->ismEditPositionSaved = false; - piDataSaved->ismEditDirectionSaved = false; -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - - resetSavedPiDataAppliance( piDataSaved ); -} - void IVAS_RTP_LogPiData( FILE *f_piDataOut, /* i/o : Output json file handle to dump PI data for debug/test */ const PIDATA_TS *piData, /* i : PI Data + Timestamp array containing all PI data in current packet */ @@ -821,7 +749,6 @@ ivas_error IVAS_RTP_READER_Init( } } } - initSavedPiData( &rtp->piDataSaved ); return error; } @@ -926,7 +853,6 @@ ivas_error IVAS_RTP_ReadNextFrame( rtp->nReadPiData = 0; rtp->nProcPiData = 0; - resetSavedPiDataAppliance( &rtp->piDataSaved ); /* Pre-read all PI data */ while ( rtp->numPiDataInPacket != 0 ) diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index 5e4472e7d7..bd8d265873 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -43,7 +43,6 @@ typedef struct { uint8_t packet[NOMINAL_BUFFER_SIZE( IVAS_MAX_FRAMES_PER_RTP_PACKET )]; PIDATA_TS piData[IVAS_PI_MAX_ID * IVAS_MAX_FRAMES_PER_RTP_PACKET]; - PIDATA_SAVED piDataSaved; IVAS_RTP_FILE_HANDLE hRtpFile; FILE *f_piDataOut; diff --git a/lib_util/ivas_rtp_pi_data.h b/lib_util/ivas_rtp_pi_data.h index ca493abc63..dc4c7f8baf 100644 --- a/lib_util/ivas_rtp_pi_data.h +++ b/lib_util/ivas_rtp_pi_data.h @@ -467,99 +467,6 @@ typedef struct uint32_t timestamp; } PIDATA_TS; -typedef struct -{ - bool sceneSaved; - bool deviceCompensatedSaved; - bool deviceUnCompensatedSaved; - bool acousticEnvSaved; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - bool audioDescSaved; - bool ismNumSaved; - bool ismIdSaved; - bool ismGainSaved; - bool ismOrientationSaved; - bool ismPositionSaved; - bool ismAttenuationSaved; - bool ismDirectivitySaved; - bool digeticIndicatorSaved; - bool dynSuppressionIndicationSaved; - bool focusIndicationSaved; - - bool playbackOrientationSaved; - bool headOrientationSaved; - bool listnerPositionSaved; - bool dynSuppressionRequestSaved; - bool focusRequestSaved; - bool piLatencySaved; - bool ismEditIdSaved; - bool ismEditGainSaved; - bool ismEditOrientationSaved; - bool ismEditPositionSaved; - bool ismEditDirectionSaved; -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - - bool sceneApplied; - bool deviceCompensatedApplied; - bool deviceUnCompensatedApplied; - bool acousticEnvApplied; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - bool audioDescApplied; - bool ismNumApplied; - bool ismIdApplied; - bool ismGainApplied; - bool ismOrientationApplied; - bool ismPositionApplied; - bool ismAttenuationApplied; - bool ismDirectivityApplied; - bool digeticIndicatorApplied; - bool dynSuppressionIndicationApplied; - bool focusIndicationApplied; - - bool playbackOrientationApplied; - bool headOrientationApplied; - bool listnerPositionApplied; - bool dynSuppressionRequestApplied; - bool focusRequestApplied; - bool piLatencyApplied; - bool ismEditIdApplied; - bool ismEditGainApplied; - bool ismEditOrientationApplied; - bool ismEditPositionApplied; - bool ismEditDirectionApplied; -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ - - IVAS_PIDATA_ORIENTATION scene; - IVAS_PIDATA_ORIENTATION deviceCompensated; - IVAS_PIDATA_ORIENTATION deviceUnCompensated; - IVAS_PIDATA_ACOUSTIC_ENV acousticEnv; -#ifdef RTP_S4_251135_CR26253_0016_REV1 - IVAS_PIDATA_AUDIO_DESC audioDesc; - IVAS_PIDATA_ISM_NUM ismNum; - IVAS_PIDATA_ISM_ID ismId; - IVAS_PIDATA_ISM_GAIN ismGain; - IVAS_PIDATA_ISM_ORIENTATION ismOrientation; - IVAS_PIDATA_ISM_POSITION ismPosition; - IVAS_PIDATA_ISM_ATTENUATION ismAttenuation; - IVAS_PIDATA_ISM_DIRECTIVITY ismDirectivity; - IVAS_PIDATA_DIEGETIC digeticIndicator; - IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionIndication; - IVAS_PIDATA_AUDIO_FOCUS focusIndication; - - IVAS_PIDATA_ORIENTATION playbackOrientation; - IVAS_PIDATA_ORIENTATION headOrientation; - IVAS_PIDATA_LISTENER_POSITION listnerPosition; - IVAS_PIDATA_DYNAMIC_SUPPRESSION dynSuppressionRequest; - IVAS_PIDATA_AUDIO_FOCUS focusRequest; - IVAS_PIDATA_REVERSE_PI_LATENCY piLatency; - IVAS_PIDATA_ISM_EDIT_ID ismEditId; - IVAS_PIDATA_ISM_EDIT_GAIN ismEditGain; - IVAS_PIDATA_ISM_EDIT_ORIENTATION ismEditOrientation; - IVAS_PIDATA_ISM_EDIT_POSITION ismEditPosition; - IVAS_PIDATA_ISM_EDIT_DIRECTION ismEditDirection; -#endif /* RTP_S4_251135_CR26253_0016_REV1 */ -} PIDATA_SAVED; - #endif /* IVAS_RTPDUMP */ #endif /* IVAS_RTP_PI_DATA_H */ -- GitLab From 545adc3013a5938eb3a6ef3200cc1c9939a68576 Mon Sep 17 00:00:00 2001 From: rtyag Date: Tue, 28 Oct 2025 21:35:28 +1100 Subject: [PATCH 305/340] fix no multi processing arg --- scripts/ivas_conformance/runConformance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 18b8f02ff8..6c29cd0094 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -560,7 +560,7 @@ if __name__ == "__main__": help='Choose tests to run ["ENC", "DEC", "REND", "ISAR", "ALL"]', ) parser.add_argument( - "--no-multi-processing", + "--no_multi_processing", default=False, action="store_true", help="Disable multi-processing for sequential test run (debugging)", -- GitLab From 829fbe1d45709239a7c35df4be78a3eaa2ecf6df Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 28 Oct 2025 17:06:45 +0100 Subject: [PATCH 306/340] Update LC3plus version to v1.6.1 --- lib_lc3plus/.clang-format | 6 + lib_lc3plus/adjust_global_gain.c | 10 +- lib_lc3plus/al_fec_fl.c | 11 +- lib_lc3plus/apply_global_gain.c | 2 +- lib_lc3plus/ari_codec.c | 112 +- lib_lc3plus/attack_detector.c | 2 +- lib_lc3plus/clib.h | 2 +- lib_lc3plus/com_entropy.c | 29 + lib_lc3plus/constants.c | 803 ++++++++- lib_lc3plus/constants.h | 70 +- lib_lc3plus/cutoff_bandwidth.c | 2 +- lib_lc3plus/dct4.c | 6 +- lib_lc3plus/dec_entropy.c | 599 ++++++- lib_lc3plus/dec_lc3_fl.c | 147 +- lib_lc3plus/defines.h | 162 +- lib_lc3plus/detect_cutoff_warped.c | 19 +- lib_lc3plus/enc_entropy.c | 410 ++++- lib_lc3plus/enc_lc3_fl.c | 142 +- lib_lc3plus/estimate_global_gain.c | 389 +++- lib_lc3plus/fft/cfft.c | 4 +- lib_lc3plus/fft/cfft.h | 2 +- lib_lc3plus/fft/fft_15_16.h | 2 +- lib_lc3plus/fft/fft_240_480.h | 2 +- lib_lc3plus/fft/fft_2_9.h | 2 +- lib_lc3plus/fft/fft_32.h | 2 +- lib_lc3plus/fft/fft_384_768.h | 2 +- lib_lc3plus/fft/fft_60_128.h | 2 +- lib_lc3plus/fft/fft_generic.h | 2 +- lib_lc3plus/fft/iis_fft.c | 2 +- lib_lc3plus/fft/iis_fft.h | 2 +- lib_lc3plus/fft/iisfft.c | 2 +- lib_lc3plus/fft/iisfft.h | 24 +- lib_lc3plus/functions.h | 129 +- lib_lc3plus/imdct.c | 2 +- lib_lc3plus/lc3plus.c | 58 +- lib_lc3plus/lc3plus.h | 26 +- lib_lc3plus/lc3plus_fft.c | 16 +- lib_lc3plus/license.h | 4 +- lib_lc3plus/ltpf_coder.c | 168 +- lib_lc3plus/ltpf_decoder.c | 471 ++++- lib_lc3plus/mdct.c | 65 +- lib_lc3plus/mdct_shaping.c | 2 +- lib_lc3plus/near_nyquist_detector.c | 28 +- lib_lc3plus/noise_factor.c | 22 +- lib_lc3plus/noise_filling.c | 22 +- lib_lc3plus/olpa.c | 39 +- lib_lc3plus/pc_apply.c | 2 +- lib_lc3plus/pc_classify.c | 8 +- lib_lc3plus/pc_main.c | 2 +- lib_lc3plus/pc_update.c | 2 +- lib_lc3plus/per_band_energy.c | 19 +- lib_lc3plus/plc_classify.c | 103 +- lib_lc3plus/plc_compute_stab_fac.c | 2 +- lib_lc3plus/plc_damping_scrambling.c | 66 +- lib_lc3plus/plc_main.c | 26 +- lib_lc3plus/plc_noise_substitution.c | 2 +- lib_lc3plus/plc_phecu_f0_refine_first.c | 2 +- lib_lc3plus/plc_phecu_fec_hq.c | 2 +- lib_lc3plus/plc_phecu_hq_ecu.c | 2 +- lib_lc3plus/plc_phecu_lf_peak_analysis.c | 2 +- lib_lc3plus/plc_phecu_rec_frame.c | 2 +- lib_lc3plus/plc_phecu_setf0hz.c | 2 +- lib_lc3plus/plc_phecu_spec_ana.c | 6 +- lib_lc3plus/plc_phecu_subst_spec.c | 4 +- lib_lc3plus/plc_phecu_tba_per_band_gain.c | 2 +- lib_lc3plus/plc_phecu_tba_spect_Xavg.c | 2 +- lib_lc3plus/plc_phecu_tba_trans_dect_gains.c | 2 +- lib_lc3plus/plc_phecu_trans_burst_ana_sub.c | 2 +- lib_lc3plus/plc_tdc.c | 79 +- lib_lc3plus/plc_tdc_tdac.c | 2 +- lib_lc3plus/plc_update.c | 2 +- lib_lc3plus/quantize_spec.c | 6 +- lib_lc3plus/reorder_bitstream.c | 2 +- lib_lc3plus/resamp12k8.c | 21 +- lib_lc3plus/residual_coding.c | 32 +- lib_lc3plus/residual_decoding.c | 52 +- lib_lc3plus/setup_com_lc3plus.c | 26 +- lib_lc3plus/setup_dec_lc3plus.c | 242 ++- lib_lc3plus/setup_dec_lc3plus.h | 30 +- lib_lc3plus/setup_enc_lc3plus.c | 256 ++- lib_lc3plus/setup_enc_lc3plus.h | 22 +- lib_lc3plus/sns_compute_scf.c | 117 +- lib_lc3plus/sns_interpolate_scf.c | 5 +- lib_lc3plus/sns_quantize_scf.c | 1585 ++++++++++++++++- lib_lc3plus/structs.h | 5 +- lib_lc3plus/tns_coder.c | 35 +- lib_lc3plus/tns_decoder.c | 2 +- lib_lc3plus/util.h | 2 +- scripts/lc3plus_lib_setup/get_lc3plus.sh | 58 +- scripts/lc3plus_lib_setup/get_lc3plus_fx.sh | 172 ++ .../ivas_lc3plus_unit_test_payload_format.c | 2 - 91 files changed, 6195 insertions(+), 847 deletions(-) create mode 100644 lib_lc3plus/com_entropy.c create mode 100644 scripts/lc3plus_lib_setup/get_lc3plus_fx.sh diff --git a/lib_lc3plus/.clang-format b/lib_lc3plus/.clang-format index dc13f4a6e0..a47b539e41 100644 --- a/lib_lc3plus/.clang-format +++ b/lib_lc3plus/.clang-format @@ -1,3 +1,9 @@ DisableFormat: true SortIncludes: Never + +DisableFormat: true +SortIncludes: Never + +DisableFormat: true +SortIncludes: Never diff --git a/lib_lc3plus/adjust_global_gain.c b/lib_lc3plus/adjust_global_gain.c index d5c122d525..1de3585fcd 100644 --- a/lib_lc3plus/adjust_global_gain.c +++ b/lib_lc3plus/adjust_global_gain.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -12,7 +12,7 @@ #include "functions.h" void processAdjustGlobalGain_fl(LC3_INT* gg_idx, LC3_INT gg_idx_min, LC3_INT gg_idx_off, LC3_FLOAT* gain, LC3_INT target, LC3_INT nBits, LC3_INT* gainChange, LC3_INT fs_idx - , LC3_INT16 hrmode, LC3_INT16 frame_dms + , LC3_INT16 hrmode, LC3PLUS_FrameDuration frame_dms ) { LC3_FLOAT delta; @@ -20,7 +20,7 @@ void processAdjustGlobalGain_fl(LC3_INT* gg_idx, LC3_INT gg_idx_min, LC3_INT gg_ LC3_INT gg_idx_inc; LC3_FLOAT factor; - if (frame_dms == 25) + if (frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) { if (target < 520) { @@ -28,11 +28,11 @@ void processAdjustGlobalGain_fl(LC3_INT* gg_idx, LC3_INT gg_idx_min, LC3_INT gg_ } else { factor = 4; } - } else if (frame_dms == 50) + } else if (frame_dms == LC3PLUS_FRAME_DURATION_5MS) { factor = 2; } - else if (frame_dms == 75) + else if (frame_dms == LC3PLUS_FRAME_DURATION_7p5MS) { factor = 1.2; } diff --git a/lib_lc3plus/al_fec_fl.c b/lib_lc3plus/al_fec_fl.c index adae19dc39..9b7276f237 100644 --- a/lib_lc3plus/al_fec_fl.c +++ b/lib_lc3plus/al_fec_fl.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -500,11 +500,12 @@ void fec_encoder(LC3_INT16 mode, LC3_INT16 epmr, LC3_UINT8 *iobuf, LC3_INT16 dat assert((slot_bytes >= FEC_SLOT_BYTES_MIN && slot_bytes <= FEC_SLOT_BYTES_MAX) && "fec_encoder: slot_bytes out of range"); - tmp -= mode == 1 ? 1 : n_codewords * (mode - 1); // reed solomon redundancy - tmp -= slot_bytes == 40 ? crc1_bytes_by_mode0[mode] : crc1_bytes_by_mode1[mode]; // crc1 - tmp -= (n_pccw > 0) && (mode > 1) ? crc2_bytes_by_mode[mode] : 0; // crc2 + tmp -= mode == 1 ? 1 : n_codewords * (mode - 1); /* reed solomon redundancy */ + tmp -= slot_bytes == 40 ? crc1_bytes_by_mode0[mode] : crc1_bytes_by_mode1[mode]; /* crc1 */ + tmp -= (n_pccw > 0) && (mode > 1) ? crc2_bytes_by_mode[mode] : 0; /* crc2 */ assert(data_bytes == tmp && "fec_encoder: inconsistent payload size"); assert(n_codewords - n_pccw >= 6); + (void)tmp; } /* data preproc: re-ordering and hash extension */ @@ -1194,7 +1195,7 @@ FEC_STATIC LC3_INT32 rs16_detect_and_correct(LC3_UINT8 *iobuf, LC3_INT32 n_symb, } } } - assert(n_mode_candidates <= 4); // suppress false gcc warning when OPTIM=3 + assert(n_mode_candidates <= 4); /* suppress false gcc warning when OPTIM=3 */ /* sort mode candidates by risk */ for (i = 0; i < n_mode_candidates; i++) diff --git a/lib_lc3plus/apply_global_gain.c b/lib_lc3plus/apply_global_gain.c index 48305676cd..107a9ff497 100644 --- a/lib_lc3plus/apply_global_gain.c +++ b/lib_lc3plus/apply_global_gain.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/ari_codec.c b/lib_lc3plus/ari_codec.c index 2a76ecac83..62e39130fc 100644 --- a/lib_lc3plus/ari_codec.c +++ b/lib_lc3plus/ari_codec.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -90,7 +90,9 @@ static LC3_INT16 pc_check_bytes(LC3_INT32* bp, Decoder_State_fl* st_fl, LC3_INT3 { bp_side_local = bp_side_local + 1; } - } else { + } + else + { bp_local = bp_local - 1; } @@ -131,12 +133,16 @@ static LC3_INT16 pc_check_bytes(LC3_INT32* bp, Decoder_State_fl* st_fl, LC3_INT3 if (st_fl->pc_bfi == 2) { - +#ifdef CR13_B_FIX_PC_BINS + if (st_fl->pc_c_bp_side && (*bp_side + 1) <= st_fl->pc_be_bp_right) +#else if ((st_fl->pc_c_bp && (*bp + 1) >= st_fl->pc_be_bp_left) || (st_fl->pc_c_bp_side && (*bp_side + 1) <= st_fl->pc_be_bp_right)) +#endif { st_fl->pc_inv_bin = cur_bin; return 1; - } else if ((st_fl->pc_c_bp && *bp >= 0) || (st_fl->pc_c_bp_side && *bp_side <= (st_fl->pc_bytes - 1))) + } + else if ((st_fl->pc_c_bp && *bp >= 0) || (st_fl->pc_c_bp_side && *bp_side <= (st_fl->pc_bytes - 1))) { st_fl->pc_inv_bin = MIN(st_fl->pc_inv_bin, cur_bin); return 0; @@ -151,6 +157,53 @@ static LC3_INT16 pc_check_bytes(LC3_INT32* bp, Decoder_State_fl* st_fl, LC3_INT3 return 0; } +#ifdef CR13_B_FIX_PC_BINS +static LC3_INT16 pc_check_bytes_ac_decode(LC3_INT32* bp, Decoder_State_fl* st_fl, LC3_INT32 from_left, LC3_INT32 mask_side, LC3_INT32* bp_side, LC3_INT16 cur_bin) +{ + LC3_INT32 bp_local, bp_side_local; +#ifdef WMOPS + push_wmops("pc_check_bytes"); +#endif + + if (st_fl->pc_bytes > 0) + { + bp_local = *bp; + bp_side_local = *bp_side; + + if (from_left) + { + if (mask_side == 1) + { + bp_side_local = bp_side_local + 1; + } + } + else + { + bp_local = bp_local - 1; + } + + + if (!st_fl->pc_enc && st_fl->pc_b_right > -1) + { + + if (st_fl->pc_bfi == 2) + { + if ((st_fl->pc_c_bp && (*bp) > st_fl->pc_be_bp_left)) + { + st_fl->pc_inv_bin = cur_bin; + return 1; + } + } + } + } + +#ifdef WMOPS + pop_wmops(); +#endif + return 0; +} +#endif + void ac_dec_init_fl(LC3_UINT8* ptr, LC3_INT* bp, Decoder_State_fl* st_fl, LC3_INT from_left, LC3_INT mask_side, LC3_INT *bp_side) { LC3_INT i; @@ -184,15 +237,25 @@ LC3_INT32 ac_decode_fl(Decoder_State_fl* st, const LC3_INT16* freq, LC3_INT32 nu push_wmops("ac_decode_fl"); #endif +#ifdef CR13_B_FIX_PC_BINS + if (pc_check_bytes_ac_decode(bp, st, from_left, mask_side, bp_side, cur_bin) != 0) + { + st->BER_detect = 1; + return 0; + } +#endif + tmp = st->ac_range_fl >> 10; - if (st->ac_low_fl >= (LC3_UINT32)(tmp << 10)) { + if (st->ac_low_fl >= (LC3_UINT32)(tmp << 10)) + { st->BER_detect = 1; } val = num_sym - 1; - while (st->ac_low_fl < (LC3_UINT32)(tmp * freq[val])) { + while (st->ac_low_fl < (LC3_UINT32)(tmp * freq[val])) + { val--; } @@ -201,7 +264,8 @@ LC3_INT32 ac_decode_fl(Decoder_State_fl* st, const LC3_INT16* freq, LC3_INT32 nu st->ac_low_fl = st->ac_low_fl - tmp * freq[val]; st->ac_range_fl = tmp * symfreq_loc; - while (st->ac_range_fl < 65536) { + while (st->ac_range_fl < 65536) + { st->ac_low_fl = ((LC3_INT32)st->ac_low_fl) & ((LC3_INT32)(16777215)); if(pc_check_bytes(bp, st, from_left, mask_side, bp_side, cur_bin) != 0) @@ -209,7 +273,7 @@ LC3_INT32 ac_decode_fl(Decoder_State_fl* st, const LC3_INT16* freq, LC3_INT32 nu st->BER_detect = 1; return 1; } - + st->ac_low_fl = st->ac_low_fl << 8; st->ac_low_fl = st->ac_low_fl + ptr[*bp]; *bp = *bp + 1; @@ -241,7 +305,7 @@ void read_bit_fl(LC3_UINT8* ptr, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT* void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, LC3_INT L_spec, LC3_INT fs_idx, LC3_INT enable_lpc_weighting, LC3_INT tns_numfilters, LC3_INT lsbMode, LC3_INT lastnz, LC3_INT* bfi, LC3_INT* tns_order, LC3_INT fac_ns_idx, LC3_INT gg_idx, uint8_t * resBits, LC3_INT* x, LC3_INT* nf_seed, LC3_INT* tns_idx, LC3_INT* zero_frame, LC3_INT numbytes, - LC3_INT* nbits_residual, LC3_INT* residualPresent, LC3_INT frame_dms, + LC3_INT* nbits_residual, LC3_INT* residualPresent, LC3PLUS_FrameDuration frame_dms, LC3_INT32 n_pc, LC3_INT32 be_bp_left, LC3_INT32 be_bp_right, LC3_INT32 enc, LC3_INT32 *b_left, LC3_INT32 *spec_inv_idx, LC3_INT hrmode ) @@ -298,7 +362,7 @@ void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, tmp = MAXLAG; - if (frame_dms <= 50) + if (frame_dms <= LC3PLUS_FRAME_DURATION_5MS) { tmp /= 2; } @@ -334,11 +398,13 @@ void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, } /* Spectral data */ - for (k = 0; k < lastnz; k = k + 2) { + for (k = 0; k < lastnz; k = k + 2) + { /* Context */ t = c + rateFlag; - if (k > (L_spec >> 1)) { + if (k > (L_spec >> 1)) + { t = t + 256; } @@ -346,23 +412,29 @@ void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, x[k] = 0; x[k + 1] = 0; - if (hrmode == 1) { + if (hrmode == 1) + { max_lev = 13 + 8; - } else { + } + else + { max_lev = 13; } - for (lev = 0; lev <= max_lev; lev++) { + for (lev = 0; lev <= max_lev; lev++) + { lev1 = MIN(lev, 3); pki = ari_spec_lookup_fl[t + lev1 * 1024]; sym = ac_decode_fl(&st, &ari_spec_cumfreq_fl[pki][0], 17, ptr, &bp, 1, mask_side, &bp_side, k); - if (sym < 16) { + if (sym < 16) + { break; } - if (lsbMode == 0 || lev > 0) { + if (lsbMode == 0 || lev > 0) + { if(pc_check_bytes(&bp, &st, 0, mask_side, &bp_side, k) != 0) { goto ber_detect; @@ -495,6 +567,12 @@ void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, { idx_len *= EXT_RES_ITER_MAX; } +#ifdef CR9_C_ADD_1p25MS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + idx_len *= 3; + } +#endif + *nbits_residual = MIN(*nbits_residual, idx_len); *residualPresent = 1; diff --git a/lib_lc3plus/attack_detector.c b/lib_lc3plus/attack_detector.c index 10e3617c8b..b74a320563 100644 --- a/lib_lc3plus/attack_detector.c +++ b/lib_lc3plus/attack_detector.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/clib.h b/lib_lc3plus/clib.h index 7d2fb7bb10..51970ef146 100644 --- a/lib_lc3plus/clib.h +++ b/lib_lc3plus/clib.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/com_entropy.c b/lib_lc3plus/com_entropy.c new file mode 100644 index 0000000000..da7ee8edaa --- /dev/null +++ b/lib_lc3plus/com_entropy.c @@ -0,0 +1,29 @@ +/****************************************************************************** +* ETSI TS 103 634 V1.6.1 * +* Low Complexity Communication Codec Plus (LC3plus) * +* * +* Copyright licence is solely granted through ETSI Intellectual Property * +* Rights Policy, 3rd April 2019. No patent licence is granted by implication, * +* estoppel or otherwise. * +******************************************************************************/ + +#include "options.h" +#include "wmc_auto.h" +#include "functions.h" + +LC3_INT16 getLastNzBits (LC3_INT16 N) +{ + LC3_INT16 minBits; + + minBits = ceil(LC3_LOGTWO(N >> 1)); +#ifdef CR9_C_ADD_1p25MS + /* minimum of 2 spare bits */ + if (((1 << minBits) - (N >> 1)) < 2) + { + minBits++; + } +#endif + + return minBits; +} + diff --git a/lib_lc3plus/constants.c b/lib_lc3plus/constants.c index 047b21226d..1040d0098a 100644 --- a/lib_lc3plus/constants.c +++ b/lib_lc3plus/constants.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,6 +11,731 @@ #include "wmc_auto.h" #include "functions.h" + +#ifdef CR9_C_ADD_1p25MS_LRSNS +/* Stage 1 section A */ +const LC3_FLOAT lrsns_st1A_topTab_1bitNoDC[2 * 16] = { /*[2][16] = */ + /* idx0 --> flat: no additional perceptual weighting than tilt */ + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + + /* : idx1 */ /* Q11 quantized */ + +1.187011718750000e+00, +1.014160156250000e+00, +7.773437500000000e-01, +4.023437500000000e-01, +7.812500000000000e-03, -2.636718750000000e-01, -2.734375000000000e-01, -2.617187500000000e-01, + -7.519531250000000e-02, +1.879882812500000e-01, +4.526367187500000e-01, +5.512695312500000e-01, +2.221679687500000e-01, -3.710937500000000e-01, -1.253417968750000e+00, -2.304199218750000e+00 +}; + +/* compressed c_domain indexes for merged CB st1B */ + +const LC3_INT16 lrsns_st1B_merged170orderSortedSegmCnt[4] = { + 88, 42, 24, 16 +}; +const LC3_INT16 lrsns_st1B_merged170orderSortedSegmCum[5] = { + 0, 88, 130, 154, 170 +}; + +/* each index point to two vectors in the split 2x32 stage 1 cb , lower 12 bits in use [1][5][1][5] */ +/* bits b13,b12 are ff,fr,rf,rr , handled by counting the cumulative idx */ +const LC3_INT16 lrsns_st1B_merged170orderSort12bitIdx[170] = { + 15, 19, 119, 162, 177, 185, 227, 296, 297, 353, 357, 369, 370, 372, 377, 416, 418, + 420, 515, 529, 561, 569, 603, 625, 627, 673, 680, 754, 756, 777, 786, 801, 809, 834, + 837, 848, 849, 850, 852, 864, 865, 866, 872, 904, 908, 913, 914, 924, 928, 978, 996, + 1041, 1082, 1088, 1089, 1090, 1139, 1651, 1751, 1982, 1999, 2066, 2120, 2194, 2247, 2391, 2495, 2579, + 2626, 2632, 2649, 2959, 3071, 3107, 3223, 3331, 3395, 3415, 3548, 3641, 3675, 3703, 3820, 3825, 3849, + 3853, 3865, 3916, 45, 107, 110, 151, 168, 169, 247, 305, 313, 323, 357, 361, 777, 785, + 841, 1096, 1123, 1347, 1427, 1498, 1555, 1617, 1643, 1646, 1651, 1774, 2051, 2087, 2265, 2296, 2371, + 2971, 3022, 3241, 3287, 3319, 3331, 3543, 3601, 3683, 3689, 3697, 122, 259, 274, 387, 401, 496, + 657, 707, 739, 850, 857, 928, 929, 932, 996, 1123, 1270, 1303, 1609, 2151, 2193, 2371, 2632, + 2633, 55, 119, 291, 299, 343, 681, 1001, 1307, 1427, 1609, 1646, 1683, 2157, 2222, 3331, 4060 +}; + + +/* Q11 quntized LF+HF tables as already available in existing 10ms BASOP */ +const LC3_INT16 st1SCF0_7_base5_32x8_Q11[8 * 32] = { + 4634 , 1666, -1086, -2778, -3276, -2951, -2343, -1547, 6032, + 4939 , 1967, -908, -2517, -3186, -3066, -2287, + -4477, -4038, -3660, -3929, -3674, -2780, -1445, -98, + 1421, 1957, 1178, -235, -1323, -1950, -2200, -1553, + -2657, -1516, -707, -642, -825, -762, -160, 199, + 1873, 3570, 3910, 3162, 2239, 1326, 74, -608, + -5149, -5922, -4105, -1538, 904, 2462, 2719, 2500, + -1889, 1295, 2227, 1246, 269, -607, -424, 276, + 1619, 1287, 805, 983, 917, 430, 13, -176, 2965, 5579, 4733, 1915, -563, -1847, -1927, -1298, + 1625, 29, -1163, -1341, -982, -356, 139, 604, 5579, 6061, 3788, 1154, 287, 737, 1412, 1310, + -1087, -436, 12, 870, 969, 1759, 2439, 2040, 3456, 4989, 4772, 3645, 2958, 3113, 3015, 2002, + -6045, -3264, -225, 796, 1050, 1286, 1685, 1794, 209, 1208, 1268, 2595, 4955, 4612, 1078, -812, + 5494, 2718, 267, -693, -754, -393, -317, -480, 9886, 6389, 2857, 513, -806, -1318, -1316, -1481, + 180, -1167, -2345, -3420, -3779, -3204, -2289, -1094, 2849, 4058, 2279, -451, -1587, -1217, 280, 1676, + 788, -329, -1105, -1084, 390, 5244, 5773, 1345, 3957, 6165, 6278, 5122, 3954, 1172, -1662, -2409, + 359, -1537, -2129, -2326, -2134, -31, 4240, 7024, -2433, 751, 2682, 3447, 2562, 1930, 1692, 901, + 5188, 4327, 2586, 1560, 1069, 243, -926, -1434, 8190, 8354, 5781, 3535, 1325, -678, -1811, -2308, + 1040, 3253, 3541, 2062, 772, 976, 2227, 2227, 6489, 6673, 4961, 3675, 3117, 2400, 1002, -128, + 3879, 2562, 1209, 1246, 1798, 2292, 2086, 1271, 1943, 4367, 5578, 5673, 5208, 4138, 1700, -56, + -3851, -2589, 638, 3762, 4621, 4195, 4496, 4150, 505, 1957, 3114, 4048, 3974, 4575, 4072, 2606 }; + +const LC3_INT16 st1SCF8_15_base5_32x8_Q11[8 * 32] = { + 475, -2066, -4387, -4865, -4568, -4456, -4691, -5187, -2652, -3685, -3865, -3707, -3611, -3756, -3696, -3557, + 285, -529, -1333, -2188, -3316, -4480, -5402, -6101, -648, -978, -1129, -993, -488, -293, 140, 181, + 1801, 611, -1875, -4519, -5614, -5860, -5915, -6045, -608, -1997, -2782, -2015, -1337, -2027, -3307, -4930, + 698, 551, 115, 102, -195, -1557, -4767, -7724, -2892, -3042, -2429, -1280, 315, 1180, 1628, 1222, + -469, -683, -1657, -3350, -3860, -3369, -2878, -3004, -2194, -2903, -3172, -2976, -2113, -1414, -878, -1014, + -1210, -146, 708, 616, -2291, -4999, -4564, -3881, -1738, -1194, 184, 1731, 2183, 1511, 525, -1008, + 2336, 1974, 781, -989, -3720, -5740, -6623, -7084, -771, 87, 1058, 516, -443, -1094, -1312, -1781, + 1362, 2249, 2833, 2751, 1685, 442, -829, -2192, -1692, -1375, -468, 1063, 2800, 4465, 5194, 4508, + 2888, 1545, -2674, -3832, -2540, -2595, -4171, -5933, 740, -45, -1187, -1801, -1742, -1596, -1500, -1819, + 896, 626, -15, -1015, -1652, -2507, -3485, -4598, 1327, 1397, 519, 151, 644, 481, 296, -140, + 2292, 2529, 1207, -2810, -4856, -4112, -3414, -3945, 291, -227, -579, -14, 586, 94, -1234, -4640, + 1032, 1694, 2293, 2415, 2212, 1429, -1869, -7325, -1026, -667, 58, 537, 738, 1302, 1964, 2678, + 7679, 3120, -937, -1636, -792, -770, -1347, -2625, -2361, -2269, -1152, -452, -716, -1543, -2025, -2638, + 2106, 2248, 1574, 422, -702, -1546, -2134, -3079, 264, 1412, 2301, 2682, 2775, 2915, 2370, 832, + 2745, 2847, 2140, 1302, -563, -3173, -5002, -6194, 4380, 8698, 5934, 1910, -600, -1660, -1616, -1916, + 1157, 3260, 4911, 6220, 5456, 2853, 827, -1344, -865, 668, 2850, 4570, 5349, 5459, 4917, 3603 +}; + + +/* harmonically trained st1C with pitch info */ +/* Q11 values, to enable BE in synthesis */ +/* st1C mean for pitch_rx==0 && ltpf_rx==* */ +const LC3_FLOAT lrsns_st1C_pitch0_mp0Q11[16] = { ++1.119628906250000e+00f, +1.270507812500000e+00f, +9.570312500000000e-01f, +5.668945312500000e-01f, +4.296875000000000e-01f, +2.036132812500000e-01f, +1.660156250000000e-02f, -8.251953125000000e-02f, +-1.796875000000000e-01f, -2.641601562500000e-01f, -2.949218750000000e-01f, -1.552734375000000e-01f, -1.401367187500000e-01f, -4.677734375000000e-01f, -1.143066406250000e+00f, -1.835449218750000e+00f +}; + +/* st1C mean for pitch_rx==1 && ltpf_rx==0 */ +float lrsns_st1C_pitch1_mp1Q11[16] = { ++1.259765625000000e-01f, +3.930664062500000e-01f, +3.852539062500000e-01f, +2.602539062500000e-01f, +2.475585937500000e-01f, +6.005859375000000e-02f, +8.935546875000000e-02f, +7.568359375000000e-02f, ++4.296875000000000e-02f, +7.373046875000000e-02f, +2.612304687500000e-01f, +4.975585937500000e-01f, +4.995117187500000e-01f, +2.832031250000000e-02f, -9.829101562500000e-01f, -2.056152343750000e+00f +}; + +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY +/* lrsns_st1C_Both_fullTableResWord8 */ +const LC3_FLOAT* lrsns_st1CTrainedMapMeans[2] = { lrsns_st1C_pitch0_mp0Q11 ,lrsns_st1C_pitch1_mp1Q11 }; +#else +/* st1C mean for pitch_rx==1 && ltpf_rx==1 */ +const LC3_FLOAT lrsns_st1C_pitch2_mp2Q11[16] = { +-2.055664062500000e-01f, +1.005859375000000e-01f, +1.948242187500000e-01f, +1.577148437500000e-01f, +1.870117187500000e-01f, +1.220703125000000e-02f, +1.137695312500000e-01f, +1.284179687500000e-01f, ++1.171875000000000e-01f, +1.860351562500000e-01f, +4.462890625000000e-01f, +7.148437500000000e-01f, +7.128906250000000e-01f, +1.938476562500000e-01f, -9.296875000000000e-01f, -2.129394531250000e+00f +}; + +/* lrsns_st1C_Both_fullTableResWord8 */ +const LC3_FLOAT* lrsns_st1CTrainedMapMeans[3] = { lrsns_st1C_pitch0_mp0Q11 ,lrsns_st1C_pitch1_mp1Q11,lrsns_st1C_pitch2_mp2Q11 }; +#endif +/* float tables BE representable in Q11 after Q4*Q7 multiplication */ +const LC3_FLOAT lrsns_st1C_Both_scaleQ4_7p4bits[2] = { ++5.375f, +86.0 /* exact BASOP Q4 value, used in synthesis */ +}; +const LC3_FLOAT lrsns_st1C_Both_inv_scaleQ15_7p4bits[2] = { ++1.860351562500000e-01f , +6096.0 /* BASOP Q15 value, invScale used in encoder side for target creation */ +}; + +/* BASOP table for stage1C BE representable in Q11 after Q4*Q7 multiplication */ +const LC3_INT16 lrsns_st1C_Both_scaleQ4_7p4bits_fx[2] = { +-1, 86 /* exact BASOP Q4 value, used in synthesis */ +}; +const LC3_INT16 lrsns_st1C_Both_inv_scaleQ15_7p4bits_fx[2] = { +-1 , 6096 /* BASOP Q15 value, invScale used in encoder side target creation */ +}; + +/* Word8 storage of ST1 C codebook to save ROM in BASOP */ +const LC3_INT8 lrsns_st1C_Both_Word8[170 * 16] = { + -88, -78, -57, -39, -27, -15, -6, 3, 11, 17, 20, 23, 32, 47, 69, 88, + -86, -40, -7, 4, 1, 5, 10, 10, 0, -5, -5, 4, 9, 23, 38, 39, + -71, -69, -65, -49, -29, -5, 16, 30, 21, -8, -3, 15, 27, 40, 63, 87, + -71, -36, -10, -5, 2, 17, 15, 18, 24, 24, 13, 7, 11, 2, -6, -7, + -70, -23, 13, 12, 0, 7, 19, 14, 1, -1, 6, 3, -2, 1, 9, 13, + -68, -79, -67, -42, -26, -17, 8, 29, 38, 34, 32, 33, 32, 36, 36, 21, + -62, -66, -56, -41, -33, -13, 11, 17, 16, 17, 28, 39, 40, 41, 40, 23, + -61, -62, -51, -31, -26, -25, -23, -15, -3, 12, 24, 30, 33, 43, 68, 85, + -60, -63, -47, -28, -17, -9, 13, 30, 36, 31, 25, 24, 27, 28, 17, -6, + -57, -47, -25, -11, -17, -12, -1, -7, -4, -1, -7, 1, 23, 44, 58, 60, + -54, -59, -61, -50, -22, -1, 25, 37, 37, 31, 21, 13, 21, 31, 27, 4, + -53, -36, -14, 3, 13, 12, 14, 19, 26, 31, 30, 22, 3, -12, -21, -36, + -53, -52, -39, -24, -12, 2, 23, 34, 32, 27, 16, 8, 11, 21, 14, -9, + -45, -46, -31, -16, -8, -5, 6, 21, 31, 32, 26, 22, 18, 11, 1, -17, + -40, -11, 7, 11, 4, 13, 5, -13, -28, -20, -10, 0, 8, 19, 23, 32, + -40, -32, -18, -2, 6, 12, 8, 1, 2, 4, 9, 15, 20, 13, 3, -1, + -40, -9, 11, 7, 6, 6, 0, -13, -13, -1, 7, 12, 8, 4, 6, 9, + -39, 5, 19, 12, 13, 9, 4, 3, -2, -6, -4, 2, 3, -2, -6, -13, + -38, -13, 1, 1, 4, 17, 16, 12, 8, 0, -9, -23, -20, 0, 18, 24, + -38, -25, -13, -12, -24, -17, -6, 4, 10, 6, -17, -13, 6, 35, 51, 53, + -38, -34, -30, -24, -9, 7, 17, 18, 14, 9, 11, 14, 18, 18, 12, -3, + -38, -36, -19, 2, 20, 28, 28, 33, 31, 16, 6, 4, -4, -12, -23, -36, + -38, -46, -38, -22, -17, -22, -11, 18, 35, 34, 27, 25, 24, 22, 12, -3, + -37, 3, 34, 16, -8, -30, -54, -45, -23, -9, -3, 14, 12, 15, 42, 72, + -35, -55, -53, -38, -30, -20, -5, 6, 13, 16, 21, 25, 29, 41, 48, 36, + -35, -45, -50, -47, -48, -41, -31, -16, -1, 9, 20, 21, 29, 51, 81, 104, + -35, -46, -46, -35, -24, -11, 9, 27, 34, 33, 27, 25, 21, 17, 9, -6, + -34, -24, -20, -13, -11, -7, 2, 2, -13, -4, 23, 31, 21, 16, 16, 16, + -33, -54, -66, -58, -43, -23, -16, -10, 1, 18, 23, 18, 28, 51, 74, 89, + -31, 16, 51, 48, 30, 18, 12, 2, -7, -17, -23, -26, -29, -26, -17, -1, + -31, -20, -18, -20, -13, -8, 1, 13, 20, 2, -13, -8, 12, 27, 31, 25, + -30, -33, -40, -36, -30, -11, 3, 10, 14, 15, 24, 29, 31, 27, 20, 8, + -30, -45, -40, -20, -14, -21, -31, -23, -10, 1, 12, 15, 22, 39, 64, 83, + -29, -15, 0, 4, 8, 16, 17, 11, 5, 4, 2, 1, 11, 7, -13, -29, + -29, -29, -20, -3, 15, 27, 27, 18, 17, 29, 31, 19, -3, -19, -35, -44, + -28, -21, -26, -26, 2, 22, 30, 37, 34, 18, 8, 2, 0, -9, -18, -25, + -28, -19, -11, -7, -1, 8, 13, 17, 21, 25, 30, 23, 5, -9, -26, -42, + -27, -9, 3, 2, 3, 11, -7, -14, 9, 25, 14, -6, -4, -1, 0, 2, + -27, -27, -25, -17, -18, -10, -8, -16, -18, 1, 16, 20, 29, 35, 38, 27, + -26, -44, -8, 8, -12, -5, -6, -9, 1, 13, 17, 15, 35, 42, 6, -29, + -24, -22, -16, -13, -12, 0, 10, 15, 19, 8, 5, 13, 26, 18, -5, -21, + -24, -42, -47, -33, -31, -32, -19, 2, 13, 13, 9, 4, 16, 35, 58, 80, + -24, -16, -7, -4, -7, -6, -8, -9, -3, 5, 11, 15, 23, 22, 12, -2, + -23, -13, 1, 1, -19, -17, 7, 21, 29, 29, 20, 12, 0, -9, -15, -26, + -23, -39, -40, -26, -19, -13, -4, -10, -21, -20, 0, 18, 26, 39, 57, 73, + -22, -26, -25, -25, -28, -21, -3, 23, 35, 38, 31, 20, 10, 5, 2, -13, + -21, 3, 27, 20, -9, -8, 4, 3, -3, -10, -18, -16, -4, 8, 11, 13, + -21, -13, -5, 1, 15, 22, 23, 27, 31, 25, 13, -1, -18, -28, -33, -40, + -20, -15, 0, 10, 9, 13, 12, 0, -15, -8, 7, 9, 4, 0, -2, -6, + -20, -15, -9, -6, -2, -14, -40, -5, 34, 49, 45, 29, -7, -16, -7, -18, + -19, 13, 24, 22, 27, 17, 2, -1, -8, -17, -26, -16, 0, 1, -4, -15, + -18, -14, -7, 6, 19, 21, 20, 22, 20, 16, 17, 14, -5, -27, -39, -44, + -17, 30, 76, 76, 57, 44, 11, -23, -43, -54, -57, -58, -53, -33, 5, 40, + -17, -19, -22, -34, -39, -15, 12, 23, 21, 17, 18, 22, 20, 13, 6, -7, + -17, -12, -8, 6, 19, 4, -15, 0, 21, 32, 30, 8, -9, -15, -20, -24, + -16, 33, 77, 82, 55, 23, -2, -11, -20, -39, -58, -65, -58, -33, 0, 32, + -16, -21, -26, -21, -7, 7, 10, 8, 11, 16, 22, 20, 12, 3, -4, -16, + -15, 8, 29, 23, 11, -9, -29, -23, -14, -5, -2, -2, 4, 10, 9, 3, + -14, -17, -19, -22, -11, 2, 15, 27, 33, 27, 20, 14, 3, -10, -20, -27, + -13, -2, 6, 3, -7, -7, 4, 26, 31, 19, 13, 12, -4, -23, -29, -32, + -13, -6, 6, 18, 21, 18, 12, 7, 5, 5, 5, -1, -7, -15, -23, -33, + -12, -7, 0, 3, 5, 0, -5, 4, 18, 24, 25, 16, 1, -13, -25, -34, + -12, -3, 0, -4, -3, 10, 9, -3, -5, -6, -6, -3, 14, 20, 5, -14, + -12, -11, -15, -14, -7, 2, 12, 6, -10, -21, -4, 11, 22, 22, 15, 4, + -11, -12, -13, -5, 11, 16, 11, 6, 5, 15, 22, 20, 3, -12, -23, -33, + -11, -10, -8, -11, -3, 13, 19, 18, 11, -1, -12, -8, 6, 7, 0, -12, + -11, -17, -20, -20, -16, -9, -2, 7, 24, 32, 32, 26, 14, 0, -14, -27, + -10, -11, -11, -20, -26, -25, -7, 7, 20, 29, 19, 6, 5, 15, 9, 0, + -10, -5, -7, -9, -4, 9, 10, 0, -17, -28, -21, -7, 12, 28, 29, 22, + -8, -20, -30, -25, -28, -26, -18, -9, 7, 15, 24, 26, 30, 33, 24, 5, + -8, -17, -19, -17, -16, -7, -7, -7, 3, 4, 4, 6, 21, 28, 22, 10, + -8, 35, 62, 60, 43, 21, 6, -4, -21, -34, -37, -42, -43, -32, -12, 6, + -8, -6, -3, -1, 1, 0, -5, -5, 2, 11, 23, 21, 8, -2, -11, -24, + -7, -21, -21, -13, 1, 18, 28, 30, 30, 34, 33, 18, 0, -21, -52, -57, + -6, -31, -40, -39, -36, -27, -20, -22, -19, -4, 10, 12, 19, 38, 69, 95, + -6, -38, -48, -42, -37, -27, -13, 2, 7, 9, 17, 23, 33, 45, 46, 28, + -6, -18, -39, -58, -59, -34, -15, -6, 0, 4, 15, 22, 24, 38, 59, 71, + -4, 6, 6, 11, 15, 9, 1, -7, 2, 11, 16, 10, -5, -14, -26, -31, + -4, 2, 7, -6, -59, -49, -4, -1, 1, 15, 9, -19, -19, 11, 52, 64, + -2, 31, 53, 43, 20, 8, -2, -17, -24, -21, -22, -25, -22, -14, -8, 1, + 1, -3, -3, 4, 6, 9, -4, -31, -39, -17, -5, -1, 6, 16, 27, 34, + 1, 10, 15, 12, -5, -20, -21, -10, 12, 27, 23, 10, -4, -12, -17, -23, + 3, 13, 23, 25, 20, 14, 3, 7, 11, -4, -31, -43, -35, -21, -1, 19, + 3, -3, -9, -16, -19, -7, 1, 7, 11, -2, -5, 5, 23, 20, 2, -10, + 4, -22, -27, -23, -23, -11, 7, 14, 15, 15, 6, -3, -9, -6, 19, 44, + 5, -5, -9, -8, -9, -7, 0, 17, 29, 22, 12, 12, 6, -8, -23, -32, + 6, -31, -58, -64, -41, -12, -1, 3, 2, 0, 3, 6, 21, 37, 55, 74, + 7, 12, 10, 1, 2, 6, 2, -5, -7, -3, -2, 0, 1, 1, -6, -19, + 9, 1, 2, 12, 15, 11, -11, -33, -17, 13, 20, 9, 0, -7, -11, -13, + 10, 21, 26, 24, 18, 4, -5, -13, -14, -6, 4, 7, -7, -19, -25, -25, + 11, 1, -4, -7, -17, -28, -18, 4, 19, 27, 21, 12, 6, 2, -8, -20, + 11, 4, -17, -36, -48, -39, -18, 0, 3, 1, 3, 6, 15, 27, 39, 49, + 11, 3, -12, -26, -20, -2, 11, 16, 14, 9, 18, 19, 6, -7, -15, -25, + 12, 0, -12, -7, -5, -15, -21, -14, -5, 4, 13, 22, 22, 13, 3, -8, + 14, 17, 19, 17, 17, 16, 1, -9, -8, -5, -13, -11, -6, -7, -14, -27, + 14, 33, 52, 50, 32, 12, -1, -6, -8, -14, -23, -30, -36, -35, -26, -13, + 16, 11, 10, 15, 21, 13, 13, 10, 8, 4, 6, 6, -6, -25, -50, -52, + 17, 9, 9, 23, 23, 10, 5, 16, 20, 13, 2, -18, -35, -39, -30, -23, + 17, 31, 47, 49, 42, 32, 22, 8, -7, -21, -31, -30, -43, -53, -42, -21, + 19, 37, 50, 42, 28, 24, 15, -8, -22, -22, -19, -28, -39, -39, -26, -12, + 19, 39, 47, 31, 10, 0, 1, 1, -6, -15, -21, -23, -26, -25, -18, -13, + 19, 12, 0, -7, -3, 1, -2, -12, -19, -19, -11, -1, 9, 14, 12, 5, + 19, 33, 47, 55, 47, 23, -6, -18, -23, -24, -14, -9, -22, -34, -42, -33, + 19, 13, -1, -20, -24, -13, -6, -6, -11, -16, -21, -24, -15, 9, 45, 71, + 21, 15, 8, 4, 5, 6, 3, 4, -2, -17, -20, -8, 12, 9, -12, -28, + 22, 10, 9, 20, 18, -4, -6, 1, 5, 9, 16, 18, -1, -22, -47, -48, + 22, 22, 27, 42, 41, 33, 21, 0, -17, -27, -28, -37, -39, -32, -19, -8, + 23, 4, -28, -27, -16, -5, 9, 28, 28, 0, -52, -61, -12, 18, 35, 56, + 23, -6, -26, -22, -18, -18, -20, -28, -23, -3, 9, 3, 1, 17, 44, 66, + 23, 21, 12, 10, 6, -5, 3, 8, 4, -1, 8, 11, -2, -16, -40, -43, + 24, 11, -4, -9, -8, -10, -21, -37, -44, -26, 0, 8, 17, 29, 36, 35, + 25, 11, 3, 7, 13, 14, 9, 2, -7, -13, -17, -12, -10, -10, -7, -8, + 25, 24, 17, 12, 20, 7, -11, -10, -15, -16, -16, -5, 3, -4, -11, -21, + 27, 26, 17, 6, -5, -16, -12, 2, -1, -9, -16, -7, 3, 3, -2, -15, + 28, 15, -10, -29, -30, -17, 4, 16, 33, 29, 15, 6, -5, -18, -18, -18, + 28, 12, 4, 16, 23, 12, 6, -1, -6, -7, 2, 8, -2, -19, -38, -37, + 29, 16, -1, -15, -25, -15, -12, -9, 18, 29, 23, 12, -1, -13, -16, -20, + 29, 28, 25, 33, 32, 6, -13, -20, -8, 4, 7, 0, -14, -25, -43, -40, + 29, 25, 19, 17, 20, 8, -12, -34, -34, -19, -14, -6, 2, 4, 3, -9, + 30, 28, 22, 14, 17, 16, 15, 8, -5, -19, -13, -4, -7, -20, -40, -41, + 30, 1, -20, -4, -6, -32, -37, -28, -18, -11, -7, -3, 8, 21, 44, 63, + 30, 42, 57, 45, 22, 6, 8, 2, -10, -27, -43, -50, -45, -33, -13, 9, + 32, 25, 20, 24, 23, 6, 3, -2, -10, -8, 4, 8, -8, -27, -47, -45, + 32, 40, 44, 29, 12, 0, -10, -18, -24, -20, -18, -23, -25, -19, -5, 4, + 34, 45, 56, 51, 33, 16, 10, 6, -8, -21, -25, -42, -55, -52, -33, -16, + 34, 53, 63, 61, 58, 44, 12, -26, -62, -68, -60, -61, -47, -18, 9, 9, + 34, 28, 22, 17, 16, 16, 9, 0, -6, -12, -26, -31, -21, -13, -13, -19, + 34, 23, 10, -15, -33, -24, -17, -15, -7, 0, -5, -10, -4, 5, 23, 35, + 35, 23, 12, 0, -16, -22, -17, 0, 17, 19, 14, 7, -5, -16, -24, -27, + 35, 32, -23, -51, -12, 33, 23, 0, -10, 1, -12, -29, -13, 9, 17, 0, + 36, 69, 78, 62, 30, -9, -40, -49, -49, -44, -48, -51, -37, -10, 19, 44, + 36, 21, 8, 11, 18, 8, 9, 8, 7, 3, 2, 1, -13, -28, -46, -44, + 37, 27, 11, 6, 14, 5, -6, -1, -9, -26, -37, -18, 3, 5, 0, -11, + 39, 31, 20, 13, 12, 6, -8, -12, -16, -16, -22, -18, -9, -6, -5, -10, + 39, 33, 25, 21, 22, 18, 7, -4, -24, -33, -33, -20, -9, -8, -14, -22, + 39, 52, 61, 57, 40, 27, 9, -12, -27, -37, -46, -50, -48, -40, -23, -2, + 40, 31, 20, 18, 14, -8, -5, 4, 8, 4, -1, -2, -14, -28, -42, -38, + 40, 29, 17, 19, 15, -9, -20, -18, -7, 1, 10, 9, -5, -15, -33, -33, + 41, 33, 23, 18, 20, 2, -13, -6, -6, -9, -16, -13, -12, -18, -21, -21, + 41, 58, 71, 51, 33, 19, -13, -42, -56, -58, -65, -67, -53, -17, 32, 65, + 41, 31, 15, 10, 4, -16, -7, 5, 7, 8, 12, 8, -7, -26, -44, -40, + 43, 40, 28, 23, 23, 6, -13, -20, -26, -18, -2, 3, -2, -13, -34, -36, + 43, 23, -11, -15, 2, 3, 6, 5, 3, 4, 11, 11, -2, -17, -33, -32, + 43, 54, 61, 49, 24, -1, -8, -3, -8, -21, -32, -43, -46, -39, -21, -9, + 43, 30, 9, -8, -2, 0, -10, -12, -14, -12, -12, -2, 4, 2, -3, -14, + 43, 35, 23, 15, 12, -8, -33, -27, -16, -12, -14, -4, 5, 0, -6, -13, + 44, 31, 19, 12, 4, -5, 1, -1, -10, -14, -1, 5, -3, -15, -33, -34, + 44, 28, 8, 8, 18, 13, 7, -3, -10, -8, -3, -3, -13, -23, -33, -31, + 46, 40, 9, -22, -17, 6, 18, 19, 6, -11, -14, -5, 5, -9, -35, -35, + 47, 47, 53, 51, 27, -3, -46, -65, -46, -33, -32, -29, -28, -17, 17, 56, + 47, 43, 27, 25, 24, 2, -2, -1, -17, -19, -1, 6, -11, -33, -50, -40, + 48, 50, 47, 34, 9, -7, -10, -9, -10, -12, -17, -28, -33, -30, -19, -12, + 48, 51, 51, 41, 25, 7, -18, -39, -47, -47, -44, -36, -21, -2, 14, 17, + 48, 23, -3, 10, 21, -2, -9, -6, -4, -2, 8, 15, 0, -23, -40, -36, + 50, 25, 2, -3, 4, 16, 13, -4, -21, -30, -30, -29, -20, -2, 13, 17, + 59, 44, 18, 13, 23, 18, 8, -7, -28, -26, -8, -3, -10, -24, -42, -35, + 61, 47, 30, 22, 25, 21, 8, -2, -16, -25, -14, -5, -15, -39, -56, -43, + 62, 46, 31, 37, 38, 16, -13, -33, -25, -7, -1, -3, -20, -39, -51, -38, + 63, 47, 23, 9, 5, -2, -13, -12, -8, -7, -14, -20, -26, -25, -15, -6, + 63, 42, 20, 9, 7, 0, -10, -18, -21, -22, -24, -24, -22, -16, 0, 17, + 64, 54, 43, 22, -17, -38, -42, -38, -37, -23, -17, -20, -15, -2, 22, 42, + 64, 51, 36, 34, 35, 21, 6, -9, -34, -41, -20, -7, -18, -35, -48, -36, + 66, 66, 65, 55, 35, 8, -9, -18, -29, -35, -39, -45, -47, -39, -24, -9, + 67, 38, -11, -47, -53, -27, -14, -19, -21, -21, 0, 6, 9, 19, 31, 45, + 70, 60, 50, 45, 32, 3, -17, -33, -34, -24, -10, -9, -22, -36, -44, -30, + 75, 57, 34, 30, 34, 20, -7, -29, -42, -36, -21, -15, -19, -28, -32, -20, + 76, 61, 27, -11, -30, -32, -29, -24, -24, -22, -18, -21, -16, 1, 22, 42, + 96, 77, 40, 5, -17, -27, -30, -28, -25, -24, -22, -23, -23, -16, 1, 16, + 99, 86, 61, 28, -5, -26, -39, -35, -34, -20, -5, -15, -34, -40, -22, 2, + 127, 101, 43, -12, -35, -34, -40, -42, -40, -40, -41, -40, -31, -6, 28, 61 +}; +#endif /* 1.25ms LRSNS */ + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +const LC3_FLOAT lrsns_gains_Q11[3][8] = { + { +1.8125 , +2.5 ,+3.3125, +4.3125, 0.0, 0.0, 0.0, 0.0}, /*4 splitLF Q11 */ + { +1.4375 , +1.8125 , +2.125 , +2.5 , +2.9375 , +3.5 , +4.0625 , +4.9375 }, /*8 full Q11 */ + { +1.375 , +1.75 , +2.0 , +2.3125 , +2.625 , +3.0625 , +3.5625 , +4.4375 } /*8 fix-env Q11 */ +}; + + +/* BASOP optimized fixenv tables */ +const LC3_INT16 env_Qs[SNSLR_N_FIXENV] = { 4, 4 , 4, 4 }; /* Q values for fix0 .. fix3 env searchrepresentation */ +const LC3_FLOAT env_Qscale[SNSLR_N_FIXENV] = { 1.0 / 16.0, 1.0 / 16.0 , 1.0 / 16.0,1.0 / 16.0 }; /* Qscaling values for fix0 .. fix3 env searchrepresentation */ + + +const LC3_INT16 signs_fix[SNSLR_N_FIXENV] = { 12, 12, 12, 10 }; + +const LC3_INT32 long_bell[M - 1 + 4] = { + 6, 6, 7, 7, 8 , + 8, 8, 7, 7, 6 , + 6, 5, 5, 5, 5 , + 5, 5, 5, 5 }; + +const LC3_INT32 decay[M - 1] = { /* floor((25:-1:11 -i)/2)*/ + 12,12,11,11,10, + 10, 9, 9, 8, 8, + 7, 7, 6, 6, 5 }; + +const LC3_INT32* env_ptrs[4] = { &(long_bell[4]), decay, &(long_bell[2]), long_bell }; + + +const LC3_FLOAT shift_en_norm_factors[SNSLR_N_FIXENV][SNSLR_N_FIXENV_SHIFTS] = +{ /* shift0, shift1, shift2, shift3 */ + /*12x init_bellamps , 1/sqrt( ), 1.0 / sqrt( ), 1.0 / sqrt( ), 1/sqrt( ) */ + {1485.0 / 32768.0, 1548.0 / 32768.0, 1620.0 / 32768.0, 1704.0 / 32768.0 } , + + /*12*decay: 1/sqrt( ), 1.0 / sqrt( ), 1.0 / sqrt( ),*/ + {980.0 / 32768.0 , 1031.0 / 32768.0 , 1091.0 / 32768.0 , 1154.0 / 32768.0}, + + + /*12x start bellamps , 1/sqrt( ), 1.0 / sqrt( ), 1.0 / sqrt( ), 1/sqrt( ) */ + { 1417.0 / 32768.0 , 1450.0 / 32768.0 , 1485.0 / 32768.0 , 1548.0 / 32768.0 }, + + + /*10x early_bellamps , 1/sqrt( ), 1.0 / sqrt( ), 1.0 / sqrt( ), 1/sqrt( ) */ + {1471.0 / 32768.0 , 1471.0 / 32768.0 , 1488.0 / 32768.0 , 1526.0 / 32768.0 } , +}; + +#endif + +#ifdef CR9_C_ADD_1p25MS + +#ifdef NEW_SIGNALLING_SCHEME_1p25 +const LC3_INT16 lrsns_ltp_bits[8] = { + 2, /*00(x=0) , ltp=0 ltpf=0 x is next param */ + 2, /*00(x=1) x is next param */ + 3 + 4, /*010 ltp=1 ltpf=0, phaseA or PhaseB(phaseB with reduced resolution) */ + 3 + 4, /*011 ltp=1 ltpf=0, PhaseB(phaseB with reduced resolution) */ + 2 + 4, /*10(x=0) ltp=1 ltpf=1, phase A , +extra bit */ + 2 + 4, /*10(x=1) */ + 2 + 5, /*11(x=0) ltp=1 ltpf=1, phase B , +extra bit */ + 2 + 5 /*11(x=1) */ +}; +#endif + + +const LC3_FLOAT MDCT_WINDOW_80_1_25ms[20] = { + 0.078459095727845, + 0.233445363855905, + 0.382683432365090, + 0.522498564715949, + 0.649448048330184, + 0.760405965600031, + 0.852640164354092, + 0.923879532511287, + 0.972369920397677, + 0.996917333733128, + 0.996917333733128, + 0.972369920397677, + 0.923879532511287, + 0.852640164354092, + 0.760405965600031, + 0.649448048330184, + 0.522498564715949, + 0.382683432365090, + 0.233445363855906, + 0.078459095727845 +}; + +const LC3_FLOAT MDCT_WINDOW_160_1_25ms[40] = { + 0.039259815759069, + 0.117537397457838, + 0.195090322016128, + 0.271440449865074, + 0.346117057077493, + 0.418659737537428, + 0.488621241496955, + 0.555570233019602, + 0.619093949309834, + 0.678800745532942, + 0.734322509435686, + 0.785316930880745, + 0.831469612302545, + 0.872496007072797, + 0.908143173825081, + 0.938191335922484, + 0.962455236453647, + 0.980785280403230, + 0.993068456954926, + 0.999229036240723, + 0.999229036240723, + 0.993068456954926, + 0.980785280403230, + 0.962455236453647, + 0.938191335922484, + 0.908143173825081, + 0.872496007072797, + 0.831469612302545, + 0.785316930880745, + 0.734322509435686, + 0.678800745532942, + 0.619093949309834, + 0.555570233019603, + 0.488621241496955, + 0.418659737537428, + 0.346117057077493, + 0.271440449865075, + 0.195090322016129, + 0.117537397457838, + 0.039259815759069 +}; + +const LC3_FLOAT MDCT_WINDOW_240_1_25ms[60] = { + 0.026176948307873, + 0.078459095727845, + 0.130526192220052, + 0.182235525492147, + 0.233445363855905, + 0.284015344703923, + 0.333806859233771, + 0.382683432365090, + 0.430511096808295, + 0.477158760259608, + 0.522498564715949, + 0.566406236924833, + 0.608761429008721, + 0.649448048330184, + 0.688354575693754, + 0.725374371012288, + 0.760405965600031, + 0.793353340291235, + 0.824126188622016, + 0.852640164354092, + 0.878817112661965, + 0.902585284349861, + 0.923879532511287, + 0.942641491092178, + 0.958819734868193, + 0.972369920397677, + 0.983254907563955, + 0.991444861373810, + 0.996917333733128, + 0.999657324975557, + 0.999657324975557, + 0.996917333733128, + 0.991444861373810, + 0.983254907563955, + 0.972369920397677, + 0.958819734868193, + 0.942641491092178, + 0.923879532511287, + 0.902585284349861, + 0.878817112661965, + 0.852640164354092, + 0.824126188622015, + 0.793353340291235, + 0.760405965600031, + 0.725374371012288, + 0.688354575693754, + 0.649448048330184, + 0.608761429008720, + 0.566406236924833, + 0.522498564715949, + 0.477158760259609, + 0.430511096808296, + 0.382683432365090, + 0.333806859233771, + 0.284015344703923, + 0.233445363855906, + 0.182235525492148, + 0.130526192220052, + 0.078459095727845, + 0.026176948307873 +}; + +const LC3_FLOAT MDCT_WINDOW_320_1_25ms[80] = { + 0.019633692460628, + 0.058870803651189, + 0.098017140329561, + 0.137012341681968, + 0.175796279934355, + 0.214309153065051, + 0.252491577015158, + 0.290284677254462, + 0.327630179561693, + 0.364470499879150, + 0.400748833103141, + 0.436409240673342, + 0.471396736825998, + 0.505657373377985, + 0.539138322911000, + 0.571787960227612, + 0.603555941953571, + 0.634393284163645, + 0.664252437911282, + 0.693087362545636, + 0.720853596702919, + 0.747508326862597, + 0.773010453362737, + 0.797320653772707, + 0.820401443525513, + 0.842217233716286, + 0.862734385977792, + 0.881921264348355, + 0.899748284052221, + 0.916187957117136, + 0.931214934758804, + 0.944806046466878, + 0.956940335732209, + 0.967599092360260, + 0.976765881320872, + 0.984426568089892, + 0.990569340443577, + 0.995184726672197, + 0.998265610184716, + 0.999807240482065, + 0.999807240482065, + 0.998265610184716, + 0.995184726672197, + 0.990569340443577, + 0.984426568089892, + 0.976765881320872, + 0.967599092360260, + 0.956940335732209, + 0.944806046466878, + 0.931214934758804, + 0.916187957117136, + 0.899748284052222, + 0.881921264348355, + 0.862734385977792, + 0.842217233716287, + 0.820401443525514, + 0.797320653772707, + 0.773010453362737, + 0.747508326862597, + 0.720853596702919, + 0.693087362545636, + 0.664252437911282, + 0.634393284163646, + 0.603555941953572, + 0.571787960227612, + 0.539138322911000, + 0.505657373377985, + 0.471396736825998, + 0.436409240673342, + 0.400748833103141, + 0.364470499879150, + 0.327630179561694, + 0.290284677254462, + 0.252491577015158, + 0.214309153065051, + 0.175796279934355, + 0.137012341681968, + 0.098017140329561, + 0.058870803651189, + 0.019633692460629 +}; + +const LC3_FLOAT MDCT_WINDOW_480_1_25ms[120] = { + 0.013089595571344, + 0.039259815759069, + 0.065403129230143, + 0.091501618663402, + 0.117537397457838, + 0.143492621991179, + 0.169349503849025, + 0.195090322016128, + 0.220697435021501, + 0.246153293028993, + 0.271440449865074, + 0.296541574975571, + 0.321439465303162, + 0.346117057077493, + 0.370557437509836, + 0.394743856384267, + 0.418659737537428, + 0.442288690219001, + 0.465614520325111, + 0.488621241496955, + 0.511293086077052, + 0.533614515915611, + 0.555570233019602, + 0.577145190037234, + 0.598324600570659, + 0.619093949309834, + 0.639439001980585, + 0.659345815100069, + 0.678800745532942, + 0.697790459841680, + 0.716301943424654, + 0.734322509435686, + 0.751839807478977, + 0.768841832073459, + 0.785316930880745, + 0.801253812691061, + 0.816641555161679, + 0.831469612302545, + 0.845727821703973, + 0.859406411501453, + 0.872496007072797, + 0.884987637463042, + 0.896872741532688, + 0.908143173825081, + 0.918791210148898, + 0.928809552871924, + 0.938191335922484, + 0.946930129495106, + 0.955019944457187, + 0.962455236453647, + 0.969230909706754, + 0.975342320508513, + 0.980785280403230, + 0.985556059058078, + 0.989651386819670, + 0.993068456954926, + 0.995804927574662, + 0.997858923238603, + 0.999229036240723, + 0.999914327574007, + 0.999914327574007, + 0.999229036240723, + 0.997858923238603, + 0.995804927574662, + 0.993068456954926, + 0.989651386819670, + 0.985556059058078, + 0.980785280403230, + 0.975342320508513, + 0.969230909706754, + 0.962455236453647, + 0.955019944457187, + 0.946930129495106, + 0.938191335922484, + 0.928809552871924, + 0.918791210148898, + 0.908143173825081, + 0.896872741532688, + 0.884987637463042, + 0.872496007072797, + 0.859406411501453, + 0.845727821703973, + 0.831469612302545, + 0.816641555161679, + 0.801253812691061, + 0.785316930880745, + 0.768841832073460, + 0.751839807478978, + 0.734322509435685, + 0.716301943424654, + 0.697790459841680, + 0.678800745532942, + 0.659345815100069, + 0.639439001980585, + 0.619093949309834, + 0.598324600570659, + 0.577145190037234, + 0.555570233019603, + 0.533614515915612, + 0.511293086077052, + 0.488621241496955, + 0.465614520325112, + 0.442288690219001, + 0.418659737537428, + 0.394743856384267, + 0.370557437509836, + 0.346117057077493, + 0.321439465303162, + 0.296541574975571, + 0.271440449865074, + 0.246153293028993, + 0.220697435021501, + 0.195090322016129, + 0.169349503849025, + 0.143492621991180, + 0.117537397457838, + 0.091501618663403, + 0.065403129230143, + 0.039259815759069, + 0.013089595571345 +}; +#endif /* CR9_C_ADD_1p25MS */ + +#ifdef CR9_C_ADD_1p25MS +const LC3_INT BW_cutoff_bin_all_1_25ms[] = {10, 20, 30, 40, 50, 50}; +#endif + +#ifdef CR9_C_ADD_1p25MS +const LC3_FLOAT sns_preemph_maxTilt_32[64] = /*Q19 in Word32*/ { ++0.000000000000000e+00, +4.260444641113281e-02, +9.552001953125000e-02, +1.606330871582031e-01, +2.401504516601562e-01, +3.366279602050781e-01, +4.530467987060547e-01, +5.928611755371094e-01, +7.600822448730469e-01, +9.593582153320312e-01, +1.196073532104492e+00, +1.476461410522461e+00, +1.807731628417969e+00, +2.198219299316406e+00, +2.657554626464844e+00, +3.196861267089844e+00, +3.828969955444336e+00, +4.568693161010742e+00, +5.433101654052734e+00, +6.441873550415039e+00, +7.617681503295898e+00, +8.986635208129883e+00, +1.057879638671875e+01, +1.242875480651855e+01, +1.457631111145020e+01, +1.706723594665527e+01, +1.995415306091309e+01, +2.329755401611328e+01, +2.716694641113281e+01, +3.164218902587891e+01, +3.681499481201172e+01, +4.279068374633789e+01, +4.969015884399414e+01, +5.765219688415527e+01, +6.683605003356934e+01, +7.742443466186523e+01, +8.962694740295410e+01, +1.036839809417725e+02, +1.198712043762207e+02, +1.385046882629395e+02, +1.599467639923096e+02, +1.846127471923828e+02, +2.129786052703857e+02, +2.455897598266602e+02, +2.830711212158203e+02, +3.261386032104492e+02, +3.756122741699219e+02, +4.324314041137695e+02, +4.976716747283936e+02, +5.725648708343506e+02, +6.585213947296143e+02, +7.571560096740723e+02, +8.703172912597656e+02, +1.000121299743652e+03, +1.148990077972412e+03, +1.319695686340332e+03, +1.515410537719727e+03, +1.739764961242676e+03, +1.996912996292114e+03, +2.291607635498047e+03, +2.629286834716797e+03, +3.016171834945679e+03, +3.459379568099976e+03, +3.967051151275635e+03 +}; +#else +const LC3_FLOAT sns_preemph_maxTilt_32[64] = { 1.0000000000, 1.1422936900, 1.3048348743, 1.4905046434, 1.7025940491, 1.9448624389, 2.2216040920, 2.5377243360, 2.8988264960, 3.3113112148, 3.7824899064, 4.3207143526, 4.9355247414, 5.6378187690, 6.4400448053, 7.3564225446, 8.4031950538, 9.5989166860, 10.9647819614, 12.5250012470, 14.3072298919, 16.3430584272, 18.6685725171, 21.3249925879, 24.3594044729, 27.8255940221, 31.7850004725, 36.3078054770, 41.4741770949, 47.3756907943, 54.1169526546, 61.8174535405, 70.6136871125, 80.6615692177, 92.1392015445, 105.2500285278, 120.2264434617, 137.3339077399, 156.8756562372, 179.1980722375, 204.6968271808, 233.8238940558, 267.0955587559, 305.1015713993, 348.5155998248, 398.1071705535, 454.7553088751, 519.4641198313, 593.3805862753, 677.8148994829, 774.2636826811, 884.4365191386, 1010.2862550356, 1154.0436142415, 1318.2567385564, 1505.8363542798, 1720.1073656972, 1964.8677899935, 2244.4560782338, 2563.8280156944, 2928.6445646252, 3345.3722064839, 3821.3975622362, 4365.1583224017 }; +#endif + +#ifdef CR9_C_ADD_1p25MS +const LC3_FLOAT sns_preemph_maxTilt_48[64] = /*Q17 in Word32 */ +{+0.000000000000000e+00, +5.003356933593750e-02, +1.141586303710938e-01, +1.953964233398438e-01, +2.973327636718750e-01, +4.242401123046875e-01, +5.811920166015625e-01, +7.742080688476562e-01, +1.010452270507812e+00, +1.298377990722656e+00, +1.648010253906250e+00, +2.071212768554688e+00, +2.581977844238281e+00, +3.196861267089844e+00, +3.935371398925781e+00, +4.820526123046875e+00, +5.879447937011719e+00, +7.144096374511719e+00, +8.652076721191406e+00, +1.044763946533203e+01, +1.258283996582031e+01, +1.511886596679688e+01, +1.812760925292969e+01, +2.169351959228516e+01, +2.591576385498047e+01, +3.091072845458984e+01, +3.681499481201172e+01, +4.378875732421875e+01, +5.201988220214844e+01, +6.172859954833984e+01, +7.317304229736328e+01, +8.665567016601562e+01, +1.025307922363281e+02, +1.212133789062500e+02, +1.431893615722656e+02, +1.690275802612305e+02, +1.993938980102539e+02, +2.350675277709961e+02, +2.769602203369141e+02, +3.261386032104492e+02, +3.838503112792969e+02, +4.515544738769531e+02, +5.309573440551758e+02, +6.240538558959961e+02, +7.331761932373047e+02, +8.610504531860352e+02, +1.010862823486328e+03, +1.186336814880371e+03, +1.391823417663574e+03, +1.632406326293945e+03, +1.914024772644043e+03, +2.243616851806641e+03, +2.629286834716797e+03, +3.080500373840332e+03, +3.608312332153320e+03, +4.225632614135742e+03, +4.947536384582520e+03, +5.791625976562500e+03, +6.778453254699707e+03, +7.932012306213379e+03, +9.280314323425293e+03, +1.085605828857422e+04, +1.269741340637207e+04, +1.484893192291260e+04 +}; +#else +const LC3_FLOAT sns_preemph_maxTilt_48[64] = {0.0000000000, 0.0500304087, 0.1141593061, 0.1953976981, 0.2973340176, 0.4242389512, 0.5811887026, 0.7742098746, 1.0104496903, 1.2983759103, 1.6480115389, 2.0712102820, 2.5819797245, 3.1968603815, 3.9353701523, 4.8205253208, 5.8794511276, 7.1440971377, 8.6520751946, 10.4476407528, 12.5828418809, 15.1188643151, 18.1276057212, 21.6935178910, 25.9157621116, 30.9107305378, 36.8149952662, 43.7887571592, 52.0198785456, 61.7285980240, 73.1730420472, 86.6556671708, 102.5307892527, 121.2133820430, 143.1893581115, 169.0275806671, 199.3938963586, 235.0675276210, 276.9602196730, 326.1386032534, 383.8503111474, 451.5544763518, 530.9573444802, 624.0538552089, 733.1761901174, 861.0504505627, 1010.8628231966, 1186.3368169939, 1391.8234195728, 1632.4063284191, 1914.0247716809, 2243.6168519816, 2629.2868351532, 3080.5003754823, 3608.3123335035, 4225.6326173056, 4947.5363821034, 5791.6259768788, 6778.4532561811, 7932.0123088052, 9280.3143269988, 10856.0582896592, 12697.4134068387, 14848.9319246111}; +#endif + +const LC3_FLOAT *sns_preemph_adaptMaxTilt_all[6] = {NULL, NULL, NULL, sns_preemph_maxTilt_32, sns_preemph_maxTilt_48, NULL}; + +#ifdef CR9_C_ADD_1p25MS +const LC3_INT bands_number_1_25ms[] = {10, 20, 27, 31, 33}; +#endif + /* DCT */ #define ENTRY_DCT2_1 {0.353553, 0.000000} #define ENTRY_DCT2_2 {0.351851, -0.034654} @@ -540,22 +1265,24 @@ const LC3_FLOAT sns_HFCB[8][32] = { -1.28790471791471, -1.50335652955529, 0.406319437516838, -3.02457606944550, -0.935353148761338, -0.656270971328114, 1.75920379670881}}; -const LC3_INT pvq_enc_A[16][11] = {{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19}, - {0, 1, 5, 13, 25, 41, 61, 85, 113, 145, 181}, - {0, 1, 7, 25, 63, 129, 231, 377, 575, 833, 1159}, - {0, 1, 9, 41, 129, 321, 681, 1289, 2241, 3649, 5641}, - {0, 1, 11, 61, 231, 681, 1683, 3653, 7183, 13073, 22363}, - {0, 1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081, 75517}, - {0, 1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545, 224143}, - {0, 1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729, 598417}, - {0, 1, 19, 181, 1159, 5641, 22363, 75517, 224143, 598417, 1462563}, - {0, 1, 21, 221, 1561, 8361, 36365, 134245, 433905, 1256465, 3317445}, - {0, 1, 23, 265, 2047, 11969, 56695, 227305, 795455, 2485825, 7059735}, - {0, 1, 25, 313, 2625, 16641, 85305, 369305, 1392065, 4673345, 14218905}, - {0, 1, 27, 365, 3303, 22569, 124515, 579125, 2340495, 8405905, 27298155}, - {0, 1, 29, 421, 4089, 29961, 177045, 880685, 3800305, 14546705, 50250765}, - {0, 1, 31, 481, 4991, 39041, 246047, 1303777, 5984767, 24331777, 89129247}}; +const LC3_UINT32 pvq_enc_A[16][11] = { + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 }, + { 0, 1, 5, 13, 25, 41, 61, 85, 113, 145, 181 }, + { 0, 1, 7, 25, 63, 129, 231, 377, 575, 833, 1159 }, + { 0, 1, 9, 41, 129, 321, 681, 1289, 2241, 3649, 5641 }, + { 0, 1, 11, 61, 231, 681, 1683, 3653, 7183, 13073, 22363 }, + { 0, 1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081, 75517 }, + { 0, 1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545, 224143 }, + { 0, 1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729, 598417 }, + { 0, 1, 19, 181, 1159, 5641, 22363, 75517, 224143, 598417, 1462563 }, + { 0, 1, 21, 221, 1561, 8361, 36365, 134245, 433905, 1256465, 3317445 }, + { 0, 1, 23, 265, 2047, 11969, 56695, 227305, 795455, 2485825, 7059735 }, + { 0, 1, 25, 313, 2625, 16641, 85305, 369305, 1392065, 4673345, 14218905 }, + { 0, 1, 27, 365, 3303, 22569, 124515, 579125, 2340495, 8405905, 27298155 }, + { 0, 1, 29, 421, 4089, 29961, 177045, 880685, 3800305, 14546705, 50250765 }, + { 0, 1, 31, 481, 4991, 39041, 246047, 1303777, 5984767, 24331777, 89129247 } +}; const LC3_FLOAT lp_scale_factors[6] = {1, 1, 0.6666666666666666, .5, 0.3333333333333333, 0.16666666666666666}; @@ -4696,6 +5423,7 @@ const LC3_FLOAT MDCT_HRA_WINDOW_480_5ms[480] = { 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, + 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00}; @@ -6315,4 +7043,45 @@ const LC3_INT32 mdct_grp_bins[10] = { 4, 14, 24, 44, 84, 164, 244, 324, 404, 484 const LC3_INT16 plc_fadeout_param_maxlen[4] = {800, 400, 266, 200}; const LC3_INT16 plc_fadeout_param_maxbytes[4] = {27, 14, 9, 7}; +#ifdef CR9_C_ADD_1p25MS +const LC3_FLOAT* MDCT_WINS_1_25ms[2][6] = { + {MDCT_WINDOW_80_1_25ms, MDCT_WINDOW_160_1_25ms, MDCT_WINDOW_240_1_25ms, MDCT_WINDOW_320_1_25ms, MDCT_WINDOW_480_1_25ms, NULL}, + {NULL, NULL, NULL, NULL, NULL, NULL}}; +const LC3_INT MDCT_la_zeroes_1_25ms[6] = {0, 0, 0, 0, 0, 0}; +#endif /* CR9_C_ADD_1p25MS */ +#ifdef CR9_C_ADD_1p25MS +const LC3_INT MDCT_WINDOWS_LENGTHS_1_25ms[6] = {20, 40, 60, 80, 120, 240}; +#endif +#ifdef CR9_C_ADD_1p25MS +const LC3_INT ACC_COEFF_PER_BAND_8_1_25ms[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +const LC3_INT ACC_COEFF_PER_BAND_16_1_25ms[21] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + +const LC3_INT ACC_COEFF_PER_BAND_24_1_25ms[28] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30}; + +const LC3_INT ACC_COEFF_PER_BAND_32_1_25ms[32] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 31, 33, 35, 37, 40}; + +const LC3_INT ACC_COEFF_PER_BAND_48_1_25ms[34] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 43, 46, 50}; + +const LC3_INT ACC_COEFF_PER_BAND_PLC_8_1_25ms[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +const LC3_INT ACC_COEFF_PER_BAND_PLC_16_1_25ms[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + +const LC3_INT ACC_COEFF_PER_BAND_PLC_24_1_25ms[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; + +const LC3_INT ACC_COEFF_PER_BAND_PLC_32_1_25ms[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}; + +const LC3_INT ACC_COEFF_PER_BAND_PLC_48_1_25ms[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60}; + +const LC3_INT* ACC_COEFF_PER_BAND_PLC_1_25ms[] = { + ACC_COEFF_PER_BAND_PLC_8_1_25ms, ACC_COEFF_PER_BAND_PLC_16_1_25ms, ACC_COEFF_PER_BAND_PLC_24_1_25ms, ACC_COEFF_PER_BAND_PLC_32_1_25ms, ACC_COEFF_PER_BAND_PLC_48_1_25ms}; +#endif /* CR9_C_ADD_1p25MS */ + +#ifdef CR9_C_ADD_1p25MS +const LC3_INT* ACC_COEFF_PER_BAND_1_25ms[5] = {ACC_COEFF_PER_BAND_8_1_25ms, ACC_COEFF_PER_BAND_16_1_25ms, + ACC_COEFF_PER_BAND_24_1_25ms, ACC_COEFF_PER_BAND_32_1_25ms, + ACC_COEFF_PER_BAND_48_1_25ms}; +#endif + + const LC3_INT16 PLC_FADEOUT_TYPE_2_SELECTOR = 10; /* can take values from 0 to 10, default is 10 for longer fadeout */ diff --git a/lib_lc3plus/constants.h b/lib_lc3plus/constants.h index 88d9ed137d..853303e59e 100644 --- a/lib_lc3plus/constants.h +++ b/lib_lc3plus/constants.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -15,6 +15,45 @@ #include "defines.h" #include "structs.h" +#ifdef CR9_C_ADD_1p25MS_LRSNS +extern const LC3_FLOAT lrsns_st1A_topTab_1bitNoDC[2 * 16]; +extern const LC3_INT16 lrsns_ltp_bits[8]; + +/* BASOP table SNSLR st1B_idx search variables */ +extern const LC3_INT16 st1SCF0_7_base5_32x8_Q11[32 * 8]; /* legacy 10ms quantized BASOP SNS stage1 LF tables 32x8=256 values */ +extern const LC3_INT16 st1SCF8_15_base5_32x8_Q11[32 * 8]; /* legacy 10ms quntized BASOP SNS stage1 HF tables 32x8=256 values */ + +/* LRSNS st1B, tables to construct from legacy BASOP st1(LF,HF) tables */ +extern const LC3_INT16 lrsns_st1B_merged170orderSortedSegmCnt[4]; +extern const LC3_INT16 lrsns_st1B_merged170orderSortedSegmCum[5]; +extern const LC3_INT16 lrsns_st1B_merged170orderSort12bitIdx[170]; + + +/* LRSNS tables to construct st1C from Word8in Q7 and a Q4 scalefactor */ +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY +extern const LC3_FLOAT * lrsns_st1CTrainedMapMeans[2]; +#else +extern const LC3_FLOAT * lrsns_st1CTrainedMapMeans[3]; +#endif +extern const LC3_INT8 lrsns_st1C_Both_Word8[170 * 16]; + +extern const LC3_INT16 lrsns_st1C_Both_scaleQ4_7p4bits_fx[2]; +extern const LC3_INT16 lrsns_st1C_Both_inv_scaleQ15_7p4bits_fx[2]; + +#endif /* CR9_C_ADD_1p25MS_LRSNS */ + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +extern const LC3_FLOAT lrsns_gains_Q11[3][8]; /* both enc and dec */ + +extern const LC3_INT16 signs_fix[SNSLR_N_FIXENV]; +extern const LC3_INT16 env_Qs[SNSLR_N_FIXENV]; +extern const LC3_FLOAT env_Qscale[SNSLR_N_FIXENV]; +extern const LC3_FLOAT shift_en_norm_factors[SNSLR_N_FIXENV][SNSLR_N_FIXENV_SHIFTS]; +extern const LC3_INT32* env_ptrs[SNSLR_N_FIXENV]; +#endif + +extern const LC3_FLOAT *sns_preemph_adaptMaxTilt_all[6]; /* DCT */ extern const Complex dct2_16[16]; @@ -30,7 +69,7 @@ extern const LC3_FLOAT sns_W[6]; extern const LC3_FLOAT *sns_preemph_all[6]; extern const LC3_FLOAT sns_LFCB[8][32]; extern const LC3_FLOAT sns_HFCB[8][32]; -extern const LC3_INT pvq_enc_A[16][11]; +extern const LC3_UINT32 pvq_enc_A[16][11]; extern const LC3_FLOAT idct_lookup[M][M]; /* 12.8 kHz resampler */ @@ -113,6 +152,11 @@ extern const LC3_INT bands_number_2_5ms_HR[6]; extern const LC3_INT BW_cutoff_bin_all_2_5ms[MAX_BW_BANDS_NUMBER]; extern const LC3_INT bands_number_2_5ms[5]; +#ifdef CR9_C_ADD_1p25MS +extern const LC3_INT bands_number_1_25ms[5]; +extern const LC3_INT BW_cutoff_bin_all_1_25ms[]; +#endif + extern const LC3_INT BW_warp_idx_start_16k_5ms[4]; extern const LC3_INT BW_warp_idx_stop_16k_5ms[4]; extern const LC3_INT BW_warp_idx_start_24k_5ms[4]; @@ -168,6 +212,19 @@ extern const LC3_FLOAT MDCT_WINDOW_480_5ms[480]; extern const LC3_FLOAT* MDCT_WINS_5ms[2][6]; extern const LC3_INT MDCT_la_zeroes_5ms[6]; +#ifdef CR9_C_ADD_1p25MS +extern const LC3_FLOAT MDCT_WINDOW_80_1_25ms[20]; +extern const LC3_FLOAT MDCT_WINDOW_160_1_25ms[40]; +extern const LC3_FLOAT MDCT_WINDOW_240_1_25ms[60]; +extern const LC3_FLOAT MDCT_WINDOW_320_1_25ms[80]; +extern const LC3_FLOAT MDCT_WINDOW_480_1_25ms[120]; +extern const LC3_FLOAT* MDCT_WINS_1_25ms[2][6]; +extern const LC3_INT MDCT_la_zeroes_1_25ms[6]; +#ifdef NEW_SIGNALLING_SCHEME_1p25 +extern const LC3_INT16 lrsns_ltp_bits[8]; +#endif +#endif /* CR9_C_ADD_1p25MS */ + extern const LC3_FLOAT* MDCT_WINS_7_5ms[2][6]; extern const LC3_INT32 MDCT_la_zeroes_7_5ms[6]; @@ -176,7 +233,16 @@ extern const LC3_INT MDCT_WINDOWS_LENGTHS_7_5ms[6]; extern const LC3_INT MDCT_WINDOWS_LENGTHS_5ms[6]; extern const LC3_INT MDCT_WINDOWS_LENGTHS_2_5ms[6]; +#ifdef CR9_C_ADD_1p25MS +extern const LC3_INT MDCT_WINDOWS_LENGTHS_1_25ms[6]; +#endif + /* Per band energy */ +#ifdef CR9_C_ADD_1p25MS +extern const LC3_INT* ACC_COEFF_PER_BAND_1_25ms[5]; +extern const LC3_INT* ACC_COEFF_PER_BAND_PLC_1_25ms[5]; +#endif + extern const LC3_INT* ACC_COEFF_PER_BAND[6]; extern const LC3_INT* ACC_COEFF_PER_BAND_HR[6]; diff --git a/lib_lc3plus/cutoff_bandwidth.c b/lib_lc3plus/cutoff_bandwidth.c index a2a617bd0d..a498dc65e7 100644 --- a/lib_lc3plus/cutoff_bandwidth.c +++ b/lib_lc3plus/cutoff_bandwidth.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/dct4.c b/lib_lc3plus/dct4.c index 4b4a3f6a0f..d90d7f5f5f 100644 --- a/lib_lc3plus/dct4.c +++ b/lib_lc3plus/dct4.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -52,8 +52,8 @@ void dct4_init(Dct4* dct, int length) int i; assert(length <= MAX_LEN); dct->length = length; - dct->twid1 = calloc(sizeof(*dct->twid1), length / 2); - dct->twid2 = calloc(sizeof(*dct->twid2), length / 2); + dct->twid1 = calloc(length / 2, sizeof(*dct->twid1)); + dct->twid2 = calloc(length / 2, sizeof(*dct->twid2)); for (i = 0; i < length / 2; i++) { dct->twid1[i] = cexpi(-(LC3_FLOAT)M_PI_LC3PLUS * (i + (LC3_FLOAT)0.25) / length); dct->twid2[i] = cexpi(-(LC3_FLOAT)M_PI_LC3PLUS * i / length); diff --git a/lib_lc3plus/dec_entropy.c b/lib_lc3plus/dec_entropy.c index cd82570a81..0d40df1020 100644 --- a/lib_lc3plus/dec_entropy.c +++ b/lib_lc3plus/dec_entropy.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,9 +11,23 @@ #include "wmc_auto.h" #include "functions.h" +static const LC3_INT32 gainMSBbits[4] = {1, 1, 2, 2}; + static void read_bit_fl(LC3_UINT8* ptr, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT* bit); static void read_uint_fl(LC3_INT nbits, LC3_UINT8* ptr, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT* val); +#ifdef CR9_C_ADD_1p25MS_LRSNS +void readSNSData_fl(LC3_UINT8* ptr, LC3_INT32* bfi, LC3_INT32* mask_side_local, LC3_INT32* bp_side_local, const LC3_INT32* ltpf_idx_2_lrsns, LC3_INT32* sns_vq_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT32 plc_trigger_SNS1, LC3_INT32 plc_trigger_SNS2); +#else +void readSNSData_fl(LC3_UINT8* ptr, LC3_INT32* bfi, LC3_INT32* mask_side_local, LC3_INT32* bp_side_local, LC3_INT32* sns_vq_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT32* plc_trigger_SNS1, LC3_INT32* plc_trigger_SNS2); +#endif + + + +#ifdef NEW_SIGNALLING_SCHEME_1p25 +void readLtpData_fl(LC3_UINT8* ptr, LC3_INT32* bfiPtr, LC3_INT32* mask_side, LC3_INT32* bp_side, LC3_INT32* ltpf_idx, LC3_INT32* rx_status, LC3_INT32* ltpfinfo_frame_cntr, LC3_INT16* mem_continuation); +#endif + void read_bit_fl(LC3_UINT8* ptr, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT* bit) { if (ptr[*bp_side] & *mask_side) { @@ -54,13 +68,13 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT LC3_INT nbbytes = nbbits >> 3; LC3_INT lastnz; LC3_INT bw_cutoff_idx; - LC3_INT nbits = ceil(LC3_LOGTWO(L_spec / 2)); - + LC3_INT nbits = getLastNzBits (L_spec); + if (nbits > nbbits) { return 1; } - + *np_zero = 0; *total_padding = 0; @@ -71,7 +85,7 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT if (bp_side < 19 || bp_side >= LC3PLUS_MAX_BYTES) { return 1; } - + ptr = bytes; if (bw_cutoff_bits > 0) { @@ -90,7 +104,7 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT /* Read 4 reserved bits */ read_uint_fl(4, ptr, &mask_side, &bp_side, &val); - + if (ep_enabled == 0) { /* Discard padding length bytes */ @@ -102,7 +116,7 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT *total_padding = *total_padding + 2; *np_zero = *np_zero + padding_len; } - + /* check if minimum payload size is reached */ if ((nbbytes - (*total_padding + *np_zero)) < 20) { return 1; @@ -115,26 +129,435 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT read_uint_fl(nbits, ptr, &mask_side, &bp_side, &lastnz); } - + if (ep_enabled != 0) { *total_padding = *total_padding + *np_zero; } - + return 0; } #endif + +#ifdef NEW_SIGNALLING_SCHEME_1p25 +void readLtpData_fl( + LC3_UINT8* ptr, + LC3_INT32* bfiPtr, + LC3_INT32* mask_side, + LC3_INT32* bp_side, + LC3_INT32* ltpf_idx, + LC3_INT32* rx_status, + LC3_INT32* ltpfinfo_frame_cntr, + LC3_INT16* mem_continuation +) +{ + LC3_INT32 rx_current_status = -1; + LC3_INT32 tmp, MSBs, LSBs; + + /* Hdr, information , bits used + 00 , no lag info , no phase info sum=2 + 010, PhaseA,LTPF=0, lagAbits=4 , sum=7 : PLC may be activated, 4 MSbs + 011, PhaseB,LTPF=0, lagBbits=4*, sum=7* : PLC may be activated, 4* = reduced lag resolution in Q_ltpf_Idx domain for PLC-activation + 10 , PhaseA,LTPF=1, lagAbits=4 , sum=6 : LTPF activated + 11 , PhaseB,LTPF=1, lagBbits=5 , sum=7 : LTPF activated + */ + + ltpf_idx[2] = -1; /* no ready lag available, conditionally decoded if phase is B, and consecutive A/B has arrived */ + + read_uint_fl(2, ptr, mask_side, bp_side, &tmp); + if (tmp == 0) /* "00" */ + { + ltpf_idx[0] = 0; /* ltp ltpf/lag was not transmitted */ + ltpf_idx[1] = 0; /* ltpf activation bit zeroed */ + + rx_status[0] = -32768; /* set unknown phase A , due to rx LTP==0 */ + rx_status[1] = -1; /* set unknown phase A MSBs content */ + *ltpfinfo_frame_cntr = -32768; + assert(ltpf_idx[2] < 0); /* ltpf_idx[2] = -1; , no ready lag available */ +#ifdef FIX_LTPF_1p25 + *mem_continuation = 0; /* also kill lag continuation state */ +#endif + } + else if (tmp == 1) /* "01" */ + { + ltpf_idx[0] = 1; + ltpf_idx[1] = 0; /* LTP=1, LTPF=0, inactive ltpf */ + read_bit_fl(ptr, mask_side, bp_side, &rx_current_status); + + if (rx_current_status == 0) + { + rx_status[0] = 0; /* phaseA */ + read_uint_fl(4, ptr, mask_side, bp_side, &(rx_status[1])); /* read four MSBs, and store in rx_status[1] */ +#ifdef FIX_LTPF_1p25 + if (*mem_continuation == 0) + { + *mem_continuation = 1; + } +#endif + *ltpfinfo_frame_cntr = 0; /*same as rx_status [0] */ + } + else + { /* LSB part of delta coded lag information */ + assert(rx_current_status == 1); + read_uint_fl(4, ptr, mask_side, bp_side, &LSBs); + LSBs = (LSBs << 1); /* NB Least Signifcant bit is on purpose always zero, truncation on encoder side */ + if ( rx_status[1] < 0 ) + { + *bfiPtr = 1; + return; + } + ltpf_idx[2] = ((rx_status[1] << 5) | LSBs); /* bitwise OR */ + + /* check frame cntr info to not combine oldA with a newB */ + if (*ltpfinfo_frame_cntr != 1) + { + rx_status[0] = -32768; /*even number of bfi frames may have happened */ + ltpf_idx[1] = 0; + ltpf_idx[2] = -1; /* send signal of non-decoded lag to PLC and LTPF decoder */ + } +#ifdef FIX_LTPF_MEM_CONTINUATION + else + { + *mem_continuation = 0; + } + # endif + rx_status[0] = -32768; + *ltpfinfo_frame_cntr = -32678; + } + } + else + { /* 2 or 3 */ + ltpf_idx[0] = 1; + ltpf_idx[1] = 1; /* active ltpf */ + + if (tmp == 2) /* 2="10" */ + { + /* phaseA */ + read_uint_fl(4, ptr, mask_side, bp_side, &MSBs); + + rx_status[0] = 0; + rx_status[1] = MSBs; /* remember the four MSBs */ +#ifdef FIX_LTPF_1p25 + if (*mem_continuation == 0) + { + *mem_continuation = 1; + } +#endif + *ltpfinfo_frame_cntr = 0; + assert(ltpf_idx[2] < 0); /* ltpf_idx[2] = -1; , no ready lag available */ + } + else + { /* 3="11" */ + assert(tmp == 3); /* phaseB */ + read_uint_fl(5, ptr, mask_side, bp_side, &LSBs); /* all 5 LSBs available*/ + if ( rx_status[1] < 0 ) + { + *bfiPtr = 1; + return; + } + ltpf_idx[2] = ((rx_status[1] << 5) | LSBs); /* bitwise OR */ + + /* check frame cntr info to not combine oldA with a newB */ + if (*ltpfinfo_frame_cntr != 1) + { + ltpf_idx[1] = 0; /* turn off LTPF activation for now, so that ltpf_idx[2] is not read */ + ltpf_idx[2] = -1; /* send signal to PLC and ltpf_decoder, that phase B could not be decoded */ + } + *ltpfinfo_frame_cntr = -32678; /*cntr init in phaseA*/ + rx_status[0] = -32768; /* phase init in phaseA*/ + +#ifdef FIX_LTPF_MEM_CONTINUATION + *mem_continuation = 0; +#endif + } + } + +} +#endif + + +#ifdef CR9_C_ADD_1p25MS_LRSNS +void readSNSData_fl(LC3_UINT8* ptr, LC3_INT32* bfi, LC3_INT32* mask_side_local, LC3_INT32* bp_side_local, const LC3_INT32* ltpf_idx_2_lrsns, + LC3_INT32* sns_vq_idx, LC3PLUS_FrameDuration frame_dms, + LC3_INT32 plc_trigger_SNS1, LC3_INT32 plc_trigger_SNS2) +#else +void readSNSData_fl(LC3_UINT8* ptr, LC3_INT32* bfi, LC3_INT32* mask_side_local, LC3_INT32* bp_side_local, LC3_INT32* sns_vq_idx, + LC3PLUS_FrameDuration frame_dms, LC3_INT32* plc_trigger_SNS1, LC3_INT32* plc_trigger_SNS2) +#endif +{ + LC3_INT32 tmp, submodeMSB, idxBorGainLSB, submodeLSB; +#ifndef CR9_C_ADD_1p25MS + (void) frame_dms; +#endif +#ifdef CR9_C_ADD_1p25MS_LRSNS + LC3_INT32 read_legacy_sns_vq_bits; +#endif + + + UNUSED(frame_dms); + +#ifdef CR9_C_ADD_1p25MS_LRSNS + read_legacy_sns_vq_bits = 1; + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + read_legacy_sns_vq_bits = 0; /* decode 9, 10, or 29/30 bits */ + } + + if (read_legacy_sns_vq_bits != 0) + { +#endif + /* SNS-VQ 1st stage */ + read_uint_fl(5, ptr, mask_side_local, bp_side_local, &sns_vq_idx[SNS_IDX_LF]); + read_uint_fl(5, ptr, mask_side_local, bp_side_local, &sns_vq_idx[SNS_IDX_HF]); + + /* SNS-VQ 2nd stage side-info (3-4 bits) */ + read_bit_fl(ptr, mask_side_local, bp_side_local, &submodeMSB); + + read_uint_fl(gainMSBbits[submodeMSB * 2], ptr, mask_side_local, bp_side_local, &sns_vq_idx[SNS_IDX_GAIN]); + read_bit_fl(ptr, mask_side_local, bp_side_local, &sns_vq_idx[SNS_IDX_LS_INDA]); + + /* SNS-VQ 2nd stage VQ decoding (24-25 bits) */ + if (submodeMSB == 0) + { + read_uint_fl(25, ptr, mask_side_local, bp_side_local, &tmp); + if (tmp >= 33460056) + { +#ifdef CR9_C_ADD_1p25MS_LRSNS + *bfi = plc_trigger_SNS1; +#else + *bfi = *plc_trigger_SNS1; +#endif + if (*bfi) + { + return; + } + } + idxBorGainLSB = floor(tmp / 2390004); + sns_vq_idx[SNS_IDX_A] = tmp - idxBorGainLSB * 2390004; + + if (idxBorGainLSB < 2) + { + submodeLSB = 1; + sns_vq_idx[SNS_IDX_GAIN] = sns_vq_idx[SNS_IDX_GAIN] * 2 + idxBorGainLSB; + sns_vq_idx[SNS_IDX_BORGAINLSB] = -2; + } + else + { + submodeLSB = 0; + sns_vq_idx[SNS_IDX_BORGAINLSB] = idxBorGainLSB - 2; + } + } + else + { + read_uint_fl(24, ptr, mask_side_local, bp_side_local, &tmp); + if (tmp >= 16708096) + { +#ifdef CR9_C_ADD_1p25MS_LRSNS + *bfi = plc_trigger_SNS2; +#else + *bfi = *plc_trigger_SNS2; +#endif + if (*bfi) + { + return; + } + } + + if (tmp >= 15158272) + { + submodeLSB = 1; + tmp -= 15158272; + sns_vq_idx[SNS_IDX_GAIN] = sns_vq_idx[SNS_IDX_GAIN] * 2 + (tmp & 1); + sns_vq_idx[SNS_IDX_A] = floor(tmp / 2); + sns_vq_idx[SNS_IDX_BORGAINLSB] = -2; + } + else + { + submodeLSB = 0; + sns_vq_idx[SNS_IDX_A] = tmp; + sns_vq_idx[SNS_IDX_BORGAINLSB] = -1; + } + } + + sns_vq_idx[SNS_IDX_SHAPEJ] = submodeMSB * 2 + submodeLSB; + +#ifdef CR9_C_ADD_1p25MS_LRSNS + } + + if (read_legacy_sns_vq_bits == 0) + { + LC3_INT32 shape_idx = -1; + LC3_INT32 gain_idx = -1; + LC3_INT32 aux_idx = -1; + LC3_INT32 tmp_shape = -1; + LC3_INT32 stop_bit = -1; + /* SNS-VQ 1st stage in 9-10 bits */ + read_uint_fl(9, ptr, mask_side_local, bp_side_local, &sns_vq_idx[SNS_IDX_LF]); + if (sns_vq_idx[SNS_IDX_LF] >= 510) /* stage 1A */ + { + assert(sns_vq_idx[SNS_IDX_LF] < 512); + sns_vq_idx[SNS_IDX_LF] -= 510; /* send only idx 0,1 */ + sns_vq_idx[SNS_IDX_HF] = -32768; /* unused */ + shape_idx = -9; + sns_vq_idx[2] = shape_idx; /* actual signal to LR SNS vector reconstruction */ + } + else + { + /* read stop bit */ + read_uint_fl(1, ptr, mask_side_local, bp_side_local, &stop_bit); + sns_vq_idx[SNS_IDX_HF] = -32768; /* unused */ + + if (sns_vq_idx[SNS_IDX_LF] < (2 * 170) && stop_bit != 0) + { + /*B or C , keep values 0...339 in sns_vq_idx[0] , so that stage1 B vs stage1 C can be determined later in the DecLR function */ + sns_vq_idx[2] = -10; + sns_vq_idx[3] = ltpf_idx_2_lrsns[0]; /* forward LTP active flag */ + sns_vq_idx[4] = ltpf_idx_2_lrsns[1]; /* forward LTPF active flag */ + } + else + { /* stage1B + stage2 */ + /*0...169 in sns_vq_idx[0]*/ + if (sns_vq_idx[SNS_IDX_LF] < (2 * 170) && stop_bit == 0) + { + aux_idx = 0; + if (sns_vq_idx[SNS_IDX_LF] >= (170)) + { + aux_idx = 1; + sns_vq_idx[SNS_IDX_LF] -= 170; + } + sns_vq_idx[SNS_IDX_HF] = aux_idx; /* aux bit for , LR_Split_LF, 29 bits */ + + shape_idx = 0; /* point to splitLF parsing */ + sns_vq_idx[2] = shape_idx; + + read_uint_fl(2, ptr, mask_side_local, bp_side_local, &gain_idx); + sns_vq_idx[3] = gain_idx; + + /* stage2 shape demux for LR_splitLF */ + read_uint_fl(10, ptr, mask_side_local, bp_side_local, &sns_vq_idx[4]); /* 10bits mPVQ(N=5,K=6) */ + + if (sns_vq_idx[4] >= (SNSLR_NPVQ_L5K6 >> 1) + (1 << 5)) /* some limited bit error detection possible here */ + { + *bfi = plc_trigger_SNS1; + if (*bfi != 0) + { + return; + } + } + + /* determine section of splitLF mpvq(5,6)+P(8,2)+P(2,0) or mpvq(5,8)+P(10,0) */ + if (sns_vq_idx[4] < (SNSLR_NPVQ_L5K6 >> 1)) + { + read_uint_fl(1, ptr, mask_side_local, bp_side_local, &tmp_shape); /* LS (8,2) */ + read_uint_fl(6, ptr, mask_side_local, bp_side_local, &sns_vq_idx[5]); /* mPVQ(8,2) */ + sns_vq_idx[5] = (sns_vq_idx[5] << 1) + tmp_shape; /* P(8,2) LS put as lsb */ + } + else + { + sns_vq_idx[4] = sns_vq_idx[4] - (SNSLR_NPVQ_L5K6 >> 1); /* 5 LSBs of mpvq (5,8) */ + + read_uint_fl(7, ptr, mask_side_local, bp_side_local, &sns_vq_idx[5]); /* 7 msbs of mPVQ(5,8) */ + sns_vq_idx[4] = (sns_vq_idx[5] << 5) | sns_vq_idx[4]; /* merge MSB's and LSBs */ + sns_vq_idx[5] = -8; /* signal to sns_decoder split_LF subshape to decode 8 lf pulses, and no hf pulses */ + + if (sns_vq_idx[4] >= (SNSLR_NPVQ_L5K8 >> 1)) { + *bfi = plc_trigger_SNS1; + if (*bfi != 0) { + return; + } + } + } + } + else if (sns_vq_idx[SNS_IDX_LF] >= (2 * 170)) + { + aux_idx = stop_bit; + sns_vq_idx[SNS_IDX_LF] -= (2 * 170); + sns_vq_idx[1] = aux_idx; + + shape_idx = 1; /* point to full parsing */ + sns_vq_idx[2] = shape_idx; /* LR_full , 30 bits */ + + read_uint_fl(3, ptr, mask_side_local, bp_side_local, &gain_idx); + sns_vq_idx[3] = gain_idx; + + /* stage2 shape demux for LR_full */ + read_uint_fl(17, ptr, mask_side_local, bp_side_local, &sns_vq_idx[4]); /* 16.666 bits mPVQ(N=15,K=5) */ + + if (sns_vq_idx[4] >= (SNSLR_NPVQ_L15K5 >> 1)) + { /* fixenv shapes demultiplexing */ + sns_vq_idx[5] = (sns_vq_idx[4] - (SNSLR_NPVQ_L15K5 >> 1)); + if (sns_vq_idx[5] < (3 * (1 << 13))) + { /*fix_env's "0,1,2" with 2 shiftbits and 11 remaining sign bits s1..s11 */ + sns_vq_idx[4] = 0; + while (sns_vq_idx[5] >= (1 << 13)) { + sns_vq_idx[5] = sns_vq_idx[5] - (1 << 13); + sns_vq_idx[4] += 1; + } + assert(sns_vq_idx[4] >= 0 && sns_vq_idx[4] <= 3); + assert(sns_vq_idx[5] >= 0 && sns_vq_idx[5] < (1 << 13)); + } + else if (sns_vq_idx[5] < (3 * (1 << 13) + (1 << 11))) + { + sns_vq_idx[4] = 3; /*smaller fix_env "3" with 2 shiftbits and 9 remaining sign bits s1..s9 */ + sns_vq_idx[5] = sns_vq_idx[5] - (3 * (1 << 13)); + assert(sns_vq_idx[5] >= 0 && sns_vq_idx[5] < (1 << 11)); + } + else + { /* bit error */ + *bfi = plc_trigger_SNS2; + if (*bfi != 0) + { + return; + } + } + shape_idx = sns_vq_idx[4] + 2; + sns_vq_idx[2] = shape_idx; + } /* fixenv */ + }/*full*/ + }/*stage1B* + stage2 */ + } /*10+ bits*/ + } +#endif +#ifdef LRSNS_PC_SIGNAL_FIX + assert(*bfi == 0 || *bfi == 1 ); /* local SNS BFI-flag output check */ +#endif +} + void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT N, LC3_INT fs_idx, - LC3_INT bw_cutoff_bits, LC3_INT* bfi, LC3_INT* gg_idx, LC3_INT* scf_idx, LC3_INT* fac_ns_idx, + LC3_INT bw_cutoff_bits, LC3_INT* bfi, LC3_INT* gg_idx, LC3_INT* sns_vq_idx, LC3_INT* fac_ns_idx, LC3_INT* tns_numfilters, LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* bw_cutoff_idx, LC3_INT* lastnz, - LC3_INT* lsbMode, LC3_INT frame_dms) + LC3_INT* lsbMode, LC3PLUS_FrameDuration frame_dms +#ifdef CR9_C_ADD_1p25MS + , LC3_INT32 rx_status[2], LC3_INT16* mem_continuation +#ifdef NEW_SIGNALLING_SCHEME_1p25 + , LC3_INT32 *ltpfinfo_frame_cntr /* set here , but also increased outside by bfi for the channel */ +#endif +#endif + ) { - LC3_INT plc_trigger_bw, plc_trigger_last_nz, plc_trigger_SNS1, plc_trigger_SNS2, tmp, bit, - submodeMSB, i, ltpf_tmp[3], ind, submodeLSB, bp_side_local, mask_side_local; - LC3_UINT8* ptr; - LC3_INT gainMSBbits[4] = {1, 1, 2, 2}; +#ifdef CR9_C_ADD_1p25MS_LRSNS + LC3_INT32 plc_trigger_bw, plc_trigger_last_nz, plc_trigger_SNS1, plc_trigger_SNS2, bit, + i, ltpf_tmp[3], bp_side_local, mask_side_local, rx_current_status, ltpf_idx_2_lrsns[3]; + LC3_UINT8 * ptr; +#ifdef LRSNS_PC_SIGNAL_FIX + LC3_INT bfiSNS; +#endif + + //UNUSED(rx_status); + //UNUSED(mem_continuation); + UNUSED(rx_current_status); +#else + LC3_INT32 plc_trigger_bw, plc_trigger_last_nz, plc_trigger_SNS1, plc_trigger_SNS2, bit, + i, ltpf_tmp[3], bp_side_local, mask_side_local, rx_current_status; + LC3_UINT8 * ptr; + UNUSED(rx_current_status); +#endif + +#ifdef NEW_SIGNALLING_SCHEME_1p25 + UNUSED(rx_current_status); +#endif *bp_side = -1; bp_side_local = numbytes - 1; /* Matlab offset by 1 */ @@ -146,11 +569,13 @@ void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_ plc_trigger_bw = 1; /* Bandwidth */ plc_trigger_last_nz = 1; /* Last non-zero tuple */ - plc_trigger_SNS1 = 1; /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ - plc_trigger_SNS2 = 1; /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ - - + plc_trigger_SNS1 = 1; /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ +#ifdef LRSNS_10MS_BFISIGNAL_FIX + plc_trigger_SNS2 = 1; /* SNS-VQ 2nd stage MPVQ data (10-16 bits) */ +#else + plc_trigger_SNS2 = 2; /* SNS-VQ 2nd stage MPVQ data (10-16 bits) */ +#endif /* Bandwidth */ if (bw_cutoff_bits > 0) { read_uint_fl(bw_cutoff_bits, ptr, &mask_side_local, &bp_side_local, bw_cutoff_idx); @@ -167,14 +592,22 @@ void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_ } /* Number of TNS filters */ - if (*bw_cutoff_idx < 3 || frame_dms == 25) { - *tns_numfilters = 1; +#ifdef CR9_C_ADD_1p25MS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + *tns_numfilters = 0; } else { - *tns_numfilters = 2; +#endif + if (*bw_cutoff_idx < 3 || frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) { + *tns_numfilters = 1; + } else { + *tns_numfilters = 2; + } +#ifdef CR9_C_ADD_1p25MS } +#endif /* Last non-zero tuple */ - read_uint_fl(ceil(LC3_LOGTWO(N >> 1)), ptr, &mask_side_local, &bp_side_local, lastnz); + read_uint_fl(getLastNzBits (N), ptr, &mask_side_local, &bp_side_local, lastnz); *lastnz = (*lastnz + 1) * 2; if (*lastnz > N) { @@ -197,73 +630,85 @@ void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_ } /* LTPF activation flag */ +#ifdef NEW_SIGNALLING_SCHEME_1p25 + ltpf_tmp[1] = 0; /* ltpf activation idx */ + ltpf_tmp[2] = 0; /* quantized lag idx */ + if (frame_dms != LC3PLUS_FRAME_DURATION_1p25MS) + { + read_bit_fl(ptr, &mask_side_local, &bp_side_local, <pf_tmp[0]); + } + else + { /* read one of {2, 6, 7} bits into ltp/ltpf/lag variable ltpf_idx[ 0 ... 2] */ + readLtpData_fl(ptr, bfi, &mask_side_local, &bp_side_local, ltpf_tmp, rx_status, ltpfinfo_frame_cntr, mem_continuation); + } /* ! LC3PLUS_FRAME_DURATION_1p25MS */ +#else read_bit_fl(ptr, &mask_side_local, &bp_side_local, <pf_tmp[0]); +#endif - /* SNS-VQ 1st stage */ - read_uint_fl(5, ptr, &mask_side_local, &bp_side_local, &scf_idx[0]); - read_uint_fl(5, ptr, &mask_side_local, &bp_side_local, &scf_idx[1]); - - /* SNS-VQ 2nd stage side-info (3-4 bits) */ - read_bit_fl(ptr, &mask_side_local, &bp_side_local, &submodeMSB); - scf_idx[2] = submodeMSB * 2; - - read_uint_fl(gainMSBbits[scf_idx[2]], ptr, &mask_side_local, &bp_side_local, &scf_idx[3]); - read_bit_fl(ptr, &mask_side_local, &bp_side_local, &scf_idx[4]); - - /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ - if (submodeMSB == 0) { - read_uint_fl(25, ptr, &mask_side_local, &bp_side_local, &tmp); - if (tmp >= 33460056) { - *bfi = plc_trigger_SNS1; - if (*bfi) { - return; - } - } - - ind = floor(tmp / 2390004); - scf_idx[5] = tmp - ind * 2390004; + /* read SNS data */ +#ifdef CR9_C_ADD_1p25MS_LRSNS + ltpf_idx_2_lrsns[0] = ltpf_tmp[0]; /* raw LTP flag input to LRSNS */ + ltpf_idx_2_lrsns[1] = ltpf_tmp[1]; /* raw LTPF flag input to LRSNS */ +#ifdef LRSNS_PC_SIGNAL_FIX + bfiSNS = 0; /* Local BFI flag for Errors SNS bit area */ + readSNSData_fl(ptr, &bfiSNS, &mask_side_local, &bp_side_local, ltpf_idx_2_lrsns, sns_vq_idx, frame_dms, plc_trigger_SNS1, plc_trigger_SNS2); + if (bfiSNS != 0 ) + { /* corrupt SNSbits triggers PLC through global PLC flag. + *bfi==2 and bfiSNS == 0 maintains bfi==2 for PC + */ + *bfi = 1; + return; + } - if (ind < 2) { - submodeLSB = 1; - scf_idx[3] = scf_idx[3] * 2 + ind; - scf_idx[6] = -2; - } else { - submodeLSB = 0; - scf_idx[6] = ind - 2; + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { /* for 1.25ms and previously detected bit errors --> handle frame as a completely corrupt bad frame */ + if (*bfi == 2) + { + *bfi = 1; + return; } + } - } else { - read_uint_fl(24, ptr, &mask_side_local, &bp_side_local, &tmp); - - if (tmp >= 16708096) { - *bfi = plc_trigger_SNS2; - if (*bfi) { - return; - } - } +#else + readSNSData_fl(ptr, bfi, &mask_side_local, &bp_side_local, ltpf_idx_2_lrsns, sns_vq_idx, frame_dms, plc_trigger_SNS1, plc_trigger_SNS2); + if (*bfi != 0) + { + *bfi = 1; + return; + } +#endif +#else + readSNSData_fl(ptr, bfi, &mask_side_local, &bp_side_local, sns_vq_idx, frame_dms, &plc_trigger_SNS1, &plc_trigger_SNS2); +#endif - if (tmp >= 15158272) { - submodeLSB = 1; - scf_idx[3] = scf_idx[3] * 2 + ((tmp - 15158272) & 1); - scf_idx[5] = floor((tmp - 15158272) / 2); - scf_idx[6] = -2; - } else { - submodeLSB = 0; - scf_idx[5] = tmp; - scf_idx[6] = -1; + /* LTPF data */ +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + if ( frame_dms != LC3PLUS_FRAME_DURATION_1p25MS ) + { + ltpf_tmp[1] = 0; + ltpf_tmp[2] = 0; + if (ltpf_tmp[0] == 1) + { + read_bit_fl(ptr, &mask_side_local, &bp_side_local, <pf_tmp[1]); + read_uint_fl(9, ptr, &mask_side_local, &bp_side_local, <pf_tmp[2]); } } +#endif +#endif - scf_idx[2] = scf_idx[2] + submodeLSB; - - /* LTPF data */ - if (ltpf_tmp[0] == 1) { +#ifndef CR9_C_ADD_1p25MS + if (ltpf_tmp[0] == 1) + { read_bit_fl(ptr, &mask_side_local, &bp_side_local, <pf_tmp[1]); read_uint_fl(9, ptr, &mask_side_local, &bp_side_local, <pf_tmp[2]); - } else { + } + else + { ltpf_tmp[1] = 0; ltpf_tmp[2] = 0; } +#endif /* CR9_C_ADD_1p25MS */ for (i = 0; i < 3; i++) { ltpf_idx[i] = ltpf_tmp[i]; @@ -271,7 +716,7 @@ void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_ /* Noise factor */ read_uint_fl(3, ptr, &mask_side_local, &bp_side_local, fac_ns_idx); - + *bp_side = bp_side_local; *mask_side = mask_side_local; } diff --git a/lib_lc3plus/dec_lc3_fl.c b/lib_lc3plus/dec_lc3_fl.c index f86eff3430..3606077cef 100644 --- a/lib_lc3plus/dec_lc3_fl.c +++ b/lib_lc3plus/dec_lc3_fl.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -19,9 +19,13 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs i = 0, tns_order[2] = {0}, sqQdec[MAX_LEN] = {0}; LC3_INT b_left; LC3_FLOAT stab_fac = 0; +#ifdef CR9_C_ADD_1p25MS_LRSNS + LC3_INT32 pitch_rx; + LC3_INT32 ltpf_rx; +#endif h_DecSetup = decoder->channel_setup[channel]; - + memset(h_DecSetup->tns_idx, 0, sizeof(*h_DecSetup->tns_idx) * TNS_NUMFILTERS_MAX * MAXLAG); bfi = bfi_ext; @@ -33,15 +37,31 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs decoder->rframe = 1; } +#ifdef NEW_SIGNALLING_SCHEME_1p25 + h_DecSetup->ltpfinfo_frame_cntr = MIN(32767, h_DecSetup->ltpfinfo_frame_cntr + 1); /*increased always, also for bfi==1 */ /* set or reset inside processDecoderEntropy_fl */ +#endif + /* Entropy decoding */ if (bfi != 1) { processDecoderEntropy_fl(bs_in, h_DecSetup->targetBytes, &mask_side, &bp_side, decoder->yLen, decoder->fs_idx, decoder->BW_cutoff_bits, &bfi, &gg_idx, h_DecSetup->scf_idx, &fac_ns_idx, &tns_numfilters, tns_order, h_DecSetup->ltpf_param, &bw_cutoff_idx, &lastnz, &lsbMode, decoder->frame_dms + + +#ifdef CR9_C_ADD_1p25MS +#ifdef FIX_TX_RX_STRUCT_STEREO + , h_DecSetup->ltpf_rx_status, &h_DecSetup->ltpf_mem_continuation +#else + , decoder->ltpf_rx_status, &h_DecSetup->ltpf_mem_continuation +#endif +#ifdef NEW_SIGNALLING_SCHEME_1p25 + , &h_DecSetup->ltpfinfo_frame_cntr /* set here, but also increased outside during/when bfi for the channel */ +#endif +#endif ); h_DecSetup->BW_cutoff_idx_nf = bw_cutoff_idx; } - + /* Arithmetic decoding */ if (bfi != 1) { processAriDecoder_fl(bs_in, bp_side, mask_side, decoder->yLen, decoder->fs_idx, @@ -50,26 +70,33 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs decoder->n_pc, decoder->be_bp_left, decoder->be_bp_right, 0, &b_left, &h_DecSetup->spec_inv_idx, decoder->hrmode ); - + if (decoder->rframe == 1 && zero_frame == 0 && bfi != 1) { LC3_INT32 max_bw_stopband = BW_cutoff_bin_all[bw_cutoff_idx]; bfi = 2; switch (decoder->frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + max_bw_stopband = max_bw_stopband >> 3; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: max_bw_stopband = max_bw_stopband >> 2; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: max_bw_stopband = max_bw_stopband >> 1; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: max_bw_stopband = 3 * (max_bw_stopband >> 2); break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } - + h_DecSetup->spec_inv_idx = MAX(lastnz, max_bw_stopband); } @@ -78,18 +105,33 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs h_DecSetup->sqQdec_fl[i] = (LC3_FLOAT)sqQdec[i]; } } - + if (bfi != 1) { /* SNS Quantize Decoder */ +#ifdef CR9_C_ADD_1p25MS_LRSNS + if (decoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) /* 9,10,29,30 bit low rate SNS VQ */ + { + pitch_rx = ( h_DecSetup->ltpf_param[0] != 0 ); + ltpf_rx = ( h_DecSetup->ltpf_param[1] != 0 ) ; + + snsQuantScfDecLR(h_DecSetup->scf_idx, h_DecSetup->scf_q, pitch_rx, ltpf_rx); /* 9,12,29,30, bits decoding including pitch_rx, ltpf_rx info */ + } + else + { + process_snsQuantizesScf_Dec(h_DecSetup->scf_idx, h_DecSetup->scf_q); /* 38 bits decoded */ + } +#else + /* SNS Quantize Decoder */ process_snsQuantizesScf_Dec(h_DecSetup->scf_idx, h_DecSetup->scf_q); +#endif } if (h_DecSetup->PlcAdvSetup) { processPlcComputeStabFacMain_fl(h_DecSetup->scf_q, h_DecSetup->PlcAdvSetup->scf_q_old, h_DecSetup->PlcAdvSetup->scf_q_old_old, bfi, h_DecSetup->PlcSetup.prevBfi, h_DecSetup->PlcSetup.prevprevBfi, &h_DecSetup->PlcAdvSetup->stabFac); } - + if ( bfi != 1 ) { stab_fac = 1; @@ -103,28 +145,52 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs fac_ns_idx, &h_DecSetup->statePC, h_DecSetup->spec_inv_idx, decoder->yLen); } +#ifdef CR9_C_ADD_1p25MS + if ( bfi == 1 ) + { +#ifdef FIX_TX_RX_STRUCT_STEREO + h_DecSetup->ltpf_rx_status[0] = 0; + h_DecSetup->ltpf_rx_status[1] = 0; +#else + decoder->ltpf_rx_status[0] = 0; + decoder->ltpf_rx_status[1] = 0; +#endif + } +#endif + /* Decoding only if no bit error detected */ if (bfi != 1) { /* Residual decoding */ if (residualPresent) { - processResidualDecoding_fl(&bitsRead, h_DecSetup->sqQdec_fl, decoder->yLen, h_DecSetup->resBits, - nbits_residual - , decoder->hrmode + processResidualDecoding_fl(&bitsRead, h_DecSetup->sqQdec_fl, decoder->yLen, h_DecSetup->resBits, nbits_residual, decoder->hrmode +#ifdef ENABLE_12p5_DMS_MODE + , decoder->frame_dms +#endif ); } - - + /* Noise filling */ - if (zero_frame == 0) { +#ifdef CR9_C_ADD_1p25MS + if (zero_frame == 0 && decoder->cutoffBins != NULL) +#else + if (zero_frame == 0) +#endif + { processNoiseFilling_fl(h_DecSetup->sqQdec_fl, nf_seed, fac_ns_idx, decoder->cutoffBins[h_DecSetup->BW_cutoff_idx_nf], decoder->frame_dms, h_DecSetup->prev_fac_ns, h_DecSetup->spec_inv_idx); } - + /* Application of global gain */ processApplyGlobalGain_fl(h_DecSetup->sqQdec_fl, decoder->yLen, gg_idx, h_DecSetup->quantizedGainOff); /* TNS decoder */ +#ifdef CR9_C_ADD_1p25MS + if (tns_numfilters > 0) { +#endif processTnsDecoder_fl(h_DecSetup->sqQdec_fl, h_DecSetup->tns_idx, tns_order, tns_numfilters, decoder->cutoffBins[bw_cutoff_idx], h_DecSetup->N_red_tns, h_DecSetup->fs_red_tns); +#ifdef CR9_C_ADD_1p25MS + } +#endif /* SNS interpolation */ processSnsInterpolateScf_fl(h_DecSetup->scf_q, 0, decoder->bands_number, h_DecSetup->int_scf); @@ -132,7 +198,7 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs /* MDCT shaping */ processMdctShaping_fl(h_DecSetup->sqQdec_fl, h_DecSetup->int_scf, decoder->bands_offset, decoder->bands_number); } - + /* PLC */ processPlcMain_fl(h_DecSetup->sqQdec_fl, h_DecSetup->x_fl, decoder, h_DecSetup, bfi, h_DecSetup->PlcAdvSetup, &h_DecSetup->PlcSetup, decoder->plcMeth, h_DecSetup->ltpf_mem_pitch, h_DecSetup->ltpf_mem_pitch_fr, decoder->tilt, decoder->bands_offset, @@ -147,7 +213,7 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs &h_DecSetup->PlcAdvSetup->cum_fflcAtten , h_DecSetup->PlcAdvSetup->plc_fadeout_type ); - + /* IMDCT */ if (h_DecSetup->concealMethod == 4 || bfi != 1 ) { @@ -158,15 +224,20 @@ static int Dec_LC3PLUS_Channel_fl(LC3PLUS_Dec* decoder, int channel, uint8_t* bs processPlcUpdate_fl(h_DecSetup->PlcAdvSetup , decoder->frame_length, h_DecSetup->x_fl, h_DecSetup->scf_q, &h_DecSetup->PlcSetup.nbLostCmpt, &h_DecSetup->PlcNsSetup.cum_alpha, bfi, &h_DecSetup->PlcSetup.prevBfi, &h_DecSetup->PlcSetup.prevprevBfi); - + /* LTPF decoder */ process_ltpf_decoder_fl(h_DecSetup->x_fl, decoder->frame_length, h_DecSetup->x_fl, decoder->fs, h_DecSetup->ltpf_mem_x, h_DecSetup->ltpf_mem_y, &h_DecSetup->ltpf_mem_pitch, &h_DecSetup->ltpf_mem_pitch_fr, &h_DecSetup->ltpf_mem_gain, &h_DecSetup->ltpf_mem_beta_idx, bfi, h_DecSetup->ltpf_param, h_DecSetup->ltpf_param_mem, h_DecSetup->ltpf_conf_beta_idx, - h_DecSetup->ltpf_conf_beta, h_DecSetup->concealMethod, h_DecSetup->alpha + &h_DecSetup->ltpf_conf_beta, h_DecSetup->concealMethod, h_DecSetup->alpha , &h_DecSetup->ltpf_mem_active , &h_DecSetup->rel_pitch_change, decoder->hrmode, decoder->frame_dms +#ifdef CR9_C_ADD_1p25MS + , &h_DecSetup->ltpf_mem_continuation, h_DecSetup->ltpf_param_mem_prev, + &h_DecSetup->ltpf_mem_pitch_prev, &h_DecSetup->ltpf_mem_pitch_fr_prev, &h_DecSetup->ltpf_mem_beta_idx_prev, &h_DecSetup->ltpf_mem_gain_prev, + &h_DecSetup->ltpf_pitch_stability_counter, &h_DecSetup->ltpf_gain_step, h_DecSetup->ltpf_conf_beta_max +#endif ); { @@ -193,17 +264,18 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num LC3_INT32 fec_num_bytes; LC3_INT32 lc3_channel_num_bytes; LC3_INT32 channel_bfi, out_bfi; + LC3_INT32 chan_error_report; LC3PLUS_EpModeRequest channel_epmr; - + bfi = bfi_ext; lc3_num_bytes = 0; err = LC3PLUS_OK; - + if (bfi == 0) { bfi = !num_bytes; } - + if (decoder->ep_enabled) { decoder->combined_channel_coding = decoder->channels > 1 && num_bytes <= 160; @@ -231,7 +303,7 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num } else { - decoder->channel_setup[ch]->last_size = lc3_channel_num_bytes; + decoder->channel_setup[ch]->last_size = lc3_channel_num_bytes; } } @@ -246,20 +318,27 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num { decoder->epmr = LC3PLUS_EPMR_HIGH_NC; out_bfi = 0; + decoder->error_report = 0; for (ch = 0; ch < decoder->channels; ch++) { fec_num_bytes = num_bytes / decoder->channels + (ch < (num_bytes % decoder->channels)); - channel_bfi = bfi; + channel_bfi = bfi; - decoder->error_report = fec_decoder(input, fec_num_bytes, &lc3_num_bytes, &channel_epmr, + chan_error_report = fec_decoder(input, fec_num_bytes, &lc3_num_bytes, &channel_epmr, decoder->combined_channel_coding, &decoder->n_pccw, &channel_bfi, &decoder->be_bp_left, &decoder->be_bp_right, &decoder->n_pc, &decoder->m_fec); + + if (chan_error_report < 0 || decoder->error_report < 0) { + decoder->error_report = -1; + } else { + decoder->error_report += chan_error_report; + } decoder->epmr = MIN((LC3PLUS_EpModeRequest) decoder->epmr, channel_epmr); - + #ifdef ENABLE_PADDING if (channel_bfi != 1) { @@ -274,9 +353,9 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num { input = input + np_zero; } - + decoder->n_pc = MAX(decoder->n_pc - (2 * np_zero), 0); - + if (channel_bfi == 2) { if (decoder->be_bp_right < (8 * np_zero)) @@ -337,7 +416,7 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num bfi = 1; decoder->last_error = LC3PLUS_PADDING_ERROR; } - + lc3_num_bytes = lc3_num_bytes - padding_len; if (lc3_num_bytes < 20 || lc3_num_bytes > LC3PLUS_MAX_BYTES) { @@ -345,8 +424,8 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, uint8_t* input, LC3_INT32 num decoder->last_error = FRAMESIZE_ERROR; } } -#endif - +#endif + if (bfi != 1 && lc3_num_bytes != decoder->channel_setup[ch]->last_size) { err = update_dec_bitrate(decoder, ch, lc3_num_bytes); diff --git a/lib_lc3plus/defines.h b/lib_lc3plus/defines.h index 48c89f9ca0..ab34eb73e7 100644 --- a/lib_lc3plus/defines.h +++ b/lib_lc3plus/defines.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -42,8 +42,52 @@ typedef uint32_t LC3_UINT32; #ifndef NO_POST_REL_CHANGES /* Post-release non-bitexact changes */ +#define CR13_B_FIX_PC_BINS +#define CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES +#define CR12_D_FIX_BITRATE_LIMITS + +#define CR9_C_ADD_1p25MS +#ifdef CR9_C_ADD_1p25MS +#define ENABLE_12p5_DMS_MODE +#define CR9_C_ADD_1p25MS_LRSNS +#define FIX_LTPF_PITCH_1p25 + +#endif + #endif /* NO_POST_REL_CHANGES */ +#ifdef CR9_C_ADD_1p25MS_LRSNS +#define LRSNS_PC_SIGNAL_FIX /*correct handling of incoming bfi==2 to DEC_ENTROPY for conformance */ +#define LRSNS_10MS_BFISIGNAL_FIX /* correct signaling of detected BER in SNS/LRSNS */ +#define LRSNS_WMC_FIX +/*#define LRSNS_CBC_NO_LTPF_DEPENDENCY */ /* turn off LRSNS CB_C dependency on LTPF activation flag */ + +#define SNSLR_N_FIXENV 4 /* 4 fix envelopes multiplexed inside the full codeword */ +#define SNSLR_N_FIXENV_SHIFTS 4 /* 2 bits */ +#define SNSLR_MAX_PVQ_CAND 6 /* splitLF(0), full(1), fixed_env 2+{0,1,2,3 }, */ +#define SNSLR_MAX_PVQ_SEARCH_CAND (SNSLR_MAX_PVQ_CAND-SNSLR_N_FIXENV+1) /* 3 = splitLF(0), full(1), fixed_envs(2), */ + + + +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +#define LC3_CONST_FLOATMAX FLT_MAX +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +#undef SNS_VQ_MAX_IDX +#define SNS_VQ_MAX_IDX 8 + +#define SNSLR_NST1 170 /* 3*170 + 2 = 512 */ +#define SNSLR_NPVQ_L5K6 1970 /* mux into 11 bits 2048 pos , 78 slots remaining, 6 whole bits */ +#define SNSLR_NPVQ_L5K8 5890 /* additional split LF part muxed into 13 bits, log2(5980)= 12.5241 */ +#define SNSLR_NPVQ_L5K5 1002 /* mux into 10 bits 1024 pos , 22 slots remainging 4 whole bits */ +#define SNSLR_NPVQ_L8K2 128 /* 1+ 6 = 7 bits */ +#define SNSLR_NPVQ_L15K5 207006 /* 1+ 16.6593 bits */ + +#endif + #define MAX_UINT8 255 #define THRESH_100_DMS_TDC_CNT 9 #define THRESH_100_DMS_NS_CNT 7 @@ -61,6 +105,68 @@ typedef uint32_t LC3_UINT32; #define PLC_LONGTERM_ANALYSIS_MS 200 /* Analysis window 2000 ms / 10 ms */ #define PLC_LONGTERM_ANALYSIS_STARTUP_FILL 0.5f /* required buffer fill amount, set to 0.0 to not require any fill at all */ + + +#define SNS_IDX_LF 0 +#define SNS_IDX_HF 1 +#define SNS_IDX_SHAPEJ 2 +#define SNS_IDX_GAIN 3 +#define SNS_IDX_LS_INDA 4 +#define SNS_IDX_A 5 +#define SNS_IDX_BORGAINLSB 6 + +#ifdef CR9_C_ADD_1p25MS + +/* master integration fixes for 1p25 */ +#define FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS /* add two last MDCT coeffs into the last quadruple for global_gain _energy_ analysis */ +#define FIX_FLOAT_LT_NORMCORR_INIT /*align state to BASOP start value of ~.5 as it has an effect on SNS_compute */ + +#define FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE /* 1p25 curve tilt calulation corrected and made into BASOP */ +#define FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE -135 /* 1p25 curve tilt calulation limited to value -135 kbps for NB&WB */ +#define FIX_BOTH_1p25_TEST_NEW_GG_EST2 /* GG_EST2_will use bands with two coeffs in each instead of four, for 1p25 WB and 1p25 SSWB , note only active for regular, hrmode==0 */ + +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 +#define FIX_BOTH_1p25_GG_EST_SWB_FB /* 1.25ms GG_EST update for SWB/FB, better RD curve float and BASOP , active for hrmode==0 */ +#endif + +/* defines to activate 2 or 3 tuple 1.25ms loops for all or any of WB,SSWB,SWB,FB */ +#define FIX_1p25_GG_EST_TUPLES /* 1.25 ms will use 2 or 3 tuples, note only active for regular, hrmode==0 */ +#ifdef FIX_1p25_GG_EST_TUPLES +#define GG_1p25_WB_TUPLES 2 +#define GG_1p25_SSWB_TUPLES 2 +#define GG_1p25_SWB_TUPLES 2 +#define GG_1p25_FB_TUPLES 3 +#define GG_1p25_MAX_TUPLES MAX(MAX(GG_1p25_WB_TUPLES ,GG_1p25_SSWB_TUPLES ), MAX(GG_1p25_SWB_TUPLES ,GG_1p25_FB_TUPLES)) /* used to control common energy loop */ + +#ifndef FIX_BOTH_1p25_ALLOC_SPECTRUM +#define FIX_BOTH_1p25_ALLOC_SPECTRUM +#endif + +#endif + + +#define LTPF_ADAPTIVE_GAIN_RATE 20 /* Number of frames it must take to reach maximum beta from the default value, provided the pitch remains constant */ +#define LTPF_ADAPTIVE_GAIN + +#ifdef LTPF_ADAPTIVE_GAIN +#define LTPF_PITCH_STABILITY_THRESHOLD 5 /* Number of frames for which the pitch must be constant for adaptive gain and pitch correction to be applied */ +#define LTPF_ADAPTIVE_GAIN_RATE 20 /* Number of frames it must take to reach maximum beta from the default value, provided the pitch remains constant */ + +#define LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR +#endif + +#ifdef CR9_C_ADD_1p25MS +#define FIX_LTPF_MEM_CONTINUATION +#define FIX_LTPF_PITCH_MEM_LEN +#define FIX_PLC_CONFORM_ISSUES +#define FIX_TDC_BURST_ERROR +#define FIX_LTPF_DEC_FLFX_MISMATCH +#define FIX_TX_RX_STRUCT_STEREO +#define FIX_ADDITONAL_1p25_ISSUES +#define NEW_SIGNALLING_SCHEME_1p25 +#define FIX_LTPF_1p25 +#endif +#endif /* Precision Defines */ #define LC3_FABS(x) (fabsf(x)) @@ -71,16 +177,23 @@ typedef uint32_t LC3_UINT32; #define LC3_SIN(x) (sin(x)) #define LC3_SQRT(x) (sqrtf(x)) #define LC3_EXP(x) (expf(x)) +#define LC3_FMIN(x, y) (fminf(x, y)) +#define LC3_FMAX(x, y) (fmaxf(x, y)) -#define MAX_BR 320000 /* 400 * 800 */ -#define MIN_BR_100DMS 16000 /* 20 * 800 * 100/100 */ -#define MIN_BR_025DMS 64000 /* 20 * 800 * 100/ 25 */ -#define MIN_BR_050DMS 32000 /* 20 * 800 * 100/ 50 */ +#define MAX_BR 320000 /* 400 * 800 */ +#define MIN_BR_100DMS 16000 /* 20 * 800 * 100/100 */ +#define MIN_BR_025DMS 64000 /* 20 * 800 * 100/ 25 */ +#define MIN_BR_050DMS 32000 /* 20 * 800 * 100/ 50 */ #define MAX_BR_050DMS_NB 260800 /* 163 * 800 * 100/ 50 */ #define MAX_BR_100DMS_NB 114400 /* for 100ms at 8kHz */ #define MAX_BR_100DMS_WB 221600 /* for 100ms at 16kHz */ #define MAX_BR_100DMS_SSWB 314400 /* for 100ms at 24kHz */ +#ifdef CR9_C_ADD_1p25MS +#define MIN_BR_0125DMS 128000 /* 20 * 800 * 100/ 12.5 */ +#define MAX_BR_0125DMS 512000 +#endif + #define MIN_BR_075DMS_48KHZ_HR ((int)124800/ 800/2)* 800 #define MIN_BR_075DMS_96KHZ_HR ((int)149600/ 800/2)* 800 #define MIN_BR_075DMS 21334 /* ceil( 20 * 800 * 100/ 75) */ @@ -148,8 +261,24 @@ typedef int32_t LC3_INT32; #define PLC34_ATTEN_FAC_050 0.7071 /* attenuation factor for NS and TDC @ 5.0 ms*/ #define PLC34_ATTEN_FAC_025 0.8409 /* attenuation factor for NS and TDC @ 2.5 ms*/ +#ifdef CR9_C_ADD_1p25MS +#define PLC34_ATTEN_FAC_0125 0.9995 /* attenuation factor for NS and TDC @ 1.25 ms*/ +#endif + #define FEC_SLOT_BYTES_MIN 40 #define FEC_SLOT_BYTES_MAX 400 +#ifdef CR12_D_FIX_BITRATE_LIMITS +#ifdef ENABLE_HR_MODE_FL +#define FEC_SLOT_BYTES_MIN_025DMS_48KHZ_HR 54 +#define FEC_SLOT_BYTES_MIN_025DMS_96KHZ_HR 61 +#define FEC_SLOT_BYTES_MIN_050DMS_48KHZ_HR 87 +#define FEC_SLOT_BYTES_MIN_050DMS_96KHZ_HR 101 +#define FEC_SLOT_BYTES_MIN_075DMS_48KHZ_HR 110 +#define FEC_SLOT_BYTES_MIN_075DMS_96KHZ_HR 126 +#define FEC_SLOT_BYTES_MIN_100DMS_48KHZ_HR 140 +#define FEC_SLOT_BYTES_MIN_100DMS_96KHZ_HR 164 +#endif +#endif #define LC3_CONST_POW_2_M15 3.051757812500000e-05 #define LC3_CONST_POW_2_23 8388608 @@ -172,7 +301,6 @@ typedef int32_t LC3_INT32; #endif #endif - #define LC3_EPS (1.1e-7f) #define M_PI_LC3PLUS 3.14159265358979323846 @@ -221,6 +349,7 @@ typedef int32_t LC3_INT32; #define MIN_BR_100MS_48KHZ_HR ((int)124800/800/2)*800 #define MIN_BR_100MS_96KHZ_HR ((int)149600/800/2)*800 #endif /* ENABLE_HR_MODE */ + #define MAX_NBYTES2 625 #define BYTESBUFSIZE (MAX_NBYTES2 * MAX_CHANNELS) #define MAX_BW_BIN 400 @@ -245,6 +374,21 @@ typedef int32_t LC3_INT32; 7 /* (L+H) + submode_MSB +gain+(Ia_leads+Ia_mpvq)+(Ib_joint_mpvq), \ submode-LSB */ +#ifdef CR9_C_ADD_1p25MS +#undef SNS_VQ_MAX_IDX +#define SNS_VQ_MAX_IDX 8 + +#define SNSLR_NST1 170 /*, 3*170 + 2 = 512 */ +#define SNSLR_NPVQ_L5K6 1970 /* mux into 11 bits 2048 pos , 78 slots remaining, 6 whole bits */ +#define SNSLR_NPVQ_L5K5 1002 /* mux into 10 bits 1024 pos , 22 slots remainging 4 whole bits */ +#define SNSLR_NPVQ_L8K2 128 /* 1+6 = 7 bits */ +#define SNSLR_NPVQ_L15K5 207006 /* 1+ 16.6593 bits , */ + +#define SNSLR_ST1_SCALEC (round(1.5*16384.0)/16384.0 ) /* SNS_ST1_SCALEC 1.5 in Q1.14 */ +#define SNSLR_ST1_INVSCALEC (round((2.0/3.0)*(32768.0))/32768.0) /* SNSLR_ST1_INVSCALEC 0.66667 in Q0.15 */ + +#endif /* CR9_C_ADD_1p25MS */ + /* RESIDUAL CODING */ #define NPRM_RESQ 5 * MAX_LEN @@ -266,7 +410,11 @@ typedef int32_t LC3_INT32; #define RES2_PITCH_12K8 157 #define RES4_PITCH_12K8 127 #define LTPF_MEMIN_LEN (MAX_PITCH_12K8 + 4) - +#ifdef CR9_C_ADD_1p25MS +#define LEN_MEM_NORMCORR 5 +#else +#define LEN_MEM_NORMCORR 2 +#endif /* Advanced PLC */ diff --git a/lib_lc3plus/detect_cutoff_warped.c b/lib_lc3plus/detect_cutoff_warped.c index 4005c68e74..f5ccc45ed6 100644 --- a/lib_lc3plus/detect_cutoff_warped.c +++ b/lib_lc3plus/detect_cutoff_warped.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,7 +11,7 @@ #include "wmc_auto.h" #include "functions.h" -void processDetectCutoffWarped_fl(LC3_FLOAT* d2, LC3_INT fs_idx, LC3_INT frame_dms, LC3_INT* bw_idx) +void processDetectCutoffWarped_fl(LC3_FLOAT* d2, LC3_INT fs_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT* bw_idx) { const LC3_INT *warp_idx_start, *warp_idx_stop; LC3_INT counter, brickwall = 0, i, stop, dist; @@ -23,26 +23,33 @@ void processDetectCutoffWarped_fl(LC3_FLOAT* d2, LC3_INT fs_idx, LC3_INT frame_d switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + assert (0); + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: warp_idx_start = BW_warp_idx_start_all_2_5ms[fs_idx - 1]; warp_idx_stop = BW_warp_idx_stop_all_2_5ms[fs_idx - 1]; bw_dist = brickwall_dist; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: warp_idx_start = BW_warp_idx_start_all_5ms[fs_idx - 1]; warp_idx_stop = BW_warp_idx_stop_all_5ms[fs_idx - 1]; bw_dist = brickwall_dist; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: warp_idx_start = BW_warp_idx_start_all_7_5ms[fs_idx - 1]; warp_idx_stop = BW_warp_idx_stop_all_7_5ms[fs_idx - 1]; bw_dist = brickwall_dist_7_5ms; break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: warp_idx_start = BW_warp_idx_start_all[fs_idx - 1]; warp_idx_stop = BW_warp_idx_stop_all[fs_idx - 1]; bw_dist = brickwall_dist; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } counter = fs_idx; diff --git a/lib_lc3plus/enc_entropy.c b/lib_lc3plus/enc_entropy.c index 28e3cea69c..51f9ddb1bf 100644 --- a/lib_lc3plus/enc_entropy.c +++ b/lib_lc3plus/enc_entropy.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,21 +11,352 @@ #include "wmc_auto.h" #include "functions.h" -static const LC3_INT gainMSBbits[4] = {1, 1, 2, 2}; -static const LC3_INT gainLSBbits[4] = {0, 1, 0, 1}; +#ifdef CR9_C_ADD_1p25MS + +#ifdef NEW_SIGNALLING_SCHEME_1p25 +void writeLtpData_fl( LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT32* ltpf_idx, LC3_INT16* Tx_ltpf ); +#endif + +void writeSNSData_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3PLUS_FrameDuration frame_dms, LC3_INT32* scf_idx); +#else /* CR9_C_ADD_1p25MS */ +void writeSNSData_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT32* scf_idx); +#endif /* CR9_C_ADD_1p25MS */ + + +static const LC3_INT32 gainMSBbits[4] = {1, 1, 2, 2}; +static const LC3_INT32 gainLSBbits[4] = {0, 1, 0, 1}; + + +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 +void writeLtpData_fl( + LC3_UINT8* ptr, + LC3_INT* bp_side, + LC3_INT* mask_side, + LC3_INT32* ltpf_idx, + LC3_INT16* Tx_ltpf +) +{ + LC3_INT32 tmp; + LC3_INT32 bitsTx; + /* Hdr, information , bits used + 00 , no lag info , no phase info sum=2 + 010, PhaseA,LTPF=0, lagAbits=4 , sum=7 : pitch-PLC may be activated with old lag , 4 MSbs + 011, PhaseB,LTPF=0, lagBbits=4*, sum=7* : pitch-PLC may be activated with fresh lag, 4* = reduced lag resolution in Quantized ltpf_idx domain for PLC + 10 , PhaseA,LTPF=1, lagAbits=4 , sum=6 : LTPF may be activated with old lag + 11 , PhaseB,LTPF=1, lagBbits=5 , sum=7 : LTPF activated with fresh lag +*/ + + + tmp = MIN(*Tx_ltpf, 1); /*phaseA==0, phaseB==1*/ + + if (ltpf_idx[0] == 0) + { + write_bit_backward_fl(ptr, bp_side, mask_side, 0); /* "00" */ + write_bit_backward_fl(ptr, bp_side, mask_side, 0); + + *Tx_ltpf = 0; + /* *Tx_ltpf A/B state forced to zero or kept at zero */ + /* decoder will discard any sofar in phase received phaseA MSB bits */ + } + else if (ltpf_idx[1] == 0) + { + /* no current LTPF activation, + lag transmitted for PLC, or for next frame LTPF activation */ + assert(ltpf_idx[0] != 0); + + /* A "010" 3 bits Hdr transmitted */ + /* B "011" 3 bits Hdr transmitted*/ + + write_uint_backward_fl(ptr, bp_side, mask_side, 1, 2); /* "01"*/ + write_bit_backward_fl(ptr, bp_side, mask_side, tmp); /* phase A or phaseB */ + + if (*Tx_ltpf == 0) + { /* phase A transmission */ + assert(tmp == 0); + assert((ltpf_idx[2] & ~(0x01ff)) == 0); /* only 9 bits info allowed within ltpf_idx[2] */ + tmp = (ltpf_idx[2] >> 5 ); /* shift_out LSBS, send 4 MSBs */ + *Tx_ltpf = (0x200 | ltpf_idx[2]); /* remember full lag, as phaseB sentinel in bit 10, */ + } + else + { /* phase B */ + assert(tmp == 1); + assert(*Tx_ltpf > 511); /*sentinel in b10 should have been set in previous phaseA frame */ + tmp = ((*Tx_ltpf & 0x001f) >> 1); /* B send 4* LSBs, 1 bit truncated */ + *Tx_ltpf = 0; /* clear sentinel in b10 and the old remebered lag value */ + } + write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 4); /* 4 bits lag info Tx, when LTPF is deactivated */ + } + else + { /* LTPF activated */ + /* A "10" 2 bits Hdr */ + /* B "11" 2 bits Hdr */ + assert(ltpf_idx[0] != 0 && ltpf_idx[1] != 0); + + tmp |= 0x02; + write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 2); + + if (*Tx_ltpf == 0) + { + bitsTx = 4; + assert((ltpf_idx[2] & ~(0x01ff)) == 0); /* only 9 bits info allowed within ltpf_idx[2] */ + tmp = (ltpf_idx[2] >> 5); /* shift away 5 LBS, LTPF active, send phaseA 4 MSBs */ + + *Tx_ltpf = 0x0200 | ltpf_idx[2]; /* remember full lag in state *Tx_ltpf, add phaseB Tx state sentinel in bit 10 */ + } + else + { + bitsTx = 5; + tmp = (*Tx_ltpf & 0x001f); /* LTPF active B send 5 LSBs , full regular resolution */ + *Tx_ltpf = 0; /* clear sentinel in b10 and also the old remebered lag value */ + } + + write_uint_backward_fl(ptr, bp_side, mask_side, tmp, bitsTx); /* ltp==1 , ltpf==1, 4(A,MSBs) or 5(b, LSBs) bits */ + } +} +#endif /* NEW_SIGNALLING_SCHEME_1p25 */ +void writeSNSData_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3PLUS_FrameDuration frame_dms, LC3_INT32* scf_idx) +#else +void writeSNSData_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT32* scf_idx) +#endif +{ +#ifdef CR9_C_ADD_1p25MS_LRSNS + + LC3_INT32 submodeMSB, submodeLSB, tmp, gainMSB, gainLSB; + LC3_INT16 write_legacy_sns_vq_bits; + LC3_INT32 aux_idx; + LC3_INT32 shape_idx; + LC3_INT32 env_shape_idx; + LC3_INT32 gain_idx; + LC3_INT16 n5k; + +#else + LC3_INT32 submodeMSB, submodeLSB, tmp, gainMSB, gainLSB; +#endif + +#if defined(CR9_C_ADD_1p25MS) && !defined( CR9_C_ADD_1p25MS_LRSNS) +UNUSED(frame_dms); +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS + write_legacy_sns_vq_bits = 1; + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) /* 9,10,29/30 */ + { + write_legacy_sns_vq_bits = 0; + } + if (write_legacy_sns_vq_bits != 0) + { +#endif + /* SNS-VQ 1st stage */ + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0], 5); + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[1], 5); + + /* SNS-VQ 2nd stage side-info (3-4 bits) */ + submodeMSB = scf_idx[2] / 2; + submodeLSB = scf_idx[2] & 1; + write_bit_backward_fl(ptr, bp_side, mask_side, submodeMSB); + gainMSB = scf_idx[3] >> (gainLSBbits[scf_idx[2]]); + gainLSB = scf_idx[3] & 1; + write_uint_backward_fl(ptr, bp_side, mask_side, gainMSB, gainMSBbits[scf_idx[2]]); + write_bit_backward_fl(ptr, bp_side, mask_side, scf_idx[4]); + + /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ + if (submodeMSB == 0) + { + if (submodeLSB == 0) + { + tmp = scf_idx[6] + 2; + } + else + { + tmp = gainLSB; + } + + tmp = tmp * 2390004 + scf_idx[5]; + write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 25); + } + else + { + tmp = scf_idx[5]; + + if (submodeLSB != 0) + { + tmp = 2 * tmp + gainLSB + 15158272; + } + write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 24); + } +#ifdef CR9_C_ADD_1p25MS_LRSNS + } + if (write_legacy_sns_vq_bits == 0) + { + /* SNS-VQ 1st stage is jointly multiplexed into 9 bits or 10 bits */ + /* input scf_idx[0], has the stage1 index for one of BCA or B* */ + aux_idx = scf_idx[1]; /* aux value: we have the LS, or the s0 sign bit */ + shape_idx = scf_idx[2]; /* st2 shape 0 .. 5 , where [2.3.4.5] are fixed FESS shapes */ + gain_idx = scf_idx[3]; /* idx of 2-3 bits valued gains */ + + if (shape_idx == 0 && scf_idx[0] >= 0) + { /*aux==2 --> split mode st1B (aux==2), + a 2 bit gain + P(5,6)10.96b + P(8,2)7b */ + assert(shape_idx == 0 && gain_idx < 4); + } + if ((shape_idx == 1) && scf_idx[0] >= 0) + { /* regular mode st1B , + a 3 bit gain + P(15,5) */ + assert(shape_idx == 1 && gain_idx >= 0 && gain_idx < 8); + } + if ((shape_idx >= 2) && scf_idx[0] >= 0) + { /* regular mode st1B , + a 3 bit gain + fixenv */ + assert(shape_idx >= 2 && shape_idx <= 5 && gain_idx >= 0 && gain_idx < 8); + } + + /* b0-b8 b9 + segm , idx9b , stop bit, comment use + -----+--------+--------- + A | 510,511| n/a, 2 entries, 9 bit total + ------+--------+-------- + B | 0--169 | 1 , 170 entries, 10 bit total + ------+--------+-------- + C | 170-339| 1 , 170 entries, 10 bit total + ------+--------+--------+------------ + */ + + if (scf_idx[0] >= 510) + { /* stage1A */ + assert(scf_idx[0] < (1 << 9)); + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0], 9); /* currently writes 510 or 511 */ + shape_idx = -9; /* only crude stage 1 A , no more bits to send */ + } + else if ( (scf_idx[0] < 2 * 170) && (shape_idx < 0) ) + { + if (scf_idx[0] < 170) + { + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0] + 0 * 170, 9); /* 1B */ + } + else if (scf_idx[0] < 2 * 170) + { + write_uint_backward_fl(ptr, bp_side, mask_side, (scf_idx[0] - 170) + 1 * 170, 9); /* 1C */ + } + /* write the stop bit value sentinel value "1", so that the demux dec_entropy can stop already at (9+1)=10 bits */ + + write_uint_backward_fl(ptr, bp_side, mask_side, 1, 1); /* dec_entropy will read a 1 "stop" bit */ + + /* pitch_rx and ltpf_rx handled separately */ + } + else if (shape_idx == 0) + { + /* aux info as a part of (9+1) 10 initial bits */ + /* b0-b8 b9 + segm , idx9b , stop bit, comment use + -----+--------+--------- + B* | 0--169 | 0 , --> aux=0, 170, 2b+17b for stage2 'LR_SplitLF', 29 bit total + ------+--------+--------+------- + B* | 170-339| 0 , --> aux=1, 170, 2b+17b for stage2 'LR_SplitLF', 29 bit total + ------+--------+--------+------- + */ + /* 29 bit total LR_splitLF */ + assert(scf_idx[0] >= 0 && scf_idx[0] < 170); /* st1B range */ + assert(aux_idx >= 0 && aux_idx <= 1); /* aux_bit can only be 1 or 0 */ + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0] + aux_idx * 170, 9); /* aux_bit defined by region 0..339 region in decoder */ + write_uint_backward_fl(ptr, bp_side, mask_side, 0, 1); /* "stop" bit. always zero for 'LR_splitLF' */ + + write_uint_backward_fl(ptr, bp_side, mask_side, gain_idx, 2); /* always 2bits == 4 gain levels for the splitLF mode */ + + n5k = 6; + if (scf_idx[5] < 0) { + assert(scf_idx[5] == -8 ); + n5k = 8; + } + + if (n5k == 6) + { /* multiplex remaining 10 + 1+6 bit */ + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[4], 10); /* 29b P(5,6)=10.94 in LS+10 bits, */ + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[5] & 0x1, 1); /* LS for P(8,2)=7 */ + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[5] >> 1, 6); /* mPVQ(8,2) in 6 */ + } + else + { /* LF is PVQ(N=5,K=8), HF is all zero , multiplexed as top section in stage2 10b , + 7b */ + /* scf_idx[4] is in the range [0 ... (SNSLR_NPVQ_L5K8 >>1)[, [0 .. 2945[ , 11.52 bits */ + /* 985 - 1024 = 39 entries where we now use only 32 (5 bits) */ + /* index_to_send = (SNSLR_NPVQ_L5K6 >> 1) + (scf_idx[4] & 0x001f) , range [985 .. 1017[ , i.e. 7 values are not sent:[1018 ... 1023] */ + write_uint_backward_fl(ptr, bp_side, mask_side, (SNSLR_NPVQ_L5K6 >> 1) + (scf_idx[4] & 0x001f), 10); /* 5 lsb's as top in the 10b */ + assert((scf_idx[4] >> 5) < (1 << 7)); + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[4] >> 5, 7); /* 7 msb's of mpvq(5,8)*/ + } + } + else if (shape_idx == 1) + { + /* b0-b8 b9 + segm , idx9b , stop bit, comment use + ------+--------+--------+------------ + B* | 340-509| 1 --> aux=1, 170, 3b+17b for stage2 'LR_full', 30 bit total + ------+--------+--------+------- + B* | 340-509| 0 --> aux=0, 170, 3b+17b for stage2 'LR_full', 30 bit total + ------+--------+--------+------- + */ + assert(scf_idx[0] >= 0 && scf_idx[0] < 170); /* st1B* range */ + + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0] + 2 * 170, 9); /* stage1B* signal */ + write_uint_backward_fl(ptr, bp_side, mask_side, aux_idx, 1); /* auxbit transmitted in the stop bit location */ + + write_uint_backward_fl(ptr, bp_side, mask_side, gain_idx, 3); /*30b always 8 gain levels in 3 bits for the full mode */ + /* the next 17 bit index is used to decode submode 1==full or one of submode 2,3,4,5 == fix */ + assert(scf_idx[4] >= 0 && scf_idx[4] < ((SNSLR_NPVQ_L15K5 >> 1))); + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[4], 17); /* P(15,5) without the leading sign, 16.6593 bits, written */ + } + else + { /* Fixed shapes 2(ones, env0) and 3(env1) , 4(env2), and later 5(env3) */ + assert(shape_idx >= 2 && shape_idx <= 5); + /* env 0,1,2, : s0(in aux) + shift(2bits)+ 11 signs , section size 8192 + env 3 : s0(in aux) + shift(2bits)+ 9 signs , section size 2048 + */ + env_shape_idx = scf_idx[4]; + assert(env_shape_idx == (shape_idx - 2)); /*scf_idx[4] has the fixed env index*/ + + assert(scf_idx[0] >= 0 && scf_idx[0] < 170); /* st1B* range */ + + write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0] + 2 * 170, 9); /* stage1B* signal */ + write_uint_backward_fl(ptr, bp_side, mask_side, aux_idx, 1); /* auxbit transmitted in the stop bit location */ + + write_uint_backward_fl(ptr, bp_side, mask_side, gain_idx, 3); /* for 30bit its is always 3 gain bits */ + + if (shape_idx < 5) /*2,3,4*/ + { + assert(scf_idx[5] >= 0 && scf_idx[5] < (1 << 13)); + assert((SNSLR_NPVQ_L15K5 >> 1) + env_shape_idx * (1 << 13) + scf_idx[5] < (1 << 17)); + /* offset is PVQ(15,5) without leading sign */ + /* int32_t tmp_idx = (SNSLR_NPVQ_L15K5 >> 1) + env_shape_idx * (1 << 13) + scf_idx[5]; */ + write_uint_backward_fl(ptr, bp_side, mask_side, (SNSLR_NPVQ_L15K5 >> 1) + env_shape_idx * (1 << 13) + scf_idx[5], 17); + } + else + { + assert(shape_idx == 5); + assert(env_shape_idx == 3); + assert(scf_idx[5] < (1 << 11)); + assert(((SNSLR_NPVQ_L15K5 >> 1) + 3 * (1 << 13) + scf_idx[5]) < (1 << 17)); + /* offset is (15,5) without leading sign */ + write_uint_backward_fl(ptr, bp_side, mask_side, (SNSLR_NPVQ_L15K5 >> 1) + 3 * (1 << 13) + scf_idx[5], 17); + } + } + } +#endif +} void processEncoderEntropy_fl(LC3_UINT8* bytes, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT numbytes, LC3_INT bw_cutoff_bits, LC3_INT bw_cutoff_idx, LC3_INT lastnz, LC3_INT N, LC3_INT lsbMode, LC3_INT gg_idx, LC3_INT num_tns_filters, - LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* scf_idx, LC3_INT fac_ns_idx - , LC3_INT bfi_ext, LC3_INT fs_idx + LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* scf_idx, LC3_INT fac_ns_idx, + LC3_INT bfi_ext, LC3_INT fs_idx +#ifdef CR9_C_ADD_1p25MS + , LC3PLUS_FrameDuration frame_dms, LC3_INT16* Tx_ltpf +#endif ) { LC3_UINT8* ptr; - LC3_INT i, submodeMSB, submodeLSB, tmp, gainMSB, gainLSB; + LC3_INT i; - LC3_INT16 lastnzTrigger[5] = {63, 127, 127, 255, 255}; +#if defined(CR9_C_ADD_1p25MS) && !defined(CR9_C_ADD_1p25MS_LRSNS) + UNUSED(Tx_ltpf); +#endif *bp_side = numbytes - 1; *mask_side = 1; ptr = bytes; @@ -37,11 +368,11 @@ void processEncoderEntropy_fl(LC3_UINT8* bytes, LC3_INT* bp_side, LC3_INT* mask_ /* Last non zero touple */ if (bfi_ext == 1) { - write_uint_backward_fl(ptr, bp_side, mask_side, lastnzTrigger[fs_idx], ceil(LC3_LOGTWO(N >> 1))); + write_uint_backward_fl(ptr, bp_side, mask_side, lastnzTrigger[fs_idx], getLastNzBits (N)); } else { - write_uint_backward_fl(ptr, bp_side, mask_side, lastnz / 2 - 1, ceil(LC3_LOGTWO(N >> 1))); + write_uint_backward_fl(ptr, bp_side, mask_side, lastnz / 2 - 1, getLastNzBits (N)); } /* LSB mode bit */ @@ -56,46 +387,43 @@ void processEncoderEntropy_fl(LC3_UINT8* bytes, LC3_INT* bp_side, LC3_INT* mask_ } /* LTPF activation flag */ +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + writeLtpData_fl(ptr, bp_side, mask_side, ltpf_idx, Tx_ltpf); /* LTP and LTPF-active and interleaved lag-idx */ + } + else + { + write_bit_backward_fl(ptr, bp_side, mask_side, ltpf_idx[0]); /* ltp tx bit */ + } +#endif +#else write_bit_backward_fl(ptr, bp_side, mask_side, ltpf_idx[0]); +#endif /* CR9_C_ADD_1p25MS */ - /* SNS-VQ 1st stage */ - write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[0], 5); - write_uint_backward_fl(ptr, bp_side, mask_side, scf_idx[1], 5); - - /* SNS-VQ 2nd stage side-info (3-4 bits) */ - submodeMSB = scf_idx[2] / 2; - submodeLSB = scf_idx[2] & 1; - write_bit_backward_fl(ptr, bp_side, mask_side, submodeMSB); - gainMSB = scf_idx[3] >> (gainLSBbits[scf_idx[2]]); - gainLSB = scf_idx[3] & 1; - write_uint_backward_fl(ptr, bp_side, mask_side, gainMSB, gainMSBbits[scf_idx[2]]); - write_bit_backward_fl(ptr, bp_side, mask_side, scf_idx[4]); - - /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ - if (submodeMSB == 0) { - if (submodeLSB == 0) { - tmp = scf_idx[6] + 2; - } else { - tmp = gainLSB; - } - - tmp = tmp * 2390004 + scf_idx[5]; - write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 25); - } else { - tmp = scf_idx[5]; - - if (submodeLSB != 0) { - tmp = 2 * tmp + gainLSB + 15158272; - } - - write_uint_backward_fl(ptr, bp_side, mask_side, tmp, 24); - } + /* SNS data*/ +#ifdef CR9_C_ADD_1p25MS + writeSNSData_fl(ptr, bp_side, mask_side, frame_dms, scf_idx); +#else + writeSNSData_fl(ptr, bp_side, mask_side, scf_idx); +#endif /* LTPF data */ +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + if ((frame_dms != LC3PLUS_FRAME_DURATION_1p25MS ) && (ltpf_idx[0] == 1) ) + { + write_uint_backward_fl( ptr, bp_side, mask_side, ltpf_idx[1], 1 ); + write_uint_backward_fl( ptr, bp_side, mask_side, ltpf_idx[2], 9 ); + } +#endif +#else /* CR9_C_ADD_1p25MS */ if (ltpf_idx[0] == 1) { write_uint_backward_fl(ptr, bp_side, mask_side, ltpf_idx[1], 1); write_uint_backward_fl(ptr, bp_side, mask_side, ltpf_idx[2], 9); } +#endif /* CR9_C_ADD_1p25MS */ /* Noise factor */ write_uint_backward_fl(ptr, bp_side, mask_side, fac_ns_idx, 3); diff --git a/lib_lc3plus/enc_lc3_fl.c b/lib_lc3plus/enc_lc3_fl.c index 0729df98dc..a1a4af8418 100644 --- a/lib_lc3plus/enc_lc3_fl.c +++ b/lib_lc3plus/enc_lc3_fl.c @@ -1,16 +1,20 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * * Rights Policy, 3rd April 2019. No patent licence is granted by implication, * * estoppel or otherwise. * ******************************************************************************/ - + #include "options.h" #include "wmc_auto.h" #include "functions.h" +#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM +#include +#endif + static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s_in, uint8_t* bytes, int bps , LC3_INT32 bfi_ext ) @@ -21,15 +25,28 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s quantizedGainMin = 0, nbits = 0, nbits2 = 0, lastnz = 0, lsbMode = 0, gainChange = 0, bp_side = 0, mask_side = 0, fac_ns_idx = 0, numResBits = 0, tns_order[2] = {0}, i = 0; LC3_FLOAT normcorr = 0, gain = 0; - LC3_FLOAT d_fl[MAX_LEN] = {0}; LC3_INT q_d[MAX_LEN] = {0}; LC3_INT indexes[TNS_NUMFILTERS_MAX * MAXLAG] = {0}; +#ifdef CR9_C_ADD_1p25MS_LRSNS + LC3_INT16 envelope_bits = -1; + LC3_INT32 pitch_rx = -1; /* pitch_rx status flag */ + LC3_INT32 ltpf_rx = -1; +#endif + h_EncSetup = encoder->channel_setup[channel]; memset(bytes, 0, sizeof(uint8_t) * h_EncSetup->targetBytes); +#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM + if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + assert(MAX_LEN >= (GG_1p25_MAX_TUPLES - 1) + (encoder->yLen)); + /*make sure that there extra tail coeffs in d_fl for Global gain estimation routine */ + } +#endif + if (bps == 24) { for (i = 0; i < encoder->frame_length; i++) { int32_t tmp = ((int32_t*)s_in)[i]; @@ -62,17 +79,33 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s /* Pitch estimation */ processOlpa_fl(h_EncSetup->s_12k8, h_EncSetup->olpa_mem_s12k8, h_EncSetup->olpa_mem_s6k4, - &h_EncSetup->olpa_mem_pitch, - &h_EncSetup->pitch_flag, + &h_EncSetup->olpa_mem_pitch, + &h_EncSetup->pitch_flag, &T0_out, &normcorr, s_12k8_len, encoder->frame_dms); /* LTPF encoder */ process_ltpf_coder_fl(h_EncSetup->s_12k8, s_12k8_len + 1, h_EncSetup->ltpf_enable, T0_out, normcorr, encoder->frame_dms, h_EncSetup->ltpf_mem_in, encoder->ltpf_mem_in_len, - &h_EncSetup->ltpf_mem_normcorr, &h_EncSetup->ltpf_mem_ltpf_on, +#ifdef CR9_C_ADD_1p25MS + h_EncSetup->ltpf_mem_normcorr, +#else + &h_EncSetup->ltpf_mem_normcorr, +#endif + &h_EncSetup->ltpf_mem_ltpf_on, &h_EncSetup->ltpf_mem_pitch, h_EncSetup->ltpf_param, &h_EncSetup->ltpf_mem_mem_normcorr, <pfBits , encoder->hrmode +#ifdef CR9_C_ADD_1p25MS +#ifdef FIX_TX_RX_STRUCT_STEREO +#ifdef NEW_SIGNALLING_SCHEME_1p25 + ,&h_EncSetup->Tx_ltpf +#else + ,h_EncSetup->Tx_ltpf +#endif +#else + , encoder->Tx_ltpf +#endif +#endif ); /* Attack detector */ @@ -83,8 +116,8 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s /* Per-band energy */ processPerBandEnergy_fl(encoder->bands_number, encoder->bands_offset, encoder->hrmode, encoder->frame_dms, h_EncSetup->ener, d_fl); /* Near Nyquist detector */ - processNearNyquistdetector_fl(&encoder->near_nyquist_flag, encoder->fs_idx, encoder->near_nyquist_index, encoder->bands_number, h_EncSetup->ener - , encoder->frame_dms, encoder->hrmode ); + processNearNyquistdetector_fl(&encoder->near_nyquist_flag, encoder->fs_idx, encoder->near_nyquist_index, encoder->bands_number, h_EncSetup->ener + , encoder->frame_dms, encoder->hrmode ); /* Disable LTPF if nyquist detector triggers or -lfe mode is active*/ if (encoder->near_nyquist_flag != 0 || h_EncSetup->lfe == 1) { @@ -105,25 +138,52 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s } processSnsComputeScf_fl(h_EncSetup->ener, encoder->bands_number, h_EncSetup->scf, - h_EncSetup->attdec_detected, encoder->sns_damping, encoder->attdec_damping, encoder->fs_idx); + h_EncSetup->attdec_detected, encoder->sns_damping, encoder->attdec_damping, encoder->fs_idx +#ifdef CR9_C_ADD_1p25MS + , encoder->frame_dms, &encoder->long_term_norm_corr, normcorr +#endif + ); /* SNS Quantizer */ +#ifdef CR9_C_ADD_1p25MS_LRSNS + if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + /* 9,10, 29,30 bit LRSNS quantizer */ + pitch_rx = h_EncSetup->ltpf_param[0]; /* pitch_rx status flag */ + ltpf_rx = h_EncSetup->ltpf_param[1]; /* ltpf active flag, LTPF lag data information is split over two 1.25 ms frames */ + assert(ltpfBits == 2 || ltpfBits == 6 || ltpfBits == 7); + assert((pitch_rx != 0 && ltpf_rx == 0) || (pitch_rx != 0 && ltpf_rx != 0) || (pitch_rx == 0 && ltpf_rx == 0)); + + envelope_bits = snsQuantScfEncLR(h_EncSetup->scf, h_EncSetup->L_scf_idx, h_EncSetup->scf_q, h_EncSetup->dct2StructSNS, pitch_rx, ltpf_rx); + } + else + { /* legacy 38 bit SNS VQ */ + assert(ltpfBits == 1 || ltpfBits == 11); + process_snsQuantizesScf_Enc(h_EncSetup->scf, h_EncSetup->L_scf_idx, h_EncSetup->scf_q, h_EncSetup->dct2StructSNS); + envelope_bits = 38; + } +#else process_snsQuantizesScf_Enc(h_EncSetup->scf, h_EncSetup->L_scf_idx, h_EncSetup->scf_q, h_EncSetup->dct2StructSNS); +#endif /* SNS Interpolation */ processSnsInterpolateScf_fl(h_EncSetup->scf_q, 1, encoder->bands_number, h_EncSetup->int_scf); /* MDCT shaping */ processMdctShaping_fl(d_fl, h_EncSetup->int_scf, encoder->bands_offset, encoder->bands_number); - + /* Bandwidth controller */ if (encoder->bandwidth < encoder->fs / 2) { process_cutoff_bandwidth(d_fl, encoder->yLen, encoder->bw_ctrl_cutoff_bin); BW_cutoff_idx = MIN(BW_cutoff_idx, encoder->bw_index); } - + /* TNS encoder */ +#ifdef CR9_C_ADD_1p25MS + if (h_EncSetup->lfe == 0 && encoder->frame_dms > LC3PLUS_FRAME_DURATION_1p25MS) +#else if (h_EncSetup->lfe == 0) +#endif { processTnsCoder_fl(d_fl, BW_cutoff_idx, encoder->cutoffBins[BW_cutoff_idx], encoder->fs, encoder->frame_length, encoder->frame_dms, h_EncSetup->total_bits, tns_order, indexes, &tns_numfilters, @@ -134,20 +194,25 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s else { tns_numfilters = 1; +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + tns_numfilters = 0; + } +#endif tns_order[0] = 0; h_EncSetup->tns_bits = tns_numfilters; } /* Global Gain Estimation */ h_EncSetup->targetBitsQuant = h_EncSetup->targetBitsInit - (h_EncSetup->tns_bits + ltpfBits); - if (h_EncSetup->targetBitsQuant < 0 && ltpfBits > 1) +#ifdef CR9_C_ADD_1p25MS_LRSNS + if ( encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { - /* Disable LTPF */ - h_EncSetup->ltpf_mem_ltpf_on = 0; - h_EncSetup->ltpf_param[1] = 0; - ltpfBits = 1; - h_EncSetup->targetBitsQuant = h_EncSetup->targetBitsInit - (h_EncSetup->tns_bits + ltpfBits); + h_EncSetup->targetBitsQuant += 38; /* 38 was already subtracted in h_EncSetup->targetBitsInit setup */ + assert(envelope_bits >= 9 && envelope_bits <= 30); + h_EncSetup->targetBitsQuant -= envelope_bits; /* 9,10, 29,30 */ } +#endif processEstimateGlobalGain_fl(d_fl, encoder->yLen, h_EncSetup->targetBitsQuant, &gain, &quantizedGain, &quantizedGainMin, h_EncSetup->quantizedGainOff, &h_EncSetup->targetBitsOff, @@ -173,7 +238,7 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s if (gainChange) { processQuantizeSpec_fl(d_fl, gain, q_d, encoder->yLen, h_EncSetup->total_bits, &nbits, &nbits2, encoder->fs, &lastnz, - h_EncSetup->codingdata, + h_EncSetup->codingdata, &lsbMode, 0, h_EncSetup->targetBitsQuant , encoder->hrmode ); @@ -181,10 +246,9 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s /* Noise factor */ if (h_EncSetup->lfe == 0) - { - processNoiseFactor_fl(&fac_ns_idx, d_fl, q_d, gain, encoder->cutoffBins[BW_cutoff_idx], encoder->frame_dms, - h_EncSetup->targetBytes - ); + { + processNoiseFactor_fl(&fac_ns_idx, d_fl, q_d, gain, encoder->cutoffBins[BW_cutoff_idx], encoder->frame_dms, + h_EncSetup->targetBytes); } else { @@ -192,9 +256,10 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s } /* Residual Coding */ if (lsbMode == 0) { - processResidualCoding_fl(d_fl, q_d, gain, encoder->yLen, h_EncSetup->targetBitsQuant, nbits2, - h_EncSetup->resBits, &numResBits - , encoder->hrmode + processResidualCoding_fl(d_fl, q_d, gain, encoder->yLen, h_EncSetup->targetBitsQuant, nbits2, h_EncSetup->resBits, &numResBits, encoder->hrmode +#ifdef ENABLE_12p5_DMS_MODE + , encoder->frame_dms +#endif ); } else { numResBits = 0; @@ -203,31 +268,38 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s /* Entropy encoding */ processEncoderEntropy_fl(bytes, &bp_side, &mask_side, h_EncSetup->targetBytes, encoder->BW_cutoff_bits, BW_cutoff_idx, lastnz, encoder->yLen, lsbMode, quantizedGain, tns_numfilters, tns_order, - h_EncSetup->ltpf_param, h_EncSetup->L_scf_idx, fac_ns_idx - , bfi_ext, encoder->fs_idx + h_EncSetup->ltpf_param, h_EncSetup->L_scf_idx, fac_ns_idx, + bfi_ext, encoder->fs_idx +#ifdef CR9_C_ADD_1p25MS +#ifdef FIX_TX_RX_STRUCT_STEREO + , encoder->frame_dms, &h_EncSetup->Tx_ltpf +#else + , encoder->frame_dms, &encoder->Tx_ltpf +#endif +#endif ); /* Artithmetic encoding */ processAriEncoder_fl(bytes, bp_side, mask_side, q_d, tns_order, tns_numfilters, indexes, lastnz, - h_EncSetup->codingdata, + h_EncSetup->codingdata, h_EncSetup->resBits, numResBits, lsbMode, h_EncSetup->targetBitsAri, h_EncSetup->enable_lpc_weighting); - + if (encoder->combined_channel_coding == 0 && h_EncSetup->n_pc > 0) { LC3_INT32 xbuf[MAX_LEN] = {0}, nf_seed, tns_idx[M], zero_frame, nbits_residual, residualPresent, b_left, spec_inv_idx; - + memset(h_EncSetup->resBits, 0, sizeof(*(h_EncSetup->resBits)) * MAX_RESBITS_LEN); - + processAriDecoder_fl(bytes, bp_side, mask_side, encoder->yLen, encoder->fs_idx, h_EncSetup->enable_lpc_weighting, tns_numfilters, lsbMode, lastnz, &bfi_ext, tns_order, fac_ns_idx, quantizedGain, h_EncSetup->resBits, xbuf, &nf_seed, tns_idx, &zero_frame, h_EncSetup->targetBytes, &nbits_residual, &residualPresent, encoder->frame_dms, h_EncSetup->n_pc, 0, h_EncSetup->total_bits >> 3, 1, &b_left, &spec_inv_idx, encoder->hrmode); - + processReorderBitstream_fl(bytes, h_EncSetup->n_pccw, h_EncSetup->n_pc, b_left, h_EncSetup->targetBytes); } - + } int Enc_LC3PLUS_fl(LC3PLUS_Enc* encoder, void** input, uint8_t* output, int bps @@ -239,7 +311,7 @@ int Enc_LC3PLUS_fl(LC3PLUS_Enc* encoder, void** input, uint8_t* output, int bps LC3_INT32 totalBytes; LC3_INT32 output_size2, input_size; - + totalBytes = encoder->bitrate * encoder->frame_length / (8 * encoder->fs_in); for (ch = 0; ch < encoder->channels; ch++) @@ -269,6 +341,6 @@ int Enc_LC3PLUS_fl(LC3PLUS_Enc* encoder, void** input, uint8_t* output, int bps fec_encoder(encoder->epmode, encoder->epmr, output, input_size, output_size, encoder->channel_setup[0]->n_pccw); } - + return output_size; } diff --git a/lib_lc3plus/estimate_global_gain.c b/lib_lc3plus/estimate_global_gain.c index c32d85c363..efa998a9ad 100644 --- a/lib_lc3plus/estimate_global_gain.c +++ b/lib_lc3plus/estimate_global_gain.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -15,7 +15,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC3_FLOAT* gain, LC3_INT* quantizedGain, LC3_INT* quantizedGainMin, LC3_INT quantizedGainOff, LC3_FLOAT* targetBitsOff, LC3_INT* old_targetBits, LC3_INT old_specBits - , LC3_INT hrmode , LC3_INT regBits, LC3_FLOAT frame_ms + , LC3_INT hrmode , LC3_INT regBits, LC3PLUS_FrameDuration frame_ms ) { @@ -23,7 +23,47 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC LC3_FLOAT g_min, x_max, tmp, ind, ind_min, target, ener; LC3_FLOAT en[MAX_LEN / 4]; LC3_FLOAT reg_val = 4.656612873077393e-10; +#ifdef FIX_1p25_GG_EST_TUPLES + LC3_INT tuples[1 + 4] = { -1, GG_1p25_WB_TUPLES, GG_1p25_SSWB_TUPLES, GG_1p25_SWB_TUPLES, GG_1p25_FB_TUPLES }; + LC3_INT bw_idx; +#endif +#ifdef FIX_1p25_GG_EST_TUPLES + LC3_INT32 lg_extra; + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + assert((MAX_LEN) >= ((lg / GG_1p25_MAX_TUPLES) + 1)*GG_1p25_MAX_TUPLES); /* en size check, for 1p25ms max tuple size*/ + } +#else +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 + assert( (MAX_LEN / 4) >= ((8*4 + 2) / 2)); /* en size check, for 1p25ms SSWB */ +#endif +#endif + +#ifdef FIX_1p25_GG_EST_TUPLES + lg_extra = 0; + bw_idx = MIN((lg / 10) - 1, 4); + + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + lg_extra = tuples[bw_idx] * (( lg + (tuples[bw_idx] - 1)) / tuples[bw_idx]) - lg; + /*x buffer is assert checked in enc_lc3_fl.c() for 1.25ms energy calculation */ + for (i = lg; i < (lg + lg_extra); i++) + { + x[i] = 0.0; /* zero extended tail if a truncated tuple-block exists */ + } + } +#else +#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS + LC3_INT32 lg_extra; + + lg_extra = lg - 4 * (lg / 4); + for ( i=lg; i< (lg+lg_extra); i++) + { + x[i] = 0.0; /* zero tail if a truncated quadruple exists */ + } +#endif +#endif if (*old_targetBits < 0) { *targetBitsOff = 0; } else { @@ -39,7 +79,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC if (hrmode && regBits > 0) { LC3_FLOAT M0 = 1e-5, M1 = 1e-5, rB_offset; - LC3_FLOAT thresh = 2*frame_ms; + LC3_FLOAT thresh = 2*frame_ms*1.25; for (i = 0; i < lg; i++) { M0 += fabs(x[i]); @@ -55,75 +95,338 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC ind_min = quantizedGainOff; ind = 0; *old_targetBits = -1; - } else { + } + else { if (hrmode == 1) { g_min = x_max / (32768 * 256 - 2); - } else { + } + else { g_min = x_max / (32767 - 0.375); } /* Prevent positive rounding errors from LC3_LOG10 function */ ind_min = 28.0 * LC3_LOGTEN(g_min); ind_min = ceil(ind_min + LC3_FABS(ind_min) * LC3_EPS); - + assert(LC3_POW(10, ind_min / 28.0) >= g_min); assert(ind_min <= (255 + quantizedGainOff)); +#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS + N = lg + lg_extra; +#else N = lg; +#endif - j = 0; - for (i = 0; i < N; i = i + 4) { - tmp = x[i] * x[i]; - tmp += x[i + 1] * x[i + 1]; - tmp += x[i + 2] * x[i + 2]; - tmp += x[i + 3] * x[i + 3]; - en[j] = (28.0 / 20.0) * (7 + 10.0 * LC3_LOGTEN(tmp + reg_val)); - j++; - } - target = (28.0 / 20.0) * (1.4) * nbitsSQ; - fac = 256; - offset = 255 + quantizedGainOff; - - for (i = 0; i < 8; i++) +#ifndef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 + /* increase to at least 10 analysis bands for WB, SSWB 1p25ms */ + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS && lg <= 30) { - fac = fac >> 1; - offset = offset - fac; - ener = 0; - iszero = 1; + j = 0; + for (i = 0; i < N; i = i + 2) /* steps of 2 for 1.25ms frame lengths */ + { + tmp = x[i] * x[i]; + tmp += x[i + 1] * x[i + 1]; + en[j] = (28.0 / 20.0) * (7 * 0.5 + 10.0 * LC3_LOGTEN(tmp + reg_val)); /* offset of 7 per 4 coeff band now changed to 3.5 per 2 coeff band */ + j++; + } - for (j = N / 4 - 1; j >= 0; j--) { - tmp = en[j] - offset; + target = (28.0 / 20.0) * (1.4) * nbitsSQ; /* global index domain sum over all 10 bands */ - if (tmp < (7.0) * (28.0 / 20.0)) { - if (iszero == 0) { - ener = ener + (2.7) * (28.0 / 20.0); + fac = 256; + offset = 255 + quantizedGainOff; + + for (i = 0; i < 8; i++) + { + fac = fac >> 1; + offset = offset - fac; + ener = 0; + iszero = 1; + + /* we sum up energy from the top, to not add up noisefilled coeffs */ + for (j = N / 2 - 1; j >= 0; j--) + { + tmp = en[j] - offset; + + if (tmp < ((7.0) * (28.0 / 20.0) * 0.5)) + { + if (iszero == 0) + { + ener = ener + ((2.7) * (28.0 / 20.0)* 0.5); /* low cost in coded band zero */ + } } - } else { - if (tmp > (50.0) * (28.0 / 20.0)) { - ener = ener + 2.0 * tmp - (50.0) * (28.0 / 20.0); - } else { - ener = ener + tmp; + else + { + if (tmp > ((50.0) * (28.0 / 20.0)*0.5)) + { + /* high value with many escapes */ + + ener = ener + 2.0 * tmp - (50.0) * (28.0 / 20.0)*0.5; + + } + else + { + ener = ener + (tmp*1.0); + } + iszero = 0; } + } /* loop over over band N/2-1 ... 0 */ - iszero = 0; + if (ener > target && iszero == 0) + { + offset = offset + fac; } - } - if (ener > target && iszero == 0) { - offset = offset + fac; + } /* over 8 splits/iterations to handle all 256 possible shifts */ + + if (offset < ind_min) + { + *old_targetBits = -1; } - } - if (offset < ind_min) { - *old_targetBits = -1; + ind = MAX(ind_min, offset) - quantizedGainOff; } + else +#endif +#endif + + + +#ifdef FIX_1p25_GG_EST_TUPLES + +#if GG_1p25_MAX_TUPLES == 2 + /* the tuple/2-block with halved limits, is here separated from the 4-block loop , + optionally they can be parametrized into one function */ + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) +#else + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS && tuples[bw_idx] == 2) +#endif + { + assert(((lg / 2) * 2) == lg); + + j = 0; + for (i = 0; i < N; i = i + 2) + { + tmp = x[i] * x[i]; + tmp += x[i + 1] * x[i + 1]; + en[j] = (28.0 / 20.0) * (7 * 0.5 + 10.0 * LC3_LOGTEN(tmp + reg_val)); /* offset of 7 per 4 coeff band now changed to 3.5 per 2 coeff band */ + j++; + } + + target = (28.0 / 20.0) * (1.4) * nbitsSQ; /* global index domain sum over all bands */ + + fac = 256; + offset = 255 + quantizedGainOff; + + for (i = 0; i < 8; i++) + { + fac = fac >> 1; + offset = offset - fac; + ener = 0; + iszero = 1; + + /* we sum up energy from the top, to not add up noisefilled coeffs */ + for (j = N / 2 - 1; j >= 0; j--) + { + tmp = en[j] - offset; - ind = MAX(ind_min, offset) - quantizedGainOff; + if (tmp < ((7.0) * (28.0 / 20.0) * 0.5)) + { + if (iszero == 0) + { + ener = ener + ((2.7) * (28.0 / 20.0)* 0.5); /* low cost in coded band zero */ + } + } + else + { + if (tmp > ((50.0) * (28.0 / 20.0)*0.5)) + { + /* high value with many escapes */ + + ener = ener + 2.0 * tmp - (50.0) * (28.0 / 20.0)*0.5; + + } + else + { + ener = ener + (tmp*1.0); + } + iszero = 0; + } + } /* loop over over band N/2-1 ... 0 */ + + if (ener > target && iszero == 0) + { + offset = offset + fac; + } + + } /* over 8 splits/iterations to handle all 256 possible shifts */ + + if (offset < ind_min) + { + *old_targetBits = -1; + } + + ind = MAX(ind_min, offset) - quantizedGainOff; + } + else + +#if GG_1p25_MAX_TUPLES == 3 + /* the 3tuple -block with modified limits, is here separated from the quadruple/4-block loop , + optionally they can be parametrized into one function */ + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) +#else + + if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS && tuples[bw_idx] == 3) +#endif + { /* 3 tuple loop */ + N = 3 * (int)ceil(lg / 3.0); + + assert(lg == 50 || lg == 40 || lg == 30 || lg == 20); + assert(N == (17 * 3) || N == (14 * 3) || N == (10 * 3) || N == (7 * 3)); + + j = 0; + for (i = 0; i < N; i = i + 3) + { + tmp = x[i] * x[i]; + tmp += x[i + 1] * x[i + 1]; + tmp += x[i + 2] * x[i + 2]; + en[j] = (28.0 / 20.0) * (7 * 0.75 + 10.0 * LC3_LOGTEN(tmp + reg_val)); /* offset of 7*0.75 per 3 coeff band n */ + j++; + } + + target = (28.0 / 20.0) * (1.4) * nbitsSQ; /* global index domain sum over all bands */ + + fac = 256; + offset = 255 + quantizedGainOff; + + for (i = 0; i < 8; i++) + { + fac = fac >> 1; + offset = offset - fac; + ener = 0; + iszero = 1; + + /* we sum up energy from the top, to not add up noisefilled coeffs */ + for (j = N / 3 - 1; j >= 0; j--) + { + tmp = en[j] - offset; + + if (tmp < ((7.0) * (28.0 / 20.0) * 0.75)) + { + if (iszero == 0) + { + ener = ener + ((2.7) * (28.0 / 20.0)* 0.75); /* low cost in coded band zero */ + } + } + else + { + if (tmp > ((50.0) * (28.0 / 20.0)*0.75)) + { + /* high value with many escapes */ + + ener = ener + 2.0 * tmp - (50.0) * (28.0 / 20.0)*0.75; + + } + else + { + ener = ener + (tmp*1.0); + } + iszero = 0; + } + } /* loop over over band N/3-1 ... 0 */ + + if (ener > target && iszero == 0) + { + offset = offset + fac; + } + + } /* over 8 splits/iterations to handle all 256 possible shifts */ + + if (offset < ind_min) + { + *old_targetBits = -1; + } + + ind = MAX(ind_min, offset) - quantizedGainOff; + } + else + +#endif /* end 2, 3 tuple loops */ + +#ifdef FIX_1p25_GG_EST_TUPLES + { /* brackets for 4 tuple loop */ + assert((((lg + lg_extra) / 4) * 4) == (lg + lg_extra)); +#endif +#ifndef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 + { +#endif +#endif + + j = 0; + for (i = 0; i < N; i = i + 4) { + tmp = x[i] * x[i]; + tmp += x[i + 1] * x[i + 1]; + tmp += x[i + 2] * x[i + 2]; + tmp += x[i + 3] * x[i + 3]; + en[j] = (28.0 / 20.0) * (7 + 10.0 * LC3_LOGTEN(tmp + reg_val)); + j++; + } + + target = (28.0 / 20.0) * (1.4) * nbitsSQ; + fac = 256; + offset = 255 + quantizedGainOff; + + for (i = 0; i < 8; i++) + { + fac = fac >> 1; + offset = offset - fac; + ener = 0; + iszero = 1; + + for (j = N / 4 - 1; j >= 0; j--) { + tmp = en[j] - offset; + + if (tmp < (7.0) * (28.0 / 20.0)) { + if (iszero == 0) { + ener = ener + (2.7) * (28.0 / 20.0); + } + } + else { + if (tmp > (50.0) * (28.0 / 20.0)) { + ener = ener + 2.0 * tmp - (50.0) * (28.0 / 20.0); + } + else { + ener = ener + tmp; + } + + iszero = 0; + } + } + + if (ener > target && iszero == 0) { + offset = offset + fac; + } + } + + if (offset < ind_min) { + *old_targetBits = -1; + } + + ind = MAX(ind_min, offset) - quantizedGainOff; + } +#ifdef FIX_1p25_GG_EST_TUPLES + } + /* end bracket for 4 tuple loop */ +#else +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 } +#endif +#endif *quantizedGainMin = ind_min; - *quantizedGain = ind; + *quantizedGain = ind; *gain = LC3_POW(10.0, ((ind + quantizedGainOff) / 28.0)); } diff --git a/lib_lc3plus/fft/cfft.c b/lib_lc3plus/fft/cfft.c index 6ec89eb299..ae8e5813bb 100644 --- a/lib_lc3plus/fft/cfft.c +++ b/lib_lc3plus/fft/cfft.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -129,7 +129,9 @@ static void scramble(LC3_FLOAT* re, LC3_FLOAT* im, LC3_INT n, LC3_INT s) for (m = 1, j = 0; m < (n - 1); m++) { { for (k = n >> 1; (!((j ^= k) & k)); k >>= 1) + { ; + } } if (j > m) { diff --git a/lib_lc3plus/fft/cfft.h b/lib_lc3plus/fft/cfft.h index a67d66f057..5794d48cea 100644 --- a/lib_lc3plus/fft/cfft.h +++ b/lib_lc3plus/fft/cfft.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_15_16.h b/lib_lc3plus/fft/fft_15_16.h index ced87c6671..a338ae24a6 100644 --- a/lib_lc3plus/fft/fft_15_16.h +++ b/lib_lc3plus/fft/fft_15_16.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_240_480.h b/lib_lc3plus/fft/fft_240_480.h index 40969ba48e..92f67cb1e0 100644 --- a/lib_lc3plus/fft/fft_240_480.h +++ b/lib_lc3plus/fft/fft_240_480.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_2_9.h b/lib_lc3plus/fft/fft_2_9.h index 0166ad43d7..6080729fa0 100644 --- a/lib_lc3plus/fft/fft_2_9.h +++ b/lib_lc3plus/fft/fft_2_9.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_32.h b/lib_lc3plus/fft/fft_32.h index 39cffc6977..8523d4b452 100644 --- a/lib_lc3plus/fft/fft_32.h +++ b/lib_lc3plus/fft/fft_32.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_384_768.h b/lib_lc3plus/fft/fft_384_768.h index 404ef79492..805fbdb70c 100644 --- a/lib_lc3plus/fft/fft_384_768.h +++ b/lib_lc3plus/fft/fft_384_768.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_60_128.h b/lib_lc3plus/fft/fft_60_128.h index e2b17450e7..da8922a709 100644 --- a/lib_lc3plus/fft/fft_60_128.h +++ b/lib_lc3plus/fft/fft_60_128.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/fft_generic.h b/lib_lc3plus/fft/fft_generic.h index 32030b5ca4..b17860de28 100644 --- a/lib_lc3plus/fft/fft_generic.h +++ b/lib_lc3plus/fft/fft_generic.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/iis_fft.c b/lib_lc3plus/fft/iis_fft.c index 085e60635c..9ead28b4af 100644 --- a/lib_lc3plus/fft/iis_fft.c +++ b/lib_lc3plus/fft/iis_fft.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/iis_fft.h b/lib_lc3plus/fft/iis_fft.h index d68b27d891..3be02950fc 100644 --- a/lib_lc3plus/fft/iis_fft.h +++ b/lib_lc3plus/fft/iis_fft.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/iisfft.c b/lib_lc3plus/fft/iisfft.c index aaca87db9f..2889c9ae25 100644 --- a/lib_lc3plus/fft/iisfft.c +++ b/lib_lc3plus/fft/iisfft.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/fft/iisfft.h b/lib_lc3plus/fft/iisfft.h index 4fe8f3abc3..c8809a3123 100644 --- a/lib_lc3plus/fft/iisfft.h +++ b/lib_lc3plus/fft/iisfft.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -23,26 +23,14 @@ the restrict keyword only gives a improvelent if more than one pointers are passed to a function. also note that the MSVC __restrict behaves differently from c99, the restrict property is not transferred to aliases. +*/ - alloca is a bit problematic because behavior is not defined in case of stack - overflow. most probably the program will crash. it might be possible to catch - those errors but it depends on compiler support. msvc has a safer _malloca - but gcc has nothing similar. */ #if defined _MSC_VER || defined __INTEL_COMPILER -#include -#define ALLOCA(size) _alloca(size) -#define restrict __restrict -#define inline __inline + #define restrict __restrict + #define inline __inline #elif defined __GNUC__ || defined __clang__ -#define ALLOCA(size) __builtin_alloca(size) -#define restrict __restrict__ -#define inline __inline -#elif defined __TI_COMPILER_VERSION__ -#include -#define ALLOCA(size) (assert(0 && "ALLOCA is not present for your compiler"), NULL) -#warn "no stack allocation for you compiler" -#else -#error "no stack allocation for your compiler" + #define restrict __restrict__ + #define inline __inline #endif diff --git a/lib_lc3plus/functions.h b/lib_lc3plus/functions.h index a2f4857b2a..d26c1ff048 100644 --- a/lib_lc3plus/functions.h +++ b/lib_lc3plus/functions.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -21,6 +21,30 @@ #include "structs.h" #include "util.h" +#ifdef CR9_C_ADD_1p25MS_LRSNS +LC3_INT16 snsQuantScfEncLR(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT* envq, Dct2 dct2structSNS, LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx); +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +/* needed in both encoder and decoder */ +LC3_FLOAT snslr_remove_st1_DC_fQ11(LC3_FLOAT *scfq, LC3_INT32 len); +void snslr_st1B_vector_dec(LC3_INT16 idx, const LC3_INT16* LFCB, const LC3_INT16 *HFCB, const LC3_INT16* seg_cnt_cum, const LC3_INT16* idx12b_cb, LC3_FLOAT *st1B_vector); +void snslr_st1C_vector_dec(LC3_INT16 idx, const LC3_INT8* CBW8, LC3_INT16 scaleQ4, LC3_INT16 inv_scaleQ15, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT *st1C_vector); +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +LC3_INT32 MSEsearchGeneric(LC3_FLOAT *scf, const LC3_FLOAT *sns_CB, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse); +LC3_INT32 MSEsearchCbBIdxMap(const LC3_FLOAT *scf, const LC3_INT16 *LFCB, const LC3_INT16 *HFCB, const LC3_INT16 *seg_cnt_cum, const LC3_INT16* idx12b_cb, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse); +LC3_INT32 MSEsearchGenericScaledW8(LC3_FLOAT *scf, const LC3_INT8 *sns_CBW8, LC3_INT16 scaleQ4, LC3_INT16 inv_scaleQ15, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse); +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +void snsQuantScfDecLR(LC3_INT32* sns_vq_idx, LC3_FLOAT* scf_q, LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx); +#endif + +/* Common */ +LC3_INT16 getLastNzBits (LC3_INT16 N); + /* FFT */ #include "fft/iisfft.h" @@ -48,7 +72,7 @@ void dct4_free(Dct4* dct); void dct4_apply(Dct4* dct, const LC3_FLOAT* input, LC3_FLOAT* output); /* mdct.c */ -void mdct_init(Mdct* mdct, LC3_INT length, LC3_INT frame_dms, LC3_INT fs_idx, LC3_INT hrmode); +void mdct_init(Mdct* mdct, LC3_INT length, LC3PLUS_FrameDuration frame_dms, LC3_INT fs_idx, LC3_INT hrmode); void mdct_free(Mdct* mdct); void mdct_apply(const LC3_FLOAT* input, LC3_FLOAT* output, Mdct* mdct); @@ -58,13 +82,22 @@ LC3_INT paddingDec_fl(LC3_UINT8* bytes, LC3_INT nbbits, LC3_INT L_spec, LC3_INT void processEncoderEntropy_fl(LC3_UINT8* bytes, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT numbytes, LC3_INT bw_cutoff_bits, LC3_INT bw_cutoff_idx, LC3_INT lastnz, LC3_INT N, LC3_INT lsbMode, LC3_INT gg_idx, LC3_INT num_tns_filters, - LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* scf_idx, LC3_INT fac_ns_idx - , LC3_INT bfi_ext, LC3_INT fs_idx + LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* scf_idx, LC3_INT fac_ns_idx, + LC3_INT bfi_ext, LC3_INT fs_idx +#ifdef CR9_C_ADD_1p25MS + , LC3PLUS_FrameDuration frame_dms, LC3_INT16* Tx_ltpf +#endif ); void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_side, LC3_INT* bp_side, LC3_INT N, LC3_INT fs_idx, LC3_INT bw_cutoff_bits, LC3_INT* bfi, LC3_INT* gg_idx, LC3_INT* scf_idx, LC3_INT* fac_ns_idx, LC3_INT* tns_numfilters, LC3_INT* tns_order, LC3_INT* ltpf_idx, LC3_INT* bw_cutoff_idx, LC3_INT* lastnz, - LC3_INT* lsbMode, LC3_INT frame_dms + LC3_INT* lsbMode, LC3PLUS_FrameDuration frame_dms +#ifdef CR9_C_ADD_1p25MS + , LC3_INT32 rx_status[2], LC3_INT16* mem_continuation +#ifdef NEW_SIGNALLING_SCHEME_1p25 + , LC3_INT32 *ltpfinfo_frame_cntr /* set/reset here, but increased outside during/when bfi for the channel */ +#endif +#endif ); void processQuantizeSpec_fl(LC3_FLOAT x[], LC3_FLOAT gain, LC3_INT xq[], LC3_INT nt, LC3_INT totalBits, LC3_INT* nbits, LC3_INT* nbits2, LC3_INT fs, LC3_INT* lastnzout, LC3_INT* codingdata, LC3_INT* lsbMode, LC3_INT mode, LC3_INT target, LC3_INT hrmode); @@ -72,46 +105,50 @@ void processQuantizeSpec_fl(LC3_FLOAT x[], LC3_FLOAT gain, LC3_INT xq[], LC3_INT void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC3_FLOAT* gain, LC3_INT* quantizedGain, LC3_INT* quantizedGainMin, LC3_INT quantizedGainOff, LC3_FLOAT* targetBitsOff, LC3_INT* old_targetBits, LC3_INT old_specBits, LC3_INT bq_mode - , LC3_INT regBits, LC3_FLOAT frame_ms + , LC3_INT regBits, LC3PLUS_FrameDuration frame_ms ); void processAriDecoder_fl(LC3_UINT8* bytes, LC3_INT bp_side, LC3_INT mask_side, LC3_INT L_spec, LC3_INT fs_idx, LC3_INT enable_lpc_weighting, LC3_INT tns_numfilters, LC3_INT lsbMode, LC3_INT lastnz, LC3_INT* bfi, LC3_INT* tns_order, LC3_INT fac_ns_idx, LC3_INT gg_idx, uint8_t* resBits, LC3_INT* x, LC3_INT* nf_seed, LC3_INT* tns_idx, LC3_INT* zero_frame, LC3_INT numbytes, - LC3_INT* nbits_residual, LC3_INT* residualPresent, LC3_INT frame_dms, + LC3_INT* nbits_residual, LC3_INT* residualPresent, LC3PLUS_FrameDuration frame_dms, LC3_INT32 n_pc, LC3_INT32 be_bp_left, LC3_INT32 be_bp_right, LC3_INT32 enc, LC3_INT32 *b_left, LC3_INT32 *spec_inv_idx, LC3_INT hrmode ); void processMdctShaping_fl(LC3_FLOAT x[], LC3_FLOAT gains[], const LC3_INT bands_offset[], LC3_INT fdns_npts); -void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_INT L_spec, LC3_INT targetBits, LC3_INT nBits, uint8_t * resBits, - LC3_INT* numResBits - , LC3_INT hrmode +void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_INT L_spec, LC3_INT targetBits, LC3_INT nBits, uint8_t * resBits, LC3_INT* numResBits, LC3_INT hrmode +#ifdef ENABLE_12p5_DMS_MODE + , LC3PLUS_FrameDuration frame_dms +#endif ); void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec, uint8_t prm[], LC3_INT resQBits , LC3_INT hrmode +#ifdef ENABLE_12p5_DMS_MODE + , LC3PLUS_FrameDuration frame_dms +#endif ); void processAdjustGlobalGain_fl(LC3_INT* gg_idx, LC3_INT gg_idx_min, LC3_INT gg_idx_off, LC3_FLOAT* gain, LC3_INT target, LC3_INT nBits, LC3_INT* gainChange, LC3_INT fs_idx - , LC3_INT16 hrmode, LC3_INT16 frame_dms + , LC3_INT16 hrmode, LC3PLUS_FrameDuration frame_dms ); void processApplyGlobalGain_fl(LC3_FLOAT x[], LC3_INT xLen, LC3_INT global_gain_idx, LC3_INT global_gain_off); -void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gg, LC3_INT BW_cutoff_idx, LC3_INT frame_dms, +void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gg, LC3_INT BW_cutoff_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT target_bytes ); -void processNoiseFilling_fl(LC3_FLOAT xq[], LC3_INT nfseed, LC3_INT fac_ns_idx, LC3_INT bw_stopband, LC3_INT frame_dms, LC3_FLOAT fac_ns_pc, LC3_INT spec_inv_idx); +void processNoiseFilling_fl(LC3_FLOAT xq[], LC3_INT nfseed, LC3_INT fac_ns_idx, LC3_INT bw_stopband, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT fac_ns_pc, LC3_INT spec_inv_idx); void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4, LC3_INT* mem_old_T0, LC3_INT* pitch_flag, - LC3_INT* T0_out,LC3_FLOAT* normcorr_out, LC3_INT len, LC3_INT frame_dms); + LC3_INT* T0_out,LC3_FLOAT* normcorr_out, LC3_INT len, LC3PLUS_FrameDuration frame_dms); -void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, LC3_INT fs, LC3_INT N, LC3_INT frame_dms, LC3_INT nBits, +void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, LC3_INT fs, LC3_INT N, LC3PLUS_FrameDuration frame_dms, LC3_INT nBits, LC3_INT* order_out, LC3_INT* rc_idx, LC3_INT* tns_numfilters, LC3_INT* bits_out , LC3_INT16 near_nyquist_flag ); @@ -119,36 +156,58 @@ void levinsonDurbin(LC3_FLOAT* r, LC3_FLOAT* out_lev, LC3_FLOAT* rc_unq, LC3_FLO void processTnsDecoder_fl(LC3_FLOAT* x, LC3_INT* rc_idx, LC3_INT* order, LC3_INT numfilters, LC3_INT bw_fcbin, LC3_INT N, LC3_INT fs); -void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_INT smooth, LC3_FLOAT sns_damping, LC3_FLOAT attdec_damping_factor, LC3_INT fs_idx); +void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_INT smooth, LC3_FLOAT sns_damping, LC3_FLOAT attdec_damping_factor, LC3_INT fs_idx +#ifdef CR9_C_ADD_1p25MS + , LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *LT_normcorr, LC3_FLOAT normcorr +#endif +); void processSnsInterpolateScf_fl(LC3_FLOAT* gains, LC3_INT encoder_side, LC3_INT bands_number, LC3_FLOAT* gains_LC3_INT); -void processDetectCutoffWarped_fl(LC3_FLOAT* d2, LC3_INT fs_idx, LC3_INT frame_dms, LC3_INT* bw_idx); +void processDetectCutoffWarped_fl(LC3_FLOAT* d2, LC3_INT fs_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT* bw_idx); void processNearNyquistdetector_fl(LC3_INT16* near_nyquist_flag, const LC3_INT fs_idx, const LC3_INT near_nyquist_index, - const LC3_INT bands_number, const LC3_FLOAT* ener - , const LC3_INT16 frame_dms, const LC3_INT16 hrmode ); -void processPerBandEnergy_fl(LC3_INT bands_number, const LC3_INT* acc_coeff_per_band, LC3_INT16 hrmode, LC3_INT16 frame_dms, LC3_FLOAT* d2, LC3_FLOAT* d); + const LC3_INT bands_number, const LC3_FLOAT* ener + , const LC3PLUS_FrameDuration frame_dms, const LC3_INT16 hrmode ); +void processPerBandEnergy_fl(LC3_INT bands_number, const LC3_INT* acc_coeff_per_band, LC3_INT16 hrmode, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT* d2, LC3_FLOAT* d); void ProcessingIMDCT_fl(LC3_FLOAT* y, LC3_INT yLen, const LC3_FLOAT* win, LC3_INT winLen, LC3_INT last_zeros, LC3_FLOAT* mem, LC3_FLOAT* x, Dct4* dct); void ProcessingITDA_WIN_OLA_fl(LC3_FLOAT* x_tda, LC3_INT32 yLen, const LC3_FLOAT* win, LC3_INT32 winLen, LC3_INT32 last_zeros, LC3_FLOAT* mem, LC3_FLOAT* x); -void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC3_INT pitch_ol, LC3_FLOAT pitch_ol_norm_corr, LC3_INT frame_dms, +void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC3_INT pitch_ol, LC3_FLOAT pitch_ol_norm_corr, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT* mem_old_x, LC3_INT memLen, LC3_FLOAT* mem_norm_corr_past, LC3_INT* mem_on, LC3_FLOAT* mem_pitch, LC3_INT* param, LC3_FLOAT* mem_norm_corr_past_past, LC3_INT* bits - , LC3_INT16 hrmode + , LC3_INT16 hrmode +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + ,LC3_INT16* Tx_ltpf +#else + ,LC3_INT16 Tx_ltpf +#endif +#endif ); +#ifdef CR9_C_ADD_1p25MS +void processTdcZeroCleanup(LC3_FLOAT *energies, LC3_INT32 n_bands, LC3PLUS_FrameDuration frame_dms); +#endif + void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT fs, LC3_FLOAT* mem_old_x, LC3_FLOAT* mem_old_y, LC3_INT* mem_pitch_LC3_INT, LC3_INT* mem_pitch_fr, LC3_FLOAT* mem_gain, LC3_INT* mem_beta_idx, LC3_INT bfi, - LC3_INT* param, LC3_INT* mem_param, LC3_INT conf_beta_idx, LC3_FLOAT conf_beta, LC3_INT concealMethod, LC3_FLOAT damping + LC3_INT* param, LC3_INT* mem_param, LC3_INT conf_beta_idx, LC3_FLOAT *conf_beta, LC3_INT concealMethod, LC3_FLOAT damping , LC3_INT *mem_ltpf_active - , LC3_FLOAT *rel_pitch_change, LC3_INT hrmode, LC3_INT frame_dms + , LC3_FLOAT *rel_pitch_change, LC3_INT hrmode, LC3PLUS_FrameDuration frame_dms +#ifdef CR9_C_ADD_1p25MS + , LC3_INT16* mem_continuation, LC3_INT32* mem_param_prev, LC3_INT16* mem_pitch_int_prev, + LC3_INT16* mem_pitch_fr_prev, LC3_INT32* mem_beta_idx_prev, LC3_FLOAT* mem_gain_prev, + LC3_INT16* pitch_stability_counter, + LC3_FLOAT* gain_step, + LC3_FLOAT conf_beta_max +#endif ); void process_resamp12k8_fl(LC3_FLOAT x[], LC3_INT x_len, LC3_FLOAT mem_in[], LC3_INT mem_in_len, LC3_FLOAT mem_50[], LC3_FLOAT mem_out[], - LC3_INT mem_out_len, LC3_FLOAT y[], LC3_INT* y_len, LC3_INT fs_idx, LC3_INT frame_dms, LC3_INT fs); + LC3_INT mem_out_len, LC3_FLOAT y[], LC3_INT* y_len, LC3_INT fs_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT fs); void write_bit_backward_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT bit); void write_uint_backward_fl(LC3_UINT8* ptr, LC3_INT* bp_side, LC3_INT* mask_side, LC3_INT val, LC3_INT numbits); @@ -187,6 +246,10 @@ LC3PLUS_Error Dec_LC3PLUS_fl(LC3PLUS_Dec* decoder, LC3_UINT8* input, int input_b void* balloc(void* base, size_t* base_size, size_t size); +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE +LC3_INT16 calc_GGainOffset_1p25(LC3_INT16 total_bits, LC3_INT16 fs_idx); +#endif + void processPlcMain_fl(LC3_FLOAT *q_d_fl_c, LC3_FLOAT *syntM_fl_c, LC3PLUS_Dec* decoder, DecSetup* h_DecSetup, LC3_INT bfi, PlcAdvSetup *PlcAdvSetup, PlcSetup *PlcSetup, LC3_INT plcMeth, LC3_INT ltpf_pitch_int, LC3_INT ltpf_pitch_fr, LC3_INT tilt, const LC3_INT *bands_offset, LC3_INT bands_number, const LC3_INT *bands_offsetPLC, @@ -213,13 +276,13 @@ LC3_INT32 fec_decoder(LC3_UINT8 *iobuf, LC3_INT16 slot_bytes, LC3_INT32 *data_by LC3_FLOAT array_max_abs(LC3_FLOAT *in, LC3_INT32 len); -void processPcClassify_fl(LC3_INT32 pitch_present, LC3_INT32 frame_dms, LC3_FLOAT *q_d_prev, LC3_FLOAT *q_old_res, LC3_INT32 yLen, LC3_INT32 spec_inv_idx, LC3_FLOAT stab_fac, LC3_INT32 *bfi); +void processPcClassify_fl(LC3_INT32 pitch_present, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *q_d_prev, LC3_FLOAT *q_old_res, LC3_INT32 yLen, LC3_INT32 spec_inv_idx, LC3_FLOAT stab_fac, LC3_INT32 *bfi); void processPcMain_fl(LC3_INT32 *bfi, LC3PLUS_Dec* decoder, LC3_FLOAT *sqQdec, DecSetup* h_DecSetup, LC3_INT32 pitch_present, LC3_FLOAT stab_fac, LC3_INT32 gg_idx, LC3_INT32 gg_idx_off, LC3_INT32 fac_ns_idx, pcState *statePC, LC3_INT32 spec_inv_idx, LC3_INT32 yLen); void processPcUpdate_fl(LC3_INT32 bfi, LC3_FLOAT *q_res, LC3_INT32 gg_idx, LC3_INT32 gg_idx_off, LC3_INT32 rframe, LC3_INT32 *BW_cutoff_idx_nf, LC3_INT32 *prev_BW_cutoff_idx_nf, LC3_INT32 fac_ns_idx, LC3_FLOAT *prev_fac_ns, LC3_FLOAT *fac, LC3_FLOAT *q_old_res, LC3_FLOAT *prev_gg, LC3_INT32 spec_inv_idx, LC3_INT32 yLen); void processPcApply_fl(LC3_FLOAT *q_res, LC3_FLOAT *q_old_res, LC3_FLOAT *q_d_prev, LC3_INT32 spec_inv_idx, LC3_INT32 yLen, LC3_INT32 gg_idx, LC3_INT32 gg_idx_off, LC3_FLOAT *prev_gg, LC3_FLOAT *fac, LC3_INT32 *pc_nbLostCmpt); void processPlcClassify_fl(LC3_INT plcMeth, LC3_INT *concealMethod, LC3_INT32 *nbLostCmpt, LC3_INT32 bfi, - LC3_FLOAT *xcorr, LC3_INT32 framelength, LC3_INT32 frame_dms, LC3_INT32 pitch_int, + LC3_FLOAT *xcorr, LC3_INT32 framelength, LC3PLUS_FrameDuration frame_dms, LC3_INT32 pitch_int, LC3_INT32 fs, const LC3_INT *band_offsets, LC3_INT32 bands_number, LC3_INT32 tilt, PlcAdvSetup *plcAd , LC3_INT32 hrmode ); @@ -229,20 +292,20 @@ void processPlcDampingScramblingMain_fl(LC3_INT32 *ns_seed, LC3_INT32 *pc_seed, LC3_INT32 ns_nbLostCmpt_pc, LC3_INT32 ns_nbLostCmpt, LC3_FLOAT *stabFac, LC3_FLOAT *cum_fading_slow, LC3_FLOAT *cum_fading_fast, LC3_FLOAT *spec_prev, LC3_FLOAT *spec, LC3_INT32 spec_inv_idx, LC3_INT32 yLen, LC3_INT32 bfi, - LC3_INT32 frame_dms, LC3_INT32 concealMethod, LC3_INT32 pitch_present_bfi1, LC3_INT32 pitch_present_bfi2, + LC3PLUS_FrameDuration frame_dms, LC3_INT32 concealMethod, LC3_INT32 pitch_present_bfi1, LC3_INT32 pitch_present_bfi2, LC3_FLOAT *cum_fflcAtten , LC3_UINT8 plc_fadeout_type ); void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 nbLostCmpt, LC3_FLOAT *stabFac, LC3_INT32 processDampScramb, - LC3_FLOAT *cum_fflcAtten, LC3_INT32 pitch_present, LC3_INT32 frame_dms, LC3_FLOAT *cum_fading_slow, + LC3_FLOAT *cum_fflcAtten, LC3_INT32 pitch_present, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *cum_fading_slow, LC3_FLOAT *cum_fading_fast, LC3_INT32 *seed, LC3_INT32 spec_inv_idx , LC3_UINT8 plc_fadeout_type ); void plc_phEcu_F0_refine_first(LC3_INT32 *plocs, LC3_INT32 n_plocs, LC3_FLOAT *f0est, const LC3_INT32 Xabs_len, LC3_FLOAT *f0binPtr, LC3_FLOAT *f0gainPtr, const LC3_INT32 nSubm); -void processTdcLpcEstimation_fl(LC3_FLOAT *r, LC3_INT32 fs_idx, LC3_INT32 len, LC3_FLOAT *A, LC3_INT32 frame_dms); +void processTdcLpcEstimation_fl(LC3_FLOAT *r, LC3_INT32 fs_idx, LC3_INT32 len, LC3_FLOAT *A, LC3PLUS_FrameDuration frame_dms); LC3_FLOAT plc_phEcuSetF0Hz(LC3_INT32 fs, LC3_FLOAT *old_pitchPtr); @@ -314,14 +377,14 @@ void processTdcPreemphasis_fl(LC3_FLOAT *in, LC3_FLOAT *pre_emph_factor, LC3_INT void processTdcTdac_fl(const LC3_FLOAT *synth_inp, const LC3_FLOAT *win, LC3_INT32 frame_length, LC3_INT32 la_zeroes, LC3_FLOAT *ola_mem); void processTdcInverseOdft_fl(LC3_FLOAT *in, LC3_INT32 n_bands, LC3_FLOAT *out, LC3_INT32 lpc_order); -void processTdcApply_fl(const LC3_INT32 pitch_LC3_INT, const LC3_FLOAT *preemphFac, const LC3_FLOAT* A, const LC3_INT32 lpc_order, const LC3_FLOAT* pcmbufHist, const LC3_INT32 max_len_pcm_plc, const LC3_INT32 N, const LC3_INT32 frame_dms, +void processTdcApply_fl(const LC3_INT32 pitch_LC3_INT, const LC3_FLOAT *preemphFac, const LC3_FLOAT* A, const LC3_INT32 lpc_order, const LC3_FLOAT* pcmbufHist, const LC3_INT32 max_len_pcm_plc, const LC3_INT32 N, const LC3PLUS_FrameDuration frame_dms, const LC3_INT32 SampRate, const LC3_INT32 nbLostCmpt, const LC3_INT32 overlap, const LC3_FLOAT *stabFac, LC3_FLOAT harmonicBuf[MAX_PITCH], LC3_FLOAT synthHist[M], LC3_INT32* fract, LC3_INT16* seed, LC3_FLOAT* gain_c, LC3_FLOAT* alpha, LC3_FLOAT* synth - ,LC3_UINT8 plc_fadeout_type + , LC3_UINT8 plc_fadeout_type ,LC3_FLOAT* alpha_type_2_table ); void* balloc(void* base, size_t* base_size, size_t size); -LC3_FLOAT type_2_fadeout(LC3_INT32 nbLostFramesInRow, LC3_INT32 frame_dms); +LC3_FLOAT type_2_fadeout(LC3_INT32 nbLostFramesInRow, LC3PLUS_FrameDuration frame_dms); #endif diff --git a/lib_lc3plus/imdct.c b/lib_lc3plus/imdct.c index b8fc4e7985..b99ef8c206 100644 --- a/lib_lc3plus/imdct.c +++ b/lib_lc3plus/imdct.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/lc3plus.c b/lib_lc3plus/lc3plus.c index d208cfb151..473b9b7c02 100644 --- a/lib_lc3plus/lc3plus.c +++ b/lib_lc3plus/lc3plus.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -65,17 +65,20 @@ static int lc3plus_plc_mode_supported(LC3PLUS_PlcMode plc_mode) return 0; } -static int lc3plus_frame_size_supported(float frame_ms) +static int lc3plus_frame_size_supported(LC3PLUS_FrameDuration frame_dms) { - switch ((int)(ceil(frame_ms * 10))) + switch (frame_dms) { - case 25: /* fallthru */ - case 50: /* fallthru */ - case 75: /* fallthru */ - case 100: return 1; - default: break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: /* fallthru */ +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: /* fallthru */ + case LC3PLUS_FRAME_DURATION_5MS: /* fallthru */ + case LC3PLUS_FRAME_DURATION_7p5MS: /* fallthru */ + case LC3PLUS_FRAME_DURATION_10MS: + return 1; + default: return 0; } - return 0; } static int null_in_list(void **list, int n) @@ -151,6 +154,9 @@ int lc3plus_enc_get_real_bitrate(const LC3PLUS_Enc *encoder) { int ch = 0, totalBytes = 0; int bitrate; +#ifdef CR9_C_ADD_1p25MS + int frame_ns; +#endif RETURN_IF(encoder == NULL, 0); RETURN_IF(!encoder->lc3_br_set, LC3PLUS_BITRATE_UNSET_ERROR); @@ -158,9 +164,12 @@ int lc3plus_enc_get_real_bitrate(const LC3PLUS_Enc *encoder) { totalBytes += encoder->channel_setup[ch]->targetBytes; } - +#ifdef CR9_C_ADD_1p25MS + frame_ns = (int)(1250L * encoder->frame_dms); + bitrate = (int) ((totalBytes*8L) * 1000000L + (frame_ns - 1) ) / (frame_ns); +#else bitrate = (totalBytes * 80000.0 + encoder->frame_dms - 1) / encoder->frame_dms; - +#endif if (encoder->fs_in == 44100) { int rem = bitrate % 480; @@ -187,13 +196,18 @@ int lc3plus_enc_get_delay(const LC3PLUS_Enc *encoder) return encoder->frame_length - 2 * encoder->la_zeroes; } -LC3PLUS_Error lc3plus_enc_set_frame_dms(LC3PLUS_Enc *encoder, int frame_dms) +LC3PLUS_Error lc3plus_enc_set_frame_dms(LC3PLUS_Enc *encoder, LC3PLUS_FrameDuration frame_dms) { RETURN_IF(encoder == NULL, LC3PLUS_NULL_ERROR); - RETURN_IF(!lc3plus_frame_size_supported(frame_dms / 10.0), LC3PLUS_FRAMEMS_ERROR); + RETURN_IF(!lc3plus_frame_size_supported(frame_dms), LC3PLUS_FRAMEMS_ERROR); RETURN_IF(encoder->lc3_br_set, LC3PLUS_BITRATE_SET_ERROR); +#ifdef CR9_C_ADD_1p25MS + RETURN_IF(encoder->fs == 8000 && frame_dms == LC3PLUS_FRAME_DURATION_1p25MS, LC3PLUS_SAMPLERATE_ERROR); +#endif + encoder->frame_dms = frame_dms; - encoder->frame_ms = frame_dms / 10.0; + encoder->frame_ms = frame_dms; + set_enc_frame_params(encoder); return LC3PLUS_OK; } @@ -279,14 +293,18 @@ int lc3plus_dec_get_scratch_size(const LC3PLUS_Dec *decoder) return 0; } -LC3PLUS_Error lc3plus_dec_set_frame_dms(LC3PLUS_Dec *decoder, int frame_dms) +LC3PLUS_Error lc3plus_dec_set_frame_dms(LC3PLUS_Dec *decoder, LC3PLUS_FrameDuration frame_dms) { RETURN_IF(decoder == NULL, LC3PLUS_NULL_ERROR); - RETURN_IF(!lc3plus_frame_size_supported(frame_dms / 10.0), LC3PLUS_FRAMEMS_ERROR); - RETURN_IF(decoder->plcMeth == 2 && frame_dms != 100, LC3PLUS_FRAMEMS_ERROR); - + RETURN_IF(!lc3plus_frame_size_supported(frame_dms), LC3PLUS_FRAMEMS_ERROR); + RETURN_IF(decoder->plcMeth == 2 && frame_dms != LC3PLUS_FRAME_DURATION_10MS, LC3PLUS_FRAMEMS_ERROR); +#ifdef CR9_C_ADD_1p25MS + RETURN_IF(decoder->fs == 8000 && frame_dms == LC3PLUS_FRAME_DURATION_1p25MS, LC3PLUS_SAMPLERATE_ERROR); +#endif + decoder->frame_dms = frame_dms; - decoder->frame_ms = frame_dms / 10.0; + decoder->frame_ms = frame_dms; + set_dec_frame_params(decoder); return LC3PLUS_OK; } @@ -403,7 +421,7 @@ LC3PLUS_Error lc3plus_enc_set_ep_mode(LC3PLUS_Enc *encoder, LC3PLUS_EpMode epmod error = encoder->lc3_br_set ? update_enc_bitrate(encoder, encoder->bitrate) : LC3PLUS_OK; if (error != LC3PLUS_OK) { - encoder->epmode = oldEpmode; // preserve old epmode in case of failure + encoder->epmode = oldEpmode; /* preserve old epmode in case of failure */ } return error; } diff --git a/lib_lc3plus/lc3plus.h b/lib_lc3plus/lc3plus.h index 063714467d..802c8b83d3 100644 --- a/lib_lc3plus/lc3plus.h +++ b/lib_lc3plus/lc3plus.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -23,9 +23,11 @@ #ifndef LC3PLUS_H #define LC3PLUS_H -#ifndef _MSC_VER #include "options.h" #include "wmc_auto.h" +#define CR9_C_ADD_1p25MS /* including defines.h redefines NPRM_RESQ */ + +#ifndef _MSC_VER #include #else typedef unsigned char uint8_t; @@ -37,7 +39,7 @@ typedef __int32 int32_t; #define LC3PLUS_VERSION_INT(major, minor, micro) (((major) << 16) | ((minor) << 8) | (micro)) /*! Version number to ensure header and binary are matching. */ -#define LC3PLUS_VERSION LC3PLUS_VERSION_INT(1, 7, 4) +#define LC3PLUS_VERSION LC3PLUS_VERSION_INT(1, 8, 0) /*! Maximum number of supported channels. The actual binary might support * less, use lc3plus_channels_supported() to check. */ @@ -57,6 +59,20 @@ typedef __int32 int32_t; #define LC3PLUS_DEC_MAX_SIZE 87528 /*! Error codes returned by functions. */ + +typedef enum { + LC3PLUS_FRAME_DURATION_UNDEFINED = 0, /* Invalid */ +#ifdef CR9_C_ADD_1p25MS + LC3PLUS_FRAME_DURATION_1p25MS = 1, /* 1.25 ms */ +#endif + LC3PLUS_FRAME_DURATION_2p5MS = 2, /* 2.5 ms */ + LC3PLUS_FRAME_DURATION_5MS = 4, /* 5 ms */ + LC3PLUS_FRAME_DURATION_7p5MS = 6, /* 7.5 ms */ + LC3PLUS_FRAME_DURATION_10MS = 8, /* 10 ms */ +} LC3PLUS_FrameDuration; + + + typedef enum { LC3PLUS_PLC_ADVANCED = 1 /*!< Enhanced concealment method */ @@ -286,7 +302,7 @@ int lc3plus_enc_get_delay(const LC3PLUS_Enc *encoder); * \param[in] frame_ms Frame length in ms. * \return LC3PLUS_OK on success or appropriate error code. */ -LC3PLUS_Error lc3plus_enc_set_frame_dms(LC3PLUS_Enc *encoder, int frame_ms); +LC3PLUS_Error lc3plus_enc_set_frame_dms(LC3PLUS_Enc *encoder, LC3PLUS_FrameDuration frame_ms); /*! Set encoder Low-frequency effect moded. deactivates LTPF, TNS, NF @@ -448,7 +464,7 @@ int lc3plus_dec_get_delay(const LC3PLUS_Dec* decoder); * \param[in] frame_ms Frame length in ms. * \return LC3PLUS_OK on success or appropriate error code. */ -LC3PLUS_Error lc3plus_dec_set_frame_dms(LC3PLUS_Dec *decoder, int frame_ms); +LC3PLUS_Error lc3plus_dec_set_frame_dms(LC3PLUS_Dec *decoder, LC3PLUS_FrameDuration frame_ms); /*! Free memory allocated within LC3plus decoder struct. diff --git a/lib_lc3plus/lc3plus_fft.c b/lib_lc3plus/lc3plus_fft.c index 75f8787e25..b2c6b66dd0 100644 --- a/lib_lc3plus/lc3plus_fft.c +++ b/lib_lc3plus/lc3plus_fft.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -18,13 +18,13 @@ void fft_init(Fft* fft, int length) { HANDLE_IIS_FFT handle = NULL; IIS_FFT_ERROR error = 0; - assert(length % 2 == 0); fft->length = length; error = LC3_IIS_CFFT_Create(&handle, length, IIS_FFT_FWD); assert(error == IIS_FFT_NO_ERROR); + (void) error; fft->handle = handle; } @@ -38,6 +38,8 @@ void fft_free(Fft* fft) assert(error == IIS_FFT_NO_ERROR); memset(fft, 0, sizeof(*fft)); } + + (void) error; } void real_fft_free(Fft* fft) @@ -50,6 +52,8 @@ void real_fft_free(Fft* fft) assert(error == IIS_FFT_NO_ERROR); memset(fft, 0, sizeof(*fft)); } + + (void) error; } void real_fft_init(Fft* fft, LC3_INT32 length, HANDLE_IIS_FFT *handle) @@ -62,6 +66,8 @@ void real_fft_init(Fft* fft, LC3_INT32 length, HANDLE_IIS_FFT *handle) error = LC3_IIS_RFFT_Create(handle, length, IIS_FFT_FWD); assert(error == IIS_FFT_NO_ERROR); fft->handle = *handle; + + (void) error; } @@ -76,6 +82,8 @@ void real_ifft_init(Fft* fft, LC3_INT32 length, HANDLE_IIS_FFT *handle) assert(error == IIS_FFT_NO_ERROR); fft->handle = *handle; + + (void) error; } void fft_apply(Fft* fft, const Complex* input, Complex* output) @@ -84,6 +92,8 @@ void fft_apply(Fft* fft, const Complex* input, Complex* output) error = LC3_IIS_FFT_Apply_CFFT(fft->handle, input, output); assert(error == IIS_FFT_NO_ERROR); + + (void) error; } @@ -96,5 +106,7 @@ void real_fft_apply(Fft* fft, const LC3_FLOAT* input, LC3_FLOAT* output) error = LC3_IIS_FFT_Apply_RFFT(fft->handle, input, output); assert(error == IIS_FFT_NO_ERROR); + + (void) error; } diff --git a/lib_lc3plus/license.h b/lib_lc3plus/license.h index 0ea743b404..2c5e6ceae4 100644 --- a/lib_lc3plus/license.h +++ b/lib_lc3plus/license.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -13,7 +13,7 @@ static const char *const LICENSE = "*******************************************************************************\n" - "* ETSI TS 103 634 V1.5.1 *\n" + "* ETSI TS 103 634 V1.6.1 *\n" "* Low Complexity Communication Codec Plus (LC3plus) *\n" "* Floating Point Software V%i.%i.%iETSI, " __DATE__ " *\n" "* Copyright licence is solely granted through ETSI Intellectual Property *\n" diff --git a/lib_lc3plus/ltpf_coder.c b/lib_lc3plus/ltpf_coder.c index 3457b6ba66..c4a8ee5764 100644 --- a/lib_lc3plus/ltpf_coder.c +++ b/lib_lc3plus/ltpf_coder.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -32,10 +32,17 @@ LC3_INT searchMaxIndice(LC3_FLOAT* in, LC3_INT len) return max_i; } -void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC3_INT pitch_ol, LC3_FLOAT pitch_ol_norm_corr, LC3_INT frame_dms, +void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC3_INT pitch_ol, LC3_FLOAT pitch_ol_norm_corr, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT* mem_old_x, LC3_INT memLen, LC3_FLOAT* mem_norm_corr_past, LC3_INT* mem_on, LC3_FLOAT* mem_pitch, LC3_INT* param, LC3_FLOAT* mem_norm_corr_past_past, LC3_INT* bits - , LC3_INT16 hrmode + , LC3_INT16 hrmode +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + ,LC3_INT16* Tx_ltpf +#else + ,LC3_INT16 Tx_ltpf +#endif +#endif ) { LC3_FLOAT buffer[LTPF_MEMIN_LEN + LEN_12K8 + 1 + (LEN_12K8 >> 2)], sum = 0, cor_up[(MAX_PITCH_12K8 - MIN_PITCH_12K8) / 2] = {0}, *x; @@ -45,8 +52,13 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC delta_up = 0, delta_down = 0, pitch_index = 0, gain = 0, acflen = 0; LC3_FLOAT cor_tmp, cor_int_tmp, norm_corr = 0, cor[MAX_LEN_NR], cor_int[MAX_LEN_NR], sum1 = 0, sum2 = 0, sum3 = 0; LC3_FLOAT pitch = 0; + LC3_FLOAT normCorrTh = 0.0f; +#if defined (CR9_C_ADD_1p25MS) + LC3_INT16 activation_due_to_past_corr, activation_due_to_stable_pitch, activation; +#endif + + UNUSED(mem_norm_corr_past_past); - LC3_FLOAT normCorrTh = 0.0f; if (hrmode) { normCorrTh = 0.4; } else { @@ -55,11 +67,28 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC /* Signal Buffer */ N = xLen - 1; + acflen = N; + + if (frame_dms == LC3PLUS_FRAME_DURATION_5MS) + { + acflen = 2 * N; + } + if (frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) + { + acflen = 4 * N; + } +#ifdef CR9_C_ADD_1p25MS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + acflen = 8 * N; + } +#endif + x = &buffer[memLen]; - move_float(buffer, mem_old_x, memLen); - move_float(x, xin, xLen); - move_float(mem_old_x, &buffer[N], xLen + memLen - N); + move_float( buffer, mem_old_x, memLen ); + move_float( x, xin, xLen ); + move_float( mem_old_x, &buffer[N], xLen + memLen - N ); ltpf_active = 0; norm_corr = 0; @@ -74,17 +103,29 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC t0_max = pitch_ol + pitch_search_delta; t0_min = MAX(t0_min, MIN_PITCH_12K8); t0_max = MIN(t0_max, MAX_PITCH_12K8); + + /* Cross-Correlation Bounds */ + t_min = t0_min - pitch_search_L_interpol1; + t_max = t0_max + pitch_search_L_interpol1; + +#ifndef FIX_LTPF_PITCH_MEM_LEN acflen = N; - - if (frame_dms == 25) + + if ( frame_dms == LC3PLUS_FRAME_DURATION_2p5MS ) { acflen = 2 * N; x = x - N; } - - /* Cross-Correlation Bounds */ - t_min = t0_min - pitch_search_L_interpol1; - t_max = t0_max + pitch_search_L_interpol1; +#ifdef CR9_C_ADD_1p25MS + if ( frame_dms == LC3PLUS_FRAME_DURATION_1p25MS ) + { + acflen = 4 * N; + x = x - 80; + } +#endif +#else + x = x - (memLen - LTPF_MEMIN_LEN); +#endif /* Compute norm */ sum1 = sum2 = 0; @@ -92,13 +133,13 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC sum1 += x[j] * x[j]; sum2 += x[j - t_min] * x[j - t_min]; } - + /* Do first iteration outside of loop */ sum = mac_loop(x, &x[-t_min], acflen); sum3 = LC3_SQRT(sum1 * sum2) + 1.00e-05; norm_corr = sum / sum3; - + norm_corr = MAX(0, norm_corr); cor[0] = norm_corr; @@ -111,7 +152,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC sum3 = LC3_SQRT(sum1 * sum2) + 1.00e-05; norm_corr = sum / sum3; - + norm_corr = MAX(0, norm_corr); cor[i - t_min] = norm_corr; @@ -129,7 +170,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC pitch_fr = 0; } else { j = 0; - + for (i = 0; i < pitch_search_upsamp * (t_max - t_min) + 1; i = i + pitch_search_upsamp) { cor_up[i] = cor[j]; j++; @@ -155,7 +196,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC } else { delta_down = pitch_search_upsamp - step; } - + j = 0; for (i = midpoint - delta_down - 1; i <= midpoint + delta_up; i = i + step) { cor[j] = cor_int[i]; @@ -178,7 +219,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC (pitch_int < RES2_PITCH_12K8 && pitch_int >= RES4_PITCH_12K8 && (pitch_fr == 0 || pitch_fr == 2)) || (pitch_int < RES4_PITCH_12K8 && pitch_int >= MIN_PITCH_12K8 && (pitch_fr == 0 || pitch_fr == 1 || pitch_fr == 2 || pitch_fr == 3))); - + if (pitch_int < RES4_PITCH_12K8) { pitch_index = pitch_int * 4 + pitch_fr - (MIN_PITCH_12K8 * 4); } else if (pitch_int < RES2_PITCH_12K8) { @@ -189,7 +230,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC assert(pitch_index >= 0 && pitch_index < 512); pitch = (LC3_FLOAT) pitch_int + (LC3_FLOAT) pitch_fr / 4.0; - + /* Normalized Correlation */ sum1 = sum2 = sum3 = 0; @@ -203,7 +244,7 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC x[n - pitch_int] * enc_inter_filter[pitch_fr][1] + x[n - pitch_int - 1] * enc_inter_filter[pitch_fr][2] + x[n - pitch_int - 2] * enc_inter_filter[pitch_fr][3]; - + sum1 += cor_tmp * cor_int_tmp; sum2 += cor_tmp * cor_tmp; sum3 += cor_int_tmp * cor_int_tmp; @@ -218,19 +259,42 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC norm_corr = 0; } - if (ltpf_enable == 1) { + if (ltpf_enable == 1) + { /* Decision if ltpf active */ - if ((*mem_on == 0 && (frame_dms == 100 || *mem_norm_corr_past_past > 0.94) && *mem_norm_corr_past > 0.94 && +#if defined (CR9_C_ADD_1p25MS) + activation_due_to_past_corr = mem_norm_corr_past[1] > 0.94; + activation_due_to_stable_pitch = 1; + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + activation_due_to_past_corr &= (mem_norm_corr_past[2] > 0.94); + activation_due_to_past_corr &= (mem_norm_corr_past[3] > 0.94); + activation_due_to_past_corr &= (mem_norm_corr_past[4] > 0.94); + + activation_due_to_stable_pitch = LC3_FMAX(pitch, *mem_pitch) * 0.7f < LC3_FMIN(pitch, *mem_pitch); + } + activation = activation_due_to_past_corr && activation_due_to_stable_pitch; + if ((*mem_on == 0 && (frame_dms == LC3PLUS_FRAME_DURATION_10MS || activation) && mem_norm_corr_past[0] > 0.94 && + norm_corr > 0.94) || + ((*mem_on == 1 && norm_corr > 0.9) && activation_due_to_stable_pitch) || + (*mem_on == 1 && LC3_FABS(pitch - *mem_pitch) < 2 && (norm_corr - mem_norm_corr_past[0]) > -0.1 && + norm_corr > 0.84)) + { + ltpf_active = 1; + } +#else + if ((*mem_on == 0 && (frame_dms == LC3PLUS_FRAME_DURATION_10MS || *mem_norm_corr_past_past > 0.94) && *mem_norm_corr_past > 0.94 && norm_corr > 0.94) || (*mem_on == 1 && norm_corr > 0.9) || (*mem_on == 1 && LC3_FABS(pitch - *mem_pitch) < 2 && (norm_corr - *mem_norm_corr_past) > -0.1 && - norm_corr > 0.84)) { + norm_corr > 0.84)) + { ltpf_active = 1; } +#endif } gain = 4; - } else { gain = 0; norm_corr = pitch_ol_norm_corr; @@ -248,8 +312,60 @@ void process_ltpf_coder_fl(LC3_FLOAT* xin, LC3_INT xLen, LC3_INT ltpf_enable, LC *bits = 1; } - if (frame_dms < 100) { + +#ifdef CR9_C_ADD_1p25MS +#ifdef NEW_SIGNALLING_SCHEME_1p25 + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + LC3_INT32 tmp = MIN(*Tx_ltpf, 1); /* 0 == phaseA, 1==PhaseB ) */ + /* tmp [0 or 1} will points to 2 ltp bits in any case */ + if ( param[0] == 0 && tmp != 0 ) + { + /* pitch correlation dropped from high(ltp active || ltpf_active) to low (no ltp info at all ), + we select to NOT transmit the remaining phaseB lag info for potential use in a next possible PLC frame */ + *Tx_ltpf = 0; /* kill the lag transmission state from the encoder side */ + /* tmp stays 0 or 1 */ + } + + /* 00 (ltp=0, ltpf=0, no Phase 2b), + 010 (ltp=1, ltpf=0, phaseA, 7b), + 011 (ltp=1, ltpf=0, phaseB , 7b), lowered lag res for PLC + 10x (ltp=1, ltpf=1) phaseA 6b) + 11x (ltp=1, ltpf=1) phaseB 7b) + */ + + if (param[0] != 0) + { + if (param[1] == 0) + { /* ltp active, PLC usage case LTPF inactive 01[PhaseA]=010=2, or 01[phaseB]=011=3 + path ltp active and ltpf inactive */ + tmp = (0x02 | tmp); /* phase Info in LSB b0, LTPFactive in b1, tmp becomes 2 or 3 */ + } + else + { /*param[1] != 0*/ /* ltp active, ltpf active */ + assert(param[2] >= 0 && param[2] <= 511); + tmp = (0x04 | (tmp << 1)); /* LTPF in b2, phase b1, always zero in b0 , tmp becomes 4 or 6 */ + /* 100=4 for phase A */ + /* 110=6 for phase B */ + } + } + assert(tmp >= 0 && tmp < 8); + *bits = lrsns_ltp_bits[tmp]; /* one of { 2,2, 7,7 , 6,6, 7,7} */ + /* tmp=idx is 0,1 2,3 4,5, 6,7 */ + + assert(*bits == 2 || *bits == 6 || *bits == 7); + } +#endif +#else + assert(*bits == 1 || *bits == 11); +#endif + + if (frame_dms < LC3PLUS_FRAME_DURATION_10MS) { +#if defined (CR9_C_ADD_1p25MS) + move_float(&mem_norm_corr_past[1], &mem_norm_corr_past[0], LEN_MEM_NORMCORR-1); +#else *mem_norm_corr_past_past = *mem_norm_corr_past; +#endif } *mem_norm_corr_past = norm_corr; diff --git a/lib_lc3plus/ltpf_decoder.c b/lib_lc3plus/ltpf_decoder.c index 3ccd452bcb..f627e3fe89 100644 --- a/lib_lc3plus/ltpf_decoder.c +++ b/lib_lc3plus/ltpf_decoder.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,30 +11,126 @@ #include "wmc_auto.h" #include "functions.h" +#ifdef CR9_C_ADD_1p25MS +static LC3_INT16 get_continuation (LC3_INT32 fading_case, LC3PLUS_FrameDuration frame_dms, LC3_INT32 pos, LC3_INT32 total) +{ + if ( frame_dms != LC3PLUS_FRAME_DURATION_1p25MS ) + { + return 0; + } + else + { + if ( pos == total ) + { + return 0; + } + else + { + return fading_case; + } + } +} +#endif + +#ifdef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR +#ifdef LTPF_PRINT_PARAMS +static bool compare_normalized_corrs(LC3_FLOAT const *sig, LC3_INT32 len, LC3_INT32 pitch_int, LC3_INT32 mem_pitch_int, LC3_FLOAT *corr, LC3_FLOAT *corr_prev) +#else +static bool compare_normalized_corrs(LC3_FLOAT const *sig, LC3_INT32 len, LC3_INT32 pitch_int, LC3_INT32 mem_pitch_int) +#endif +{ + LC3_FLOAT norm_0, norm_t, norm_t_prev, xcorr, xcorr_prev; + norm_0 = norm_t = norm_t_prev = xcorr = xcorr_prev = 0; + + for ( int i=0; i < len; i++ ) + { + norm_0 += sig[i] * sig[i]; + norm_t += sig[i - pitch_int] * sig[i - pitch_int]; + xcorr += sig[i] * sig[i - pitch_int]; + + norm_t_prev += sig[i - mem_pitch_int] * sig[i - mem_pitch_int]; + xcorr_prev += sig[i] * sig[i - mem_pitch_int]; + } + + xcorr = MIN( 1.f, MAX( 0.f, xcorr / ( LC3_SQRT( norm_0 * norm_t ) + 1.00e-05f ) ) ); + xcorr_prev = MIN( 1.f, MAX( 0.f, xcorr_prev / ( LC3_SQRT( norm_0 * norm_t_prev ) + 1.00e-05f ) ) ); + +#ifdef LTPF_PRINT_PARAMS + *corr = xcorr; + *corr_prev = xcorr_prev; +#endif + + if ( xcorr_prev - xcorr > 5e-4 ) + { + return true; + } + + return false; +} +#endif + void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT fs, LC3_FLOAT* mem_old_x, LC3_FLOAT* mem_old_y, LC3_INT* mem_pitch_int, LC3_INT* mem_pitch_fr, LC3_FLOAT* mem_gain, LC3_INT* mem_beta_idx, LC3_INT bfi, - LC3_INT* param, LC3_INT* mem_param, LC3_INT conf_beta_idx, LC3_FLOAT conf_beta, LC3_INT concealMethod, - LC3_FLOAT damping - , LC3_INT *mem_ltpf_active - , LC3_FLOAT *rel_pitch_change, LC3_INT hrmode, LC3_INT frame_dms -) + LC3_INT* param, LC3_INT* mem_param, LC3_INT conf_beta_idx, LC3_FLOAT *conf_beta, LC3_INT concealMethod, LC3_FLOAT damping + , LC3_INT *mem_ltpf_active + , LC3_FLOAT *rel_pitch_change, LC3_INT hrmode, LC3PLUS_FrameDuration frame_dms +#ifdef CR9_C_ADD_1p25MS + , LC3_INT16* mem_continuation, LC3_INT32* mem_param_prev, LC3_INT16* mem_pitch_int_prev, + LC3_INT16* mem_pitch_fr_prev, LC3_INT32* mem_beta_idx_prev, LC3_FLOAT* mem_gain_prev, + LC3_INT16* pitch_stability_counter, + LC3_FLOAT* gain_step, + LC3_FLOAT conf_beta_max +#endif + ) { LC3_INT i, j, n, N, L_past_x, N4, N34, pitch_int, pitch_fr, p1, p2, L_past_y, inter_len, tilt_len = 0, - tilt_len_r, inter_len_r, old_x_len, old_y_len; + tilt_len_r, inter_len_r, old_x_len, old_y_len, fading_case, N4_D, N4_S; LC3_FLOAT conf_alpha, gain, a1[12], a2[12], b1[11], b2[11], buf_x[4 * MAX_LEN], buf_y[4 * MAX_LEN], buf_z[4 * MAX_LEN], pitch, sum1, sum2; LC3_FLOAT *p_x, *p_y, *p_y2, *p_x_init, *p_y_init, *p_a1, *p_b1, *p_a2, *p_b2, fade_fac, current_fade_fac_up, current_fade_fac_down; - LC3_FLOAT pitch_fl_c_old, pitch_delta; + LC3_FLOAT pitch_delta; const LC3_FLOAT *inter_filter[4], *tilt_filter[4]; +#ifdef LTPF_ADAPTIVE_GAIN + bool ltpf_active = false; + bool ltpf_active_prev = false; + bool pitch_changed = false; + bool pitch_was_stable = false; +#endif + +#ifdef LTPF_ADAPTIVE_GAIN + LC3_INT32 original_param[3]; + LC3_INT32 original_pitch_int, original_pitch_fr; + LC3_FLOAT original_gain; +#ifdef LTPF_PRINT_PARAMS + static int local_frame = 0; +#ifdef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + LC3_FLOAT xcorr, xcorr_prev; +#endif +#endif +#endif + +#ifdef FIX_LTPF_DEC_FLFX_MISMATCH + UNUSED(mem_ltpf_active); + fading_case = 0; +#endif + tilt_len = 0; + conf_alpha = 0.85; + p1 = 0; + p2 = 0; + #ifdef WMOPS push_wmops("process_ltpf_decoder_fl"); #endif - pitch_fl_c_old = (LC3_FLOAT) *mem_pitch_int + (LC3_FLOAT)*mem_pitch_fr / 4.0; - conf_alpha = 0.85; - + +#ifdef FIX_LTPF_1p25 + if (param[2] == -1) { + param[1] = 0; + param[0] = 0; + } +#endif if (bfi != 1) { /* Decode pitch */ if (param[0] == 1) { @@ -55,7 +151,7 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f pitch = ((LC3_FLOAT)pitch_int + (LC3_FLOAT)pitch_fr / 4.0) * (LC3_FLOAT)fs / 12800.0; pitch = round(pitch * 4.0) / 4.0; pitch_int = floor(pitch); - pitch_fr = (LC3_INT)((pitch - (LC3_FLOAT)pitch_int) * 4.0); + pitch_fr = (LC3_INT)((pitch - (LC3_FLOAT)pitch_int) * 4.0); } else { pitch_int = 0; pitch_fr = 0; @@ -67,12 +163,16 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f } if (param[1] == 1) { - gain = conf_beta; + gain = *conf_beta; } else { gain = 0; } } - else if (concealMethod > 0) { + else if ((concealMethod > 0) +#ifdef CR9_C_ADD_1p25MS + && (*mem_continuation == 0) +#endif + ) { if (conf_beta_idx < 0) { if (mem_param[1] && *mem_beta_idx >= 0) { @@ -92,9 +192,40 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f pitch_fr = *mem_pitch_fr; gain = (LC3_FLOAT) *mem_gain * damping; } + +#ifdef CR9_C_ADD_1p25MS + if (*mem_continuation) + { +#ifdef LTPF_ADAPTIVE_GAIN + /* Save original LTPF parameters */ + move_int(original_param, param, 3); + original_pitch_int = pitch_int; + original_pitch_fr = pitch_fr; + original_gain = gain; +#endif - if ((conf_beta <= 0) && (*mem_ltpf_active == 0)) - { + fading_case = *mem_continuation; + move_int(param, mem_param, 3); + pitch_int = *mem_pitch_int; + pitch_fr = *mem_pitch_fr; + gain = *mem_gain; +#ifdef FIX_LTPF_1p25 + conf_beta_idx = *mem_beta_idx; +#endif + + move_int(mem_param, mem_param_prev, 3); + *mem_pitch_int = *mem_pitch_int_prev; + *mem_pitch_fr = *mem_pitch_fr_prev; + *mem_gain = *mem_gain_prev; +#ifdef FIX_LTPF_1p25 + *mem_beta_idx = *mem_beta_idx_prev; +#endif + + } +#endif + + if ( fs <= 48000 ) + { if (fs == 8000 || fs == 16000) { tilt_len = 4 - 2; } @@ -108,16 +239,196 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f tilt_len = 12 - 2; } + inter_len = MAX(fs, 16000) / 8000; + + /* Init buffers */ + N = xLen; + old_x_len = tilt_len; + old_y_len = ceil(228.0 * fs / 12800.0) + inter_len; + L_past_x = old_x_len; + + move_float(buf_x, mem_old_x, old_x_len); + move_float(&buf_x[old_x_len], x, xLen); + L_past_y = old_y_len; + move_float(buf_y, mem_old_y, old_y_len); + move_float(&buf_y[old_y_len], x, xLen); + } +#ifdef LTPF_ADAPTIVE_GAIN + if ( frame_dms == LC3PLUS_FRAME_DURATION_1p25MS ) + { + conf_alpha = 0.98; + + /* Control variables */ + ltpf_active = param[1]; + ltpf_active_prev = mem_param[1]; + pitch_changed = !( ( pitch_int == *mem_pitch_int ) && ( pitch_fr == *mem_pitch_fr ) ); + pitch_was_stable = ( ( *pitch_stability_counter >= LTPF_PITCH_STABILITY_THRESHOLD ) ); + +#ifdef CR9_C_ADD_1p25MS + if ( *mem_continuation == 0 ) + { +#endif +#ifdef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + if ( !pitch_was_stable && pitch_changed && pitch_int != 0 && *mem_pitch_int != 0 ) + { +#ifdef LTPF_PRINT_PARAMS + pitch_was_stable = compare_normalized_corrs(buf_y + L_past_y, xLen, pitch_int, *mem_pitch_int, &xcorr, &xcorr_prev); +#else + pitch_was_stable = compare_normalized_corrs(buf_y + L_past_y, xLen, pitch_int, *mem_pitch_int); +#endif + } +#endif + + if ( ltpf_active && !pitch_changed ) + { + /* increment gain and increment pitch stability counter */ + gain = pitch_was_stable ? MIN( conf_beta_max, MAX( gain, *mem_gain ) + *gain_step ) : MAX( gain, *mem_gain ); + ( *pitch_stability_counter )++; + } + else if ( ltpf_active && pitch_changed && !pitch_was_stable ) + { + /* decrement gain and reset pitch stability counter */ + gain = ( *mem_gain > gain ) ? MAX( gain, *mem_gain - ( conf_beta_max / LTPF_ADAPTIVE_GAIN_RATE ) ) : gain; + *pitch_stability_counter = 0; + } + else if ( !ltpf_active && !pitch_was_stable && ltpf_active_prev && pitch_changed ) + { + /* decrement gain, use previous pitch and reset pitch stability counter */ + gain = *mem_gain - *gain_step; + +#ifdef FIX_LTPF_1p25 + if (*conf_beta > 0 && (gain - *conf_beta) > -(20.f/(1<<15))) +#else + if ( (gain - *conf_beta) > -(20.f/(1<<15))) +#endif + { + move_int( param, mem_param, 3 ); + pitch_int = *mem_pitch_int; + pitch_fr = *mem_pitch_fr; + } + else + { + gain = 0.f; + } + *pitch_stability_counter = 0; + } + else if ( ( ltpf_active && pitch_changed && pitch_was_stable ) || ( !ltpf_active && pitch_was_stable ) || ( !ltpf_active && !pitch_was_stable && ltpf_active_prev && !pitch_changed ) ) + { + /* use previous pitch and gain and reset pitch stability counter */ + move_int( param, mem_param, 3 ); + pitch_int = *mem_pitch_int; + pitch_fr = *mem_pitch_fr; + gain = *mem_gain; + *pitch_stability_counter = 0; + } +#ifdef CR9_C_ADD_1p25MS /* This is added here for the future when adaptive gain will be enabled for other frame sizes. */ + } + else if ( *mem_continuation != 0 && original_param[1] == 1 +#ifdef FIX_PLC_CONFORM_ISSUES + && bfi == 0 +#endif + ) + { + /* Code enters this block if LTPF is reenabled when adaptive gain is being applied. */ + /* In this case, use new LTPF parameters but with a smaller gain than in the prev frame.*/ + fading_case = 0; + *mem_continuation = 0; + + move_int( mem_param_prev, mem_param, 3 ); + *mem_pitch_int_prev = *mem_pitch_int; + *mem_pitch_fr_prev = *mem_pitch_fr; + *mem_gain_prev = *mem_gain; + + move_int( mem_param, param, 3 ); + *mem_pitch_int = pitch_int; + *mem_pitch_fr = pitch_fr; + *mem_gain = gain; + + move_int( param, original_param, 3 ); + pitch_int = original_pitch_int; + pitch_fr = original_pitch_fr; + gain = original_gain; + + /* if prev gain > curr gain, then decrease gain slowly. */ + gain = ( *mem_gain > gain) ? MAX( gain, *mem_gain - ( conf_beta_max / LTPF_ADAPTIVE_GAIN_RATE ) ) : gain; + + *pitch_stability_counter = 0; + } +#endif + } +#endif /* LTPF_ADAPTIVE_GAIN */ + if ( mem_param[1] && *mem_beta_idx < 0 ) + { + mem_param[1] = 0; + } + + if ( param[1] && conf_beta_idx < 0 ) + { + param[1] = 0; + } + + if ( fs <= 48000 ) + { +#ifdef FIX_LTPF_MEM_CONTINUATION +#ifdef FIX_LTPF_DEC_FLFX_MISMATCH + if ( (( param[1] == 0 ) && ( mem_param[1] == 0 )) || fading_case == 1 ) +#else + if ( (( *conf_beta <= 0 ) && ( *mem_ltpf_active == 0 )) || fading_case == 1 ) +#endif +#else + if ( ( *conf_beta <= 0 ) && ( *mem_ltpf_active == 0 ) ) +#endif + { +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + if ( fs == 8000 || fs == 16000 ) + { + tilt_len = 4 - 2; + } + else if ( fs == 24000 ) + { + tilt_len = 6 - 2; + } + else if ( fs == 32000 ) + { + tilt_len = 8 - 2; + } + else if ( fs == 44100 || fs == 48000 ) + { + tilt_len = 12 - 2; + } N = xLen; old_x_len = tilt_len; inter_len = MAX(fs, 16000) / 8000; old_y_len = ceilf((LC3_FLOAT)228.0 * fs / 12800.0) + inter_len; /* 228.0 needed to make use of ceil */ +#endif + +#ifdef FIX_LTPF_MEM_CONTINUATION +#ifdef CR9_C_ADD_1p25MS + fading_case = 1; + N4 = fs * 0.0025; + N4_S = 0; + N4_D = N4; + if ( frame_dms == LC3PLUS_FRAME_DURATION_1p25MS ) + { + N4_D = 2 * N4; + if ( *mem_continuation ) + { + N4_S = N4; + } + } + *mem_continuation = get_continuation( fading_case, frame_dms, ( N4 + N4_S ), N4_D ); +#endif +#endif move_float(mem_old_y, &mem_old_y[N], (old_y_len - N)); move_float(&mem_old_y[old_y_len - N], x, N); move_float(mem_old_x, &x[N - old_x_len], old_x_len); +#ifdef FIX_LTPF_DEC_FLFX_MISMATCH + mem_param[1] = 0; +#else *mem_ltpf_active = 0; +#endif } else { @@ -133,8 +444,10 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f tilt_filter[1] = conf_tilt_filter_16[1]; tilt_filter[2] = conf_tilt_filter_16[2]; tilt_filter[3] = conf_tilt_filter_16[3]; - tilt_len = 4 - 2; - tilt_len_r = 3; +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + tilt_len = 4 - 2; +#endif + tilt_len_r = 3; } else if (fs == 24000) { inter_filter[0] = conf_inter_filter_24[0]; inter_filter[1] = conf_inter_filter_24[1]; @@ -146,8 +459,10 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f tilt_filter[1] = conf_tilt_filter_24[1]; tilt_filter[2] = conf_tilt_filter_24[2]; tilt_filter[3] = conf_tilt_filter_24[3]; - tilt_len = 6 - 2; - tilt_len_r = 5; +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + tilt_len = 6 - 2; +#endif + tilt_len_r = 5; } else if (fs == 32000) { inter_filter[0] = conf_inter_filter_32[0]; inter_filter[1] = conf_inter_filter_32[1]; @@ -159,8 +474,10 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f tilt_filter[1] = conf_tilt_filter_32[1]; tilt_filter[2] = conf_tilt_filter_32[2]; tilt_filter[3] = conf_tilt_filter_32[3]; - tilt_len = 8 - 2; - tilt_len_r = 7; +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + tilt_len = 8 - 2; +#endif + tilt_len_r = 7; } else if (fs == 44100 || fs == 48000) { inter_filter[0] = conf_inter_filter_48[0]; inter_filter[1] = conf_inter_filter_48[1]; @@ -172,11 +489,14 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f tilt_filter[1] = conf_tilt_filter_48[1]; tilt_filter[2] = conf_tilt_filter_48[2]; tilt_filter[3] = conf_tilt_filter_48[3]; - tilt_len = 12 - 2; - tilt_len_r = 11; +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + tilt_len = 12 - 2; +#endif + tilt_len_r = 11; } - inter_len = MAX(fs, 16000) / 8000; +#ifndef LTPF_ADAPTIVE_GAIN_WITH_NORM_CORR + inter_len = MAX( fs, 16000 ) / 8000; /* Init buffers */ N = xLen; @@ -187,9 +507,13 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f move_float(&buf_x[old_x_len], x, xLen); L_past_y = old_y_len; move_float(buf_y, mem_old_y, old_y_len); - zero_float(&buf_y[old_y_len], xLen); + zero_float( &buf_y[old_y_len], xLen ); +#endif N4 = fs * 0.0025; +#ifdef CR9_C_ADD_1p25MS + N4 = MIN(N4, xLen); +#endif N34 = N - N4; /* Init filter parameters */ @@ -198,6 +522,7 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f a1[i] = *mem_gain * inter_filter[*mem_pitch_fr][i]; } + assert( *mem_beta_idx >= 0 ); for (i = 0; i < tilt_len_r; i++) { b1[i] = conf_alpha * (*mem_gain) * tilt_filter[*mem_beta_idx][i]; } @@ -206,6 +531,7 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f } if (param[1] == 1) { + assert( conf_beta_idx >= 0 ); for (i = 0; i < tilt_len_r; i++) { b2[i] = conf_alpha * gain * tilt_filter[conf_beta_idx][i]; } @@ -216,17 +542,53 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f p2 = pitch_int; } + +#ifdef CR9_C_ADD_1p25MS + if (*mem_continuation == 0) { +#endif + /* check fading case */ + if (mem_param[1] == 0 && param[1] == 0) { + fading_case = 1; + } else if (mem_param[1] == 1 && param[1] == 0) { + fading_case = 3; + } else if (mem_param[1] == 0 && param[1] == 1) { + fading_case = 2; + } else if (*mem_pitch_int == pitch_int && *mem_pitch_fr == pitch_fr) { + fading_case = 4; + } else { + fading_case = 5; + } +#ifdef CR9_C_ADD_1p25MS + } +#endif + + N4_D = N4; + UNUSED(N4_D); UNUSED(N4_S); + N4_S = 0; +#ifdef CR9_C_ADD_1p25MS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + N4_D = 2*N4; + if (*mem_continuation) + { + N4_S = N4; + } + } +#endif /* First quarter of the current frame: cross-fading */ - fade_fac = 1. / (LC3_FLOAT) N4; - current_fade_fac_up = 0.f; - current_fade_fac_down = 1.f; + fade_fac = 1. / (LC3_FLOAT) N4_D; + current_fade_fac_up = N4_S*fade_fac; + current_fade_fac_down = 1.f - current_fade_fac_up; (void) p_x; (void) p_y; (void) p_a1; (void) p_b1; - if (mem_param[1] == 0 && param[1] == 0) { + if (fading_case == 1) { + memmove(&buf_y[L_past_y], &buf_x[L_past_x], sizeof(LC3_FLOAT) * N4); - - } else if (mem_param[1] == 1 && param[1] == 0) { +#if defined( CR9_C_ADD_1p25MS ) && defined( FIX_LTPF_MEM_CONTINUATION ) + *mem_continuation = get_continuation(fading_case, frame_dms, (N4+N4_S), N4_D); +#endif + } else if (fading_case == 3) { for (n = 0; n < N4; n++) { sum1 = 0; sum2 = 0; @@ -246,8 +608,10 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f current_fade_fac_down * sum2; current_fade_fac_down -= fade_fac; } - - } else if (mem_param[1] == 0 && param[1] == 1) { +#ifdef CR9_C_ADD_1p25MS + *mem_continuation = get_continuation(fading_case, frame_dms, (n+N4_S), N4_D); +#endif + } else if (fading_case == 2) { for (n = 0; n < N4; n++) { sum1 = 0; sum2 = 0; @@ -266,7 +630,10 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f buf_y[L_past_y + n] = buf_x[L_past_x + n] - current_fade_fac_up * sum1 + current_fade_fac_up * sum2; current_fade_fac_up += fade_fac; } - } else if (*mem_pitch_int == pitch_int && *mem_pitch_fr == pitch_fr) { +#ifdef CR9_C_ADD_1p25MS + *mem_continuation = get_continuation(fading_case, frame_dms, (n+N4_S), N4_D); +#endif + } else if (fading_case == 4) { for (n = 0; n < N4; n++) { sum1 = 0; sum2 = 0; @@ -284,6 +651,9 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f buf_y[L_past_y + n] = buf_x[L_past_x + n] - sum1 + sum2; } +#ifdef CR9_C_ADD_1p25MS + *mem_continuation = get_continuation(fading_case, frame_dms, (n+N4_S), N4_D); +#endif } else { p_x_init = &buf_x[L_past_x]; p_y_init = &buf_y[L_past_y - p1 + inter_len - 1]; @@ -346,6 +716,9 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f p_y_init++; p_y2++; } +#ifdef CR9_C_ADD_1p25MS + *mem_continuation = get_continuation(fading_case, frame_dms, (n+N4_S), N4_D); +#endif } /* Second quarter of the current frame */ @@ -389,19 +762,37 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f move_float(mem_old_x, &buf_x[N], old_x_len); move_float(mem_old_y, &buf_y[N], old_y_len); - *mem_ltpf_active = (conf_beta > 0); +#ifndef FIX_LTPF_DEC_FLFX_MISMATCH + *mem_ltpf_active = ( *conf_beta > 0 ); +#endif + } + } + + + if ( bfi == 0 && hrmode == 1 && ( frame_dms == LC3PLUS_FRAME_DURATION_5MS || frame_dms == LC3PLUS_FRAME_DURATION_2p5MS ) ) + { + pitch_delta = LC3_FABS( (LC3_FLOAT) *mem_pitch_int + (LC3_FLOAT) ( *mem_pitch_fr / 4.0 ) - (LC3_FLOAT) pitch_int - (LC3_FLOAT) ( pitch_fr / 4.0 ) ); + *rel_pitch_change = pitch_delta / MAX( (LC3_FLOAT) *mem_pitch_int + (LC3_FLOAT) ( *mem_pitch_fr / 4.0 ), 1 ); } +#ifdef CR9_C_ADD_1p25MS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + move_int(mem_param_prev, mem_param, 3); + *mem_pitch_int_prev = *mem_pitch_int; + *mem_pitch_fr_prev = *mem_pitch_fr; + *mem_gain_prev = *mem_gain; + *mem_beta_idx_prev = *mem_beta_idx; + } +#endif + /* Update ltpf param memory */ move_int(mem_param, param, 3); *mem_pitch_int = pitch_int; *mem_pitch_fr = pitch_fr; *mem_gain = gain; *mem_beta_idx = conf_beta_idx; - if (bfi == 0 && hrmode == 1 && (frame_dms == 50 || frame_dms == 25)){ - pitch_delta = LC3_FABS(pitch_fl_c_old - (LC3_FLOAT)pitch_int - (LC3_FLOAT)(pitch_fr / 4.0)); - *rel_pitch_change = pitch_delta / MAX(pitch_fl_c_old, 1); - } + #ifdef WMOPS pop_wmops(); diff --git a/lib_lc3plus/mdct.c b/lib_lc3plus/mdct.c index 4698afd4bf..94e6cada27 100644 --- a/lib_lc3plus/mdct.c +++ b/lib_lc3plus/mdct.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,9 +11,9 @@ #include "wmc_auto.h" #include "functions.h" -static const LC3_FLOAT* mdct_window(LC3_INT length, LC3_INT frame_dms, LC3_INT hrmode) +static const LC3_FLOAT* mdct_window(LC3_INT length, LC3PLUS_FrameDuration frame_dms, LC3_INT hrmode) { - if (frame_dms == 100) { + if (frame_dms == LC3PLUS_FRAME_DURATION_10MS) { switch (length) { case 80: return MDCT_WINS_10ms[hrmode][0]; @@ -31,7 +31,7 @@ static const LC3_FLOAT* mdct_window(LC3_INT length, LC3_INT frame_dms, LC3_INT h return NULL; } } - else if (frame_dms == 75) { + else if (frame_dms == LC3PLUS_FRAME_DURATION_7p5MS) { switch (length) { case 60: return MDCT_WINS_7_5ms[hrmode][0]; @@ -49,7 +49,7 @@ static const LC3_FLOAT* mdct_window(LC3_INT length, LC3_INT frame_dms, LC3_INT h return NULL; } } - else if (frame_dms == 50) { + else if (frame_dms == LC3PLUS_FRAME_DURATION_5MS) { switch (length) { case 40: return MDCT_WINS_5ms[hrmode][0]; @@ -67,7 +67,7 @@ static const LC3_FLOAT* mdct_window(LC3_INT length, LC3_INT frame_dms, LC3_INT h return NULL; } } - else if (frame_dms == 25) { + else if (frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) { switch (length) { case 20: return MDCT_WINS_2_5ms[hrmode][0]; @@ -85,31 +85,56 @@ static const LC3_FLOAT* mdct_window(LC3_INT length, LC3_INT frame_dms, LC3_INT h return NULL; } } +#ifdef CR9_C_ADD_1p25MS + else if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + switch (length) { + case 10: + return MDCT_WINS_1_25ms[hrmode][0]; + case 20: + return MDCT_WINS_1_25ms[hrmode][1]; + case 30: + return MDCT_WINS_1_25ms[hrmode][2]; + case 40: + return MDCT_WINS_1_25ms[hrmode][3]; + case 60: + return MDCT_WINS_1_25ms[hrmode][4]; + default: + return NULL; + } + } +#endif return NULL; } -void mdct_init(Mdct* mdct, LC3_INT length, LC3_INT frame_dms, LC3_INT fs_idx, LC3_INT hrmode) +void mdct_init(Mdct* mdct, LC3_INT length, LC3PLUS_FrameDuration frame_dms, LC3_INT fs_idx, LC3_INT hrmode) { - if (frame_dms == 100) { - mdct->leading_zeros = MDCT_la_zeroes[fs_idx]; - } - else if (frame_dms == 75) { - mdct->leading_zeros = MDCT_la_zeroes_7_5ms[fs_idx]; - } - else if (frame_dms == 50) { - mdct->leading_zeros = MDCT_la_zeroes_5ms[fs_idx]; - } - else if (frame_dms == 25) { + switch (frame_dms) + { +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + mdct->leading_zeros = MDCT_la_zeroes_1_25ms[fs_idx]; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: mdct->leading_zeros = MDCT_la_zeroes_2_5ms[fs_idx]; - } - else { + break; + case LC3PLUS_FRAME_DURATION_5MS: + mdct->leading_zeros = MDCT_la_zeroes_5ms[fs_idx]; + break; + case LC3PLUS_FRAME_DURATION_7p5MS: + mdct->leading_zeros = MDCT_la_zeroes_7_5ms[fs_idx]; + break; + case LC3PLUS_FRAME_DURATION_10MS: + mdct->leading_zeros = MDCT_la_zeroes[fs_idx]; + break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: assert(!"invalid frame_ms"); } mdct->length = length; mdct->mem_length = length - mdct->leading_zeros; mdct->window = mdct_window(length, frame_dms, hrmode); - mdct->mem = calloc(sizeof(*mdct->mem), mdct->mem_length); + mdct->mem = calloc(mdct->mem_length, sizeof(*mdct->mem)); dct4_init(&mdct->dct, length); } diff --git a/lib_lc3plus/mdct_shaping.c b/lib_lc3plus/mdct_shaping.c index 296a8cd454..6c1b0f5dbb 100644 --- a/lib_lc3plus/mdct_shaping.c +++ b/lib_lc3plus/mdct_shaping.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/near_nyquist_detector.c b/lib_lc3plus/near_nyquist_detector.c index 5d81392b21..56f66937d2 100644 --- a/lib_lc3plus/near_nyquist_detector.c +++ b/lib_lc3plus/near_nyquist_detector.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -12,7 +12,8 @@ #include "functions.h" void processNearNyquistdetector_fl(LC3_INT16* near_nyquist_flag, const LC3_INT fs_idx, const LC3_INT near_nyquist_index, - const LC3_INT bands_number, const LC3_FLOAT* ener , const LC3_INT16 frame_dms, const LC3_INT16 hrmode) + const LC3_INT bands_number, const LC3_FLOAT* ener + , const LC3PLUS_FrameDuration frame_dms, const LC3_INT16 hrmode) { *near_nyquist_flag = 0; if (hrmode == 0){ @@ -36,36 +37,43 @@ void processNearNyquistdetector_fl(LC3_INT16* near_nyquist_flag, const LC3_INT f } } } - else // hrmode == 1 + else /* hrmode == 1 */ { - // inverse spectral flatness = mean(energy) ./ 2^(mean(log2(energy))); + /* inverse spectral flatness = mean(energy) ./ 2^(mean(log2(energy))); */ LC3_INT32 td_thresh, i = 0; LC3_FLOAT mean_ener = 0, mean_ener_log2 = 0, inv_flatness = 0; switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + assert(0); + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: td_thresh = TD_HR_thresh_2_5ms; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: td_thresh = TD_HR_thresh_5ms; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: td_thresh = TD_HR_thresh_7_5ms; break; - default: /* 100 */ + case LC3PLUS_FRAME_DURATION_10MS: td_thresh = TD_HR_thresh_10ms; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } - // calculate arithmetic mean + /* calculate arithmetic mean */ for (i = 0; i < bands_number; i++) { mean_ener += ener[i]; } mean_ener = mean_ener / bands_number; - // calculate geometric mean + /* calculate geometric mean */ for (i = 0; i < bands_number; i++) { if (ener[i] != 0) { diff --git a/lib_lc3plus/noise_factor.c b/lib_lc3plus/noise_factor.c index 72b2602251..80adaf399c 100644 --- a/lib_lc3plus/noise_factor.c +++ b/lib_lc3plus/noise_factor.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,7 +11,7 @@ #include "wmc_auto.h" #include "functions.h" -void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gg, LC3_INT BW_cutoff_idx, LC3_INT frame_dms, +void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gg, LC3_INT BW_cutoff_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT target_bytes ) { @@ -21,22 +21,30 @@ void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3 switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: nTransWidth = 1; startOffset = 6; break; - case 50: +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: + nTransWidth = 1; + startOffset = 6; + break; + case LC3PLUS_FRAME_DURATION_5MS: nTransWidth = 1; startOffset = 12; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: nTransWidth = 2; startOffset = 18; break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: nTransWidth = 3; startOffset = 24; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } for (k = startOffset - nTransWidth; k < startOffset + nTransWidth; k++) @@ -91,7 +99,7 @@ void processNoiseFactor_fl(LC3_INT* fac_ns_idx, LC3_FLOAT x[], LC3_INT xq[], LC3 - if (kZeroLines > 1 && target_bytes <= 20 && frame_dms == 100) { + if (kZeroLines > 1 && target_bytes <= 20 && frame_dms == LC3PLUS_FRAME_DURATION_10MS) { j = 0, k = 0, nsf1 = 0, nsf2 = 0, sumZeroLines = 0; diff --git a/lib_lc3plus/noise_filling.c b/lib_lc3plus/noise_filling.c index f929338d0c..77eca88c81 100644 --- a/lib_lc3plus/noise_filling.c +++ b/lib_lc3plus/noise_filling.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,30 +11,38 @@ #include "wmc_auto.h" #include "functions.h" -void processNoiseFilling_fl(LC3_FLOAT xq[], LC3_INT nfseed, LC3_INT fac_ns_idx, LC3_INT bw_stopband, LC3_INT frame_dms, LC3_FLOAT fac_ns_pc, LC3_INT spec_inv_idx) +void processNoiseFilling_fl(LC3_FLOAT xq[], LC3_INT nfseed, LC3_INT fac_ns_idx, LC3_INT bw_stopband, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT fac_ns_pc, LC3_INT spec_inv_idx) { LC3_INT zeroLines[MAX_LEN]; - LC3_INT nTransWidth, startOffset, j, k, nzeros = 0, kZeroLines; + LC3_INT nTransWidth = 0, startOffset = 0, j, k, nzeros = 0, kZeroLines; LC3_FLOAT fac_ns = 0; switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: nTransWidth = 1; startOffset = 6; break; - case 50: +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: + nTransWidth = 1; + startOffset = 6; + break; + case LC3PLUS_FRAME_DURATION_5MS: nTransWidth = 1; startOffset = 12; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: nTransWidth = 2; startOffset = 18; break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: nTransWidth = 3; startOffset = 24; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } fac_ns = (8.0 - fac_ns_idx) / 16.0; diff --git a/lib_lc3plus/olpa.c b/lib_lc3plus/olpa.c index 976f0a177e..aacbdd34fb 100644 --- a/lib_lc3plus/olpa.c +++ b/lib_lc3plus/olpa.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -46,8 +46,8 @@ LC3_INT searchMaxIndice(LC3_FLOAT* in, LC3_INT len) } void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4, LC3_INT* mem_old_T0, - LC3_INT* pitch_flag, - LC3_INT* T0_out, LC3_FLOAT* normcorr_out, LC3_INT len, LC3_INT frame_dms) + LC3_INT* pitch_flag, + LC3_INT* T0_out, LC3_FLOAT* normcorr_out, LC3_INT len, LC3PLUS_FrameDuration frame_dms) { LC3_FLOAT norm_corr = 0, sum = 0, sum0 = 0, sum1 = 0, sum2 = 0, norm_corr2 = 0, *s6k4; LC3_FLOAT buf[LEN_6K4 + MAX_PITCH_6K4 + MAX_LEN], R0[RANGE_PITCH_6K4]; /* constant length */ @@ -56,15 +56,21 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 len2 = len / 2; switch(frame_dms) { - case 50: + case LC3PLUS_FRAME_DURATION_5MS: delta = len / 2; acflen = len2 * 2; break; - case 25: + case LC3PLUS_FRAME_DURATION_2p5MS: delta = 3*(len /2); acflen = len2*4; break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + delta = 7 * (len / 2); + acflen = len2 * 8; + break; +#endif default: delta = 0; @@ -84,6 +90,7 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 move_float(&buf[mem_in_len], R0, len2); move_float(buf, mem_s6k4, mem_in_len); move_float(mem_s6k4, &buf[len2], mem_in_len); + for (i = MIN_PITCH_6K4; i <= MAX_PITCH_6K4; i++) { sum = mac_loop(s6k4, &s6k4[-i], acflen); R0[i - MIN_PITCH_6K4] = sum; @@ -137,10 +144,10 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 switch(frame_dms) { - case 50: + case LC3PLUS_FRAME_DURATION_5MS: if (*pitch_flag == 1) { - *mem_old_T0 = T0; + *mem_old_T0 = T0; *pitch_flag = 0; } else @@ -149,10 +156,10 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 } break; - case 25: + case LC3PLUS_FRAME_DURATION_2p5MS: if (*pitch_flag == 3) { - *mem_old_T0 = T0; + *mem_old_T0 = T0; *pitch_flag = 0; } else @@ -160,6 +167,19 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 *pitch_flag += 1; } break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + if (*pitch_flag == 7) + { + *mem_old_T0 = T0; + *pitch_flag = 0; + } + else + { + *pitch_flag += 1; + } + break; +#endif default: *mem_old_T0 = T0; @@ -167,5 +187,4 @@ void processOlpa_fl(LC3_FLOAT* s_12k8, LC3_FLOAT* mem_s12k8, LC3_FLOAT* mem_s6k4 *T0_out = T0 * 2.0; *normcorr_out = norm_corr; - } diff --git a/lib_lc3plus/pc_apply.c b/lib_lc3plus/pc_apply.c index a7113f1bc7..9ae4a724da 100644 --- a/lib_lc3plus/pc_apply.c +++ b/lib_lc3plus/pc_apply.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/pc_classify.c b/lib_lc3plus/pc_classify.c index adb8a59713..628cb9d12d 100644 --- a/lib_lc3plus/pc_classify.c +++ b/lib_lc3plus/pc_classify.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -13,14 +13,14 @@ LC3_FLOAT pc_peak_detector(LC3_FLOAT *q_d_prev, LC3_INT32 yLen); -void processPcClassify_fl(LC3_INT32 pitch_present, LC3_INT32 frame_dms, LC3_FLOAT *q_d_prev, LC3_FLOAT *q_old_res, LC3_INT32 yLen, LC3_INT32 spec_inv_idx, LC3_FLOAT stab_fac, LC3_INT32 *bfi) +void processPcClassify_fl(LC3_INT32 pitch_present, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *q_d_prev, LC3_FLOAT *q_old_res, LC3_INT32 yLen, LC3_INT32 spec_inv_idx, LC3_FLOAT stab_fac, LC3_INT32 *bfi) { LC3_INT32 maxPitchBin, xover, i; LC3_FLOAT part_nrg, full_nrg; part_nrg = 0; full_nrg = 0; - if (spec_inv_idx < (4 * frame_dms / 10)) + if (spec_inv_idx < (4 * frame_dms * 1.25)) { if (stab_fac < 0.5) { @@ -28,7 +28,7 @@ void processPcClassify_fl(LC3_INT32 pitch_present, LC3_INT32 frame_dms, LC3_FLOA } else if (pitch_present == 1) { maxPitchBin = 8; - if (frame_dms == 50) + if (frame_dms == LC3PLUS_FRAME_DURATION_5MS) { maxPitchBin = 4; } diff --git a/lib_lc3plus/pc_main.c b/lib_lc3plus/pc_main.c index 5ef1676d48..28c065f6db 100644 --- a/lib_lc3plus/pc_main.c +++ b/lib_lc3plus/pc_main.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/pc_update.c b/lib_lc3plus/pc_update.c index b24b2ea62a..35166e1780 100644 --- a/lib_lc3plus/pc_update.c +++ b/lib_lc3plus/pc_update.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/per_band_energy.c b/lib_lc3plus/per_band_energy.c index 413b6cfd84..fe1e31f66f 100644 --- a/lib_lc3plus/per_band_energy.c +++ b/lib_lc3plus/per_band_energy.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,7 +11,7 @@ #include "wmc_auto.h" #include "functions.h" -void processPerBandEnergy_fl(LC3_INT bands_number, const LC3_INT* acc_coeff_per_band, LC3_INT16 hrmode, LC3_INT16 frame_dms, LC3_FLOAT* d2, LC3_FLOAT* d) +void processPerBandEnergy_fl(LC3_INT bands_number, const LC3_INT* acc_coeff_per_band, LC3_INT16 hrmode, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT* d2, LC3_FLOAT* d) { LC3_INT i, j, start, stop, maxBwBin; LC3_FLOAT sum; @@ -30,15 +30,24 @@ void processPerBandEnergy_fl(LC3_INT bands_number, const LC3_INT* acc_coeff_per_ } switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + maxBwBin = maxBwBin >> 3; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: maxBwBin = maxBwBin >> 2; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: maxBwBin = maxBwBin >> 1; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: maxBwBin = (maxBwBin >> 2) * 3; break; + case LC3PLUS_FRAME_DURATION_10MS: + break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } for (i = 0; i < bands_number; i++) { diff --git a/lib_lc3plus/plc_classify.c b/lib_lc3plus/plc_classify.c index f1f1878d4b..6ab043abe8 100644 --- a/lib_lc3plus/plc_classify.c +++ b/lib_lc3plus/plc_classify.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -83,23 +83,58 @@ static void array_calculate(LC3_INT32 *array_tdc, LC3_INT32 *array_ns, int lengt *counter_ns = counter_loc_ns; } -static void plc_xcorr_lc(LC3_FLOAT *pcmbufHist, LC3_INT32 max_len_pcm_plc, LC3_INT32 pitch_int, LC3_INT32 framelength, LC3_INT32 frame_dms, LC3_INT32 fs, LC3_FLOAT *xcorr); +static void plc_xcorr_lc(LC3_FLOAT *pcmbufHist, LC3_INT32 max_len_pcm_plc, LC3_INT32 pitch_int, LC3_INT32 framelength, LC3PLUS_FrameDuration frame_dms, LC3_INT32 fs, LC3_FLOAT *xcorr); static void spectral_centroid_lc(LC3_FLOAT *gains, LC3_INT32 tilt, const LC3_INT *bands_offset, LC3_INT32 bands_number, LC3_INT32 framelength, LC3_INT32 fs, LC3_FLOAT *sc); void processPlcClassify_fl(LC3_INT plcMeth, LC3_INT *concealMethod, LC3_INT32 *nbLostCmpt, LC3_INT32 bfi, - LC3_FLOAT *xcorr, LC3_INT32 framelength, LC3_INT32 frame_dms, LC3_INT32 pitch_int, + LC3_FLOAT *xcorr, LC3_INT32 framelength, LC3PLUS_FrameDuration frame_dms, LC3_INT32 pitch_int, LC3_INT32 fs, const LC3_INT *band_offsets, LC3_INT32 bands_number, LC3_INT32 tilt, PlcAdvSetup *plcAd , LC3_INT32 hrmode ) { LC3_FLOAT sc, class; int fs_idx_tmp; +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + LC3_INT16 resetClassifierThreshold = 0; + LC3_INT16 updateStatistics = 0; +#endif if (plcAd) { *xcorr = 0; } +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + switch (frame_dms) + { +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + resetClassifierThreshold = 16; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: + resetClassifierThreshold = 8; + break; + case LC3PLUS_FRAME_DURATION_5MS: + resetClassifierThreshold = 4; + break; + case LC3PLUS_FRAME_DURATION_7p5MS: + resetClassifierThreshold = 3; + break; + case LC3PLUS_FRAME_DURATION_10MS: + resetClassifierThreshold = 2; + break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: assert(0); + } + + if (plcAd->numberOfGoodFrames > resetClassifierThreshold) + { + updateStatistics = 1; + } else { + updateStatistics = 0; + } +#endif + fs_idx_tmp = FS2FS_IDX(fs); /* Save statistics for 24 kHz, 48 kHz and 96 kHz */ if ((bfi == 1) || ((bfi >= 0) && (bfi <= 2) && ((fs_idx_tmp == 2) || (fs_idx_tmp == 4) || (fs_idx_tmp == 5)))) /* Partial Concealment PC(bfi==2) requires allowing value 2 to pass thru as well */ @@ -126,34 +161,70 @@ void processPlcClassify_fl(LC3_INT plcMeth, LC3_INT *concealMethod, LC3_INT32 *n if (class <= 0) { - if (frame_dms == 100 && hrmode == 0) + if (frame_dms == LC3PLUS_FRAME_DURATION_10MS && hrmode == 0) { *concealMethod = 2; /* PhaseEcu selected */ - array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); - array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (updateStatistics == 1) +#endif + { + array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + } } else { - array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); - array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (updateStatistics == 1) +#endif + { + array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + } } } else { - array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 1, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); - array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (updateStatistics == 1) +#endif + { + array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 1, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + array_insert_and_shift(plcAd->plc_longterm_advc_ns, 0, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + } } } else { *concealMethod = 4; /* Noise Substitution */ - array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); - array_insert_and_shift(plcAd->plc_longterm_advc_ns, 1, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (updateStatistics == 1) +#endif + { + array_insert_and_shift(plcAd->plc_longterm_advc_tdc, 0, plcAd->longterm_analysis_counter_max, &plcAd->overall_counter, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + array_insert_and_shift(plcAd->plc_longterm_advc_ns, 1, plcAd->longterm_analysis_counter_max, NULL, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + } } - array_calculate(plcAd->plc_longterm_advc_tdc, plcAd->plc_longterm_advc_ns, plcAd->longterm_analysis_counter_max_bytebuffer, &plcAd->longterm_counter_plcTdc, &plcAd->longterm_counter_plcNsAdv, plcAd->longterm_analysis_counter_max); - update_bit_and_byte_positions(plcAd->longterm_analysis_counter_max_bytebuffer, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (updateStatistics == 1) +#endif + { + array_calculate(plcAd->plc_longterm_advc_tdc, plcAd->plc_longterm_advc_ns, plcAd->longterm_analysis_counter_max_bytebuffer, &plcAd->longterm_counter_plcTdc, &plcAd->longterm_counter_plcNsAdv, plcAd->longterm_analysis_counter_max); + update_bit_and_byte_positions(plcAd->longterm_analysis_counter_max_bytebuffer, &plcAd->longterm_counter_byte_position, &plcAd->longterm_counter_bit_position); + } } } + +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + if (bfi == 1) + { + plcAd->numberOfGoodFrames = 0; + } else { + plcAd->numberOfGoodFrames = plcAd->numberOfGoodFrames + 1; + } +#endif } static void spectral_centroid_lc(LC3_FLOAT *gains, LC3_INT32 tilt, const LC3_INT *band_offsets, LC3_INT32 bands_number, LC3_INT32 framelength, LC3_INT32 fs, LC3_FLOAT *sc) @@ -234,7 +305,7 @@ static void spectral_centroid_lc(LC3_FLOAT *gains, LC3_INT32 tilt, const LC3_INT } static void plc_xcorr_lc(LC3_FLOAT *pcmbufHist, LC3_INT32 max_len_pcm_plc, LC3_INT32 pitch_int, LC3_INT32 framelength, - LC3_INT32 frame_dms, LC3_INT32 fs, LC3_FLOAT *xcorr) + LC3PLUS_FrameDuration frame_dms, LC3_INT32 fs, LC3_FLOAT *xcorr) { LC3_INT32 max_corr_len, pitch_min, corr_len, min_corr_len, pcm_max_corr_len, range1Start, range2Start, i; LC3_FLOAT norm_w, norm_w_t; @@ -252,7 +323,7 @@ static void plc_xcorr_lc(LC3_FLOAT *pcmbufHist, LC3_INT32 max_len_pcm_plc, LC3_I pcm_max_corr_len = max_len_pcm_plc - pitch_int; min_corr_len = 2 * pitch_min; /* at least 5 ms (=2*pitchmin*) corr length */ - max_corr_len = framelength*100/frame_dms; /* maximum 10 ms */ + max_corr_len = framelength*100/(frame_dms*1.25*10); /* maximum 10 ms */ max_corr_len = MIN( max_corr_len, pcm_max_corr_len ); corr_len = MIN( max_corr_len, pitch_int ); /* pitch_int is prefered, but maximum 10ms or left pcm buf size */ diff --git a/lib_lc3plus/plc_compute_stab_fac.c b/lib_lc3plus/plc_compute_stab_fac.c index 121c2f06a2..cd5a95441b 100644 --- a/lib_lc3plus/plc_compute_stab_fac.c +++ b/lib_lc3plus/plc_compute_stab_fac.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_damping_scrambling.c b/lib_lc3plus/plc_damping_scrambling.c index ac4526b4ec..edd430335d 100644 --- a/lib_lc3plus/plc_damping_scrambling.c +++ b/lib_lc3plus/plc_damping_scrambling.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -15,7 +15,7 @@ void processPlcDampingScramblingMain_fl(LC3_INT32 *ns_seed, LC3_INT32 *pc_seed, LC3_INT32 ns_nbLostCmpt_pc, LC3_INT32 ns_nbLostCmpt, LC3_FLOAT *stabFac, LC3_FLOAT *cum_fading_slow, LC3_FLOAT *cum_fading_fast, LC3_FLOAT *spec_prev, LC3_FLOAT *spec, LC3_INT32 spec_inv_idx, LC3_INT32 yLen, LC3_INT32 bfi, - LC3_INT32 frame_dms, LC3_INT32 concealMethod, LC3_INT32 pitch_present_bfi1, LC3_INT32 pitch_present_bfi2, + LC3PLUS_FrameDuration frame_dms, LC3_INT32 concealMethod, LC3_INT32 pitch_present_bfi1, LC3_INT32 pitch_present_bfi2, LC3_FLOAT *cum_fflcAtten , LC3_UINT8 plc_fadeout_type ) @@ -59,7 +59,7 @@ void processPlcDampingScramblingMain_fl(LC3_INT32 *ns_seed, } void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 nbLostCmpt, LC3_FLOAT *stabFac, LC3_INT32 processDampScramb, - LC3_FLOAT *cum_fflcAtten, LC3_INT32 pitch_present, LC3_INT32 frame_dms, LC3_FLOAT *cum_fading_slow, + LC3_FLOAT *cum_fflcAtten, LC3_INT32 pitch_present, LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *cum_fading_slow, LC3_FLOAT *cum_fading_fast, LC3_INT32 *seed, LC3_INT32 spec_inv_idx , LC3_UINT8 plc_fadeout_type ) @@ -74,18 +74,28 @@ void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 n switch (frame_dms) { - case 25: - slow = LC3_SQRT(LC3_SQRT(slow)); - fast = LC3_SQRT(LC3_SQRT(fast)); - break; - case 50: - slow = LC3_SQRT(slow); - fast = LC3_SQRT(fast); - break; - case 75: - slow = LC3_SQRT(LC3_SQRT(slow*slow*slow)); - fast = LC3_SQRT(LC3_SQRT(fast*fast*fast)); - break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + slow = LC3_SQRT(LC3_SQRT(LC3_SQRT(slow))); + fast = LC3_SQRT(LC3_SQRT(LC3_SQRT(fast))); + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: + slow = LC3_SQRT(LC3_SQRT(slow)); + fast = LC3_SQRT(LC3_SQRT(fast)); + break; + case LC3PLUS_FRAME_DURATION_5MS: + slow = LC3_SQRT(slow); + fast = LC3_SQRT(fast); + break; + case LC3PLUS_FRAME_DURATION_7p5MS: + slow = LC3_SQRT(LC3_SQRT(slow*slow*slow)); + fast = LC3_SQRT(LC3_SQRT(fast*fast*fast)); + break; + case LC3PLUS_FRAME_DURATION_10MS: + break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } if (plc_fadeout_type == 0) @@ -98,10 +108,10 @@ void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 n { if (plc_fadeout_type != 0) { - if (nbLostCmpt < (4 * (100.0 / (LC3_FLOAT)frame_dms))) { + if (nbLostCmpt < (4 * (100.0 / (LC3_FLOAT)(frame_dms*1.25*10)))) { cum_fading_slow_local = 1.0; } - else if (nbLostCmpt < (8 * (100.0 / (LC3_FLOAT)frame_dms))) { + else if (nbLostCmpt < (8 * (100.0 / (LC3_FLOAT)(frame_dms*1.25*10)))) { cum_fading_slow_local = 0.9; } else { @@ -118,23 +128,27 @@ void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 n if (spec_inv_idx == 0) { - if (nbLostCmpt * frame_dms > PLC_FADEOUT_IN_MS * 10) + if (nbLostCmpt * frame_dms * 1.25 * 10 > PLC_FADEOUT_IN_MS * 10) { fflcAtten = 0; *cum_fflcAtten = 0; } - else if (nbLostCmpt * frame_dms > 200) + else if (nbLostCmpt * frame_dms * 1.25 * 10 > 200) { switch (frame_dms) { - case 25: fflcAtten = PLC34_ATTEN_FAC_025; break; - case 50: fflcAtten = PLC34_ATTEN_FAC_050; break; - case 75: fflcAtten = PLC34_ATTEN_FAC_075; break; - case 100: fflcAtten = PLC34_ATTEN_FAC_100; break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: fflcAtten = PLC34_ATTEN_FAC_0125; break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: fflcAtten = PLC34_ATTEN_FAC_025; break; + case LC3PLUS_FRAME_DURATION_5MS: fflcAtten = PLC34_ATTEN_FAC_050; break; + case LC3PLUS_FRAME_DURATION_7p5MS: fflcAtten = PLC34_ATTEN_FAC_075; break; + case LC3PLUS_FRAME_DURATION_10MS: fflcAtten = PLC34_ATTEN_FAC_100; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } } - *cum_fflcAtten = *cum_fflcAtten * fflcAtten; cum_fading_slow_local = *cum_fading_slow * *cum_fflcAtten; cum_fading_fast_local = *cum_fading_fast * *cum_fflcAtten; @@ -145,10 +159,10 @@ void processPlcDampingScrambling_fl(LC3_FLOAT *spec, LC3_INT32 yLen, LC3_INT32 n plc_start_inFrames = 1; } else { - plc_start_inFrames = floor(PLC4_TRANSIT_START_IN_MS / (frame_dms / 10.0)); + plc_start_inFrames = floor(PLC4_TRANSIT_START_IN_MS / (frame_dms * 1.25)); } - plc_end_inFrames = floor(PLC4_TRANSIT_END_IN_MS / (frame_dms / 10.0)); + plc_end_inFrames = floor(PLC4_TRANSIT_END_IN_MS / (frame_dms * 1.25)); plc_duration_inFrames = plc_end_inFrames - plc_start_inFrames; if (nbLostCmpt <= plc_start_inFrames) diff --git a/lib_lc3plus/plc_main.c b/lib_lc3plus/plc_main.c index 01fe7b237c..507e78d128 100644 --- a/lib_lc3plus/plc_main.c +++ b/lib_lc3plus/plc_main.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -55,23 +55,29 @@ void processPlcMain_fl(LC3_FLOAT *q_d_fl_c, LC3_FLOAT *syntM_fl_c, LC3PLUS_Dec* { switch(decoder->frame_dms) { - case 25: - consecutiveLostThreshold = 16; +#ifdef FIX_ADDITONAL_1p25_ISSUES + case LC3PLUS_FRAME_DURATION_1p25MS: consecutiveLostThreshold = 32; + thresh_tdc_cnt = THRESH_025_DMS_TDC_CNT; + thresh_ns_cnt = THRESH_025_DMS_NS_CNT; + thresh_tdc_ns_cnt = THRESH_025_DMS_TDC_NS_CNT; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: consecutiveLostThreshold = 16; thresh_tdc_cnt = THRESH_025_DMS_TDC_CNT; thresh_ns_cnt = THRESH_025_DMS_NS_CNT; thresh_tdc_ns_cnt = THRESH_025_DMS_TDC_NS_CNT; break; - case 50: consecutiveLostThreshold = 8; + case LC3PLUS_FRAME_DURATION_5MS: consecutiveLostThreshold = 8; thresh_tdc_cnt = THRESH_050_DMS_TDC_CNT; thresh_ns_cnt = THRESH_050_DMS_NS_CNT; thresh_tdc_ns_cnt = THRESH_050_DMS_TDC_NS_CNT; break; - case 75: consecutiveLostThreshold = 6; + case LC3PLUS_FRAME_DURATION_7p5MS: consecutiveLostThreshold = 6; thresh_tdc_cnt = THRESH_075_DMS_TDC_CNT; thresh_ns_cnt = THRESH_075_DMS_NS_CNT; thresh_tdc_ns_cnt = THRESH_075_DMS_TDC_NS_CNT; break; - case 100: consecutiveLostThreshold = 4; + case LC3PLUS_FRAME_DURATION_10MS: consecutiveLostThreshold = 4; thresh_tdc_cnt = THRESH_100_DMS_TDC_CNT; thresh_ns_cnt = THRESH_100_DMS_NS_CNT; thresh_tdc_ns_cnt = THRESH_100_DMS_TDC_NS_CNT; @@ -98,7 +104,7 @@ void processPlcMain_fl(LC3_FLOAT *q_d_fl_c, LC3_FLOAT *syntM_fl_c, LC3PLUS_Dec* { PlcAdvSetup->plc_fadeout_type = 0; } - if (h_DecSetup->rel_pitch_change > REL_PITCH_THRESH && hrmode == 1 && (decoder->frame_dms == 50 || decoder->frame_dms == 25) ){ + if (h_DecSetup->rel_pitch_change > REL_PITCH_THRESH && hrmode == 1 && (decoder->frame_dms == LC3PLUS_FRAME_DURATION_5MS || decoder->frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) ){ PlcAdvSetup->plc_fadeout_type = 2; } else if ( h_DecSetup->concealMethod != 2 ) { @@ -120,9 +126,9 @@ void processPlcMain_fl(LC3_FLOAT *q_d_fl_c, LC3_FLOAT *syntM_fl_c, LC3PLUS_Dec* assert(decoder->fs_idx == floor(decoder->fs / 10000)); /* phaseECU supports only 10ms framing*/ - assert(PlcSetup->nbLostCmpt != 0 || decoder->frame_dms == 100); + assert(PlcSetup->nbLostCmpt != 0 || decoder->frame_dms == LC3PLUS_FRAME_DURATION_10MS); - if (decoder->frame_dms != 100) + if (decoder->frame_dms != LC3PLUS_FRAME_DURATION_10MS) { /* muting, if frame size changed during phaseECU concealment */ memset(q_d_fl_c, 0, sizeof(LC3_FLOAT) * decoder->frame_length); @@ -262,7 +268,7 @@ void processPlcMain_fl(LC3_FLOAT *q_d_fl_c, LC3_FLOAT *syntM_fl_c, LC3PLUS_Dec* } yLen = MIN(decoder->frame_length, MAX_PLC_LMEM); - if (PlcAdvSetup != NULL && (decoder->frame_dms == 100) && (hrmode == 0)) + if (PlcAdvSetup != NULL && (decoder->frame_dms == LC3PLUS_FRAME_DURATION_10MS) && (hrmode == 0)) { /* BASOP processPLCspec2shape_fx(prev_bfi, bfi, q_old_d_fx, yLen, plcAd->PhECU_oold_grp_shape_fx, plcAd->PhECU_old_grp_shape_fx);*/ plc_phEcu_processPLCspec2shape(prev_bfi_plc2, bfi, q_d_fl_c, yLen, diff --git a/lib_lc3plus/plc_noise_substitution.c b/lib_lc3plus/plc_noise_substitution.c index dc8e4d56fc..89ae0a5bbe 100644 --- a/lib_lc3plus/plc_noise_substitution.c +++ b/lib_lc3plus/plc_noise_substitution.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_f0_refine_first.c b/lib_lc3plus/plc_phecu_f0_refine_first.c index 7abb19813c..4752591bd6 100644 --- a/lib_lc3plus/plc_phecu_f0_refine_first.c +++ b/lib_lc3plus/plc_phecu_f0_refine_first.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_fec_hq.c b/lib_lc3plus/plc_phecu_fec_hq.c index bb7be94bf0..3d41a14057 100644 --- a/lib_lc3plus/plc_phecu_fec_hq.c +++ b/lib_lc3plus/plc_phecu_fec_hq.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_hq_ecu.c b/lib_lc3plus/plc_phecu_hq_ecu.c index 8b2b981582..24da271224 100644 --- a/lib_lc3plus/plc_phecu_hq_ecu.c +++ b/lib_lc3plus/plc_phecu_hq_ecu.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_lf_peak_analysis.c b/lib_lc3plus/plc_phecu_lf_peak_analysis.c index a53b6a4352..869efb9f3d 100644 --- a/lib_lc3plus/plc_phecu_lf_peak_analysis.c +++ b/lib_lc3plus/plc_phecu_lf_peak_analysis.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_rec_frame.c b/lib_lc3plus/plc_phecu_rec_frame.c index 412c5205ca..cb693fadbb 100644 --- a/lib_lc3plus/plc_phecu_rec_frame.c +++ b/lib_lc3plus/plc_phecu_rec_frame.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_setf0hz.c b/lib_lc3plus/plc_phecu_setf0hz.c index c67f96d410..2f9752e50e 100644 --- a/lib_lc3plus/plc_phecu_setf0hz.c +++ b/lib_lc3plus/plc_phecu_setf0hz.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_spec_ana.c b/lib_lc3plus/plc_phecu_spec_ana.c index c8b46309ba..6e63844ca1 100644 --- a/lib_lc3plus/plc_phecu_spec_ana.c +++ b/lib_lc3plus/plc_phecu_spec_ana.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -473,8 +473,8 @@ static void plc_phEcu_peak_locator_fxlike(const LC3_INT16 *inp, /* i: vector wit { tmp = sub(tmp, 1); } - num_pairs = tmp / 2; // shr(tmp, 1); - n_tail_values = sub(tmp, num_pairs * 2); // shl(num_pairs, 1)); + num_pairs = tmp / 2; + n_tail_values = sub(tmp, num_pairs * 2); /* filter preliminary sign changes into sensitivity filtered sign changes */ diff --git a/lib_lc3plus/plc_phecu_subst_spec.c b/lib_lc3plus/plc_phecu_subst_spec.c index 62a1d4434e..b65d791d12 100644 --- a/lib_lc3plus/plc_phecu_subst_spec.c +++ b/lib_lc3plus/plc_phecu_subst_spec.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -54,7 +54,7 @@ void plc_phEcu_subst_spec(LC3_INT32* plocs, LC3_INT32 n_plocs, LC3_FLOAT* f0est, corr_phase[i] = (LC3_FLOAT)2.0 * (LC3_FLOAT)M_PI_LC3PLUS * (f0est[i]/Lprot)*(LC3_FLOAT)t_adv; } - // EVOLVE PHASE ----------------- + /* EVOLVE PHASE -----------------*/ one_peak_flag_mask = -1; fs_idx = (LC3_INT16)LC3_FLOOR((LC3_FLOAT)Lprot / 160.0); /* aquire, fs_idx for 10 ms frame sizes */ diff --git a/lib_lc3plus/plc_phecu_tba_per_band_gain.c b/lib_lc3plus/plc_phecu_tba_per_band_gain.c index 8bf33b8853..3d5109f5c7 100644 --- a/lib_lc3plus/plc_phecu_tba_per_band_gain.c +++ b/lib_lc3plus/plc_phecu_tba_per_band_gain.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_tba_spect_Xavg.c b/lib_lc3plus/plc_phecu_tba_spect_Xavg.c index ca58ade71e..55e9f57793 100644 --- a/lib_lc3plus/plc_phecu_tba_spect_Xavg.c +++ b/lib_lc3plus/plc_phecu_tba_spect_Xavg.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_tba_trans_dect_gains.c b/lib_lc3plus/plc_phecu_tba_trans_dect_gains.c index 0ea04fe1af..ba41eaeaa5 100644 --- a/lib_lc3plus/plc_phecu_tba_trans_dect_gains.c +++ b/lib_lc3plus/plc_phecu_tba_trans_dect_gains.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_phecu_trans_burst_ana_sub.c b/lib_lc3plus/plc_phecu_trans_burst_ana_sub.c index 52bee02583..bafa6c5f57 100644 --- a/lib_lc3plus/plc_phecu_trans_burst_ana_sub.c +++ b/lib_lc3plus/plc_phecu_trans_burst_ana_sub.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_tdc.c b/lib_lc3plus/plc_tdc.c index d789a9df29..a48371853d 100644 --- a/lib_lc3plus/plc_tdc.c +++ b/lib_lc3plus/plc_tdc.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -19,7 +19,7 @@ static LC3_INT16 TDC_random_short(LC3_INT16 *seed); static LC3_FLOAT TDC_get_gainp(const LC3_FLOAT x[], const LC3_FLOAT y[], LC3_INT32 n); -static LC3_FLOAT TDC_get_gainc(const LC3_FLOAT x[], const LC3_FLOAT y[], const LC3_FLOAT *gain_p, const LC3_INT32 n, const LC3_INT32 frame_dms); +static LC3_FLOAT TDC_get_gainc(const LC3_FLOAT x[], const LC3_FLOAT y[], const LC3_FLOAT *gain_p, const LC3_INT32 n, const LC3PLUS_FrameDuration frame_dms); static void TDC_LPC_synthesis(const LC3_FLOAT a[], LC3_FLOAT x[], LC3_FLOAT y[], LC3_INT32 l, const LC3_FLOAT mem[], LC3_INT32 lpcorder, LC3_FLOAT *buf); static void TDC_LPC_residu(const LC3_FLOAT *a, LC3_FLOAT *x, LC3_FLOAT *y, LC3_INT32 l, LC3_INT32 lpcorder); static void TDC_highPassFiltering(const LC3_INT32 L_buffer, LC3_FLOAT exc2[], const LC3_FLOAT hp_filt[], const LC3_INT32 l_fir_fer); @@ -32,7 +32,23 @@ const LC3_FLOAT TDC_high_32_harm[TDC_L_FIR_HP] = {-0.0053f, -0.0037f, -0.0140f, static void TDC_levinson(LC3_FLOAT *acf, LC3_INT32 len, LC3_FLOAT *out); static void TDC_copyFLOAT(const LC3_FLOAT * X, LC3_FLOAT * Z, LC3_INT32 n); static LC3_FLOAT TDC_dotFLOAT(const LC3_FLOAT * X, const LC3_FLOAT * Y, LC3_INT32 n); -static LC3_FLOAT type_2_alpha_long(LC3_INT32 nbLostFramesInRow, LC3_INT32 frame_dms); +static LC3_FLOAT type_2_alpha_long(LC3_INT32 nbLostFramesInRow, LC3PLUS_FrameDuration frame_dms); +#ifdef FIX_TDC_BURST_ERROR +const LC3_INT32 beforeNextIncArray[5][8] = { + { 0, 0, 0, 0, 0, 0, 0, 1 }, + { 0, 0, 0, 1, 0, 0, 0, 1 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 0, 1, 1, 1, 0, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 } +}; +const LC3_INT32 nextIncArray[5][8] = { + { 1, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1, 0, 0, 0 }, + { 1, 0, 1, 0, 1, 0, 1, 0 }, + { 1, 0, 1, 1, 1, 0, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1 } +}; +#else const LC3_INT32 beforeNextIncArray[4][4] = {{0,0,0,1}, {0,1,0,1}, {0,1,1,1}, @@ -41,7 +57,7 @@ const LC3_INT32 nextIncArray[4][4] = {{1,0,0,0}, {1,0,1,0}, {1,0,1,1}, {1,1,1,1}}; - +#endif void processTdcApply_fl(const LC3_INT32 pitch_int, const LC3_FLOAT *preemphFac, const LC3_FLOAT* A, @@ -49,7 +65,7 @@ void processTdcApply_fl(const LC3_INT32 pitch_int, const LC3_FLOAT* pcmbufHist, const LC3_INT32 max_len_pcm_plc, const LC3_INT32 N, - const LC3_INT32 frame_dms, + const LC3PLUS_FrameDuration frame_dms, const LC3_INT32 SampRate, const LC3_INT32 nbLostFramesInRow, const LC3_INT32 overlap, @@ -62,7 +78,7 @@ void processTdcApply_fl(const LC3_INT32 pitch_int, LC3_FLOAT* alpha, LC3_FLOAT* synth , LC3_UINT8 plc_fadeout_type - , LC3_FLOAT* alpha_type_2_table + ,LC3_FLOAT* alpha_type_2_table ) { LC3_FLOAT step, step_n; @@ -87,9 +103,18 @@ void processTdcApply_fl(const LC3_INT32 pitch_int, /* len of synthesized signal */ len = N + overlap; - nbLostCmpt_loc = floor(frame_dms/100.0 * (nbLostFramesInRow - 1) + 1); - frame_dms_idx = frame_dms / 25 - 1; /* 0,1,2,3 */ - nbLostFramesInRow_mod = (nbLostFramesInRow - 1) % 4; + nbLostCmpt_loc = floor(frame_dms*1.25*10/100.0 * (nbLostFramesInRow - 1) + 1); + +#ifdef FIX_TDC_BURST_ERROR + if (frame_dms == 1) + frame_dms_idx = 0; + else + frame_dms_idx = frame_dms / 2; + nbLostFramesInRow_mod = ( nbLostFramesInRow - 1 ) % 8; +#else + frame_dms_idx = frame_dms*1.25*10 / 25 - 1; /* 0,1,2,3 */ + nbLostFramesInRow_mod = (nbLostFramesInRow - 1) % 4; +#endif beforeNextInc = beforeNextIncArray[frame_dms_idx][nbLostFramesInRow_mod]; nextInc = nextIncArray [frame_dms_idx][nbLostFramesInRow_mod]; @@ -245,10 +270,14 @@ void processTdcApply_fl(const LC3_INT32 pitch_int, { switch (frame_dms) { - case 25: *alpha *= PLC34_ATTEN_FAC_025; break; - case 50: *alpha *= PLC34_ATTEN_FAC_025; break; - case 75: *alpha *= PLC34_ATTEN_FAC_075; break; - case 100: *alpha *= PLC34_ATTEN_FAC_100; break; +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: *alpha *= PLC34_ATTEN_FAC_0125; break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: *alpha *= PLC34_ATTEN_FAC_025; break; + case LC3PLUS_FRAME_DURATION_5MS: *alpha *= PLC34_ATTEN_FAC_025; break; + case LC3PLUS_FRAME_DURATION_7p5MS: *alpha *= PLC34_ATTEN_FAC_075; break; + case LC3PLUS_FRAME_DURATION_10MS: *alpha *= PLC34_ATTEN_FAC_100; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: assert(0); } } @@ -469,14 +498,14 @@ void processTdcPreemphasis_fl(LC3_FLOAT *in, LC3_FLOAT *pre_emph_factor, LC3_INT } } -void processTdcLpcEstimation_fl(LC3_FLOAT *r, LC3_INT32 fs_idx, LC3_INT32 len, LC3_FLOAT *A, LC3_INT32 frame_dms) +void processTdcLpcEstimation_fl(LC3_FLOAT *r, LC3_INT32 fs_idx, LC3_INT32 len, LC3_FLOAT *A, LC3PLUS_FrameDuration frame_dms) { LC3_INT32 i; const LC3_FLOAT *lpc_array; lpc_array = plc_tdc_lpc_all[fs_idx]; - if (fs_idx == 0 && frame_dms == 25) + if (fs_idx == 0 && frame_dms == LC3PLUS_FRAME_DURATION_2p5MS) { lpc_array = plc_tdc_lpc_8_25ms; } @@ -532,7 +561,7 @@ static LC3_FLOAT TDC_get_gainc( /* output: gain of code const LC3_FLOAT y[], /* input : shifted input signal */ const LC3_FLOAT *gain_p, /* input : gain of pitch */ const LC3_INT32 n, /* input : vector length */ - const LC3_INT32 frame_dms /* input : frame length in dms */ + const LC3PLUS_FrameDuration frame_dms /* input : frame length in dms */ ) { LC3_FLOAT gain_c; @@ -546,7 +575,7 @@ static LC3_FLOAT TDC_get_gainc( /* output: gain of code gain_c += ( x[-i] - *gain_p * y[-i] ) * ( x[-i] - *gain_p * y[-i] ); } - if (frame_dms < 100) + if (frame_dms < LC3PLUS_FRAME_DURATION_10MS) { for (i = 0; i < n; i++) { @@ -777,24 +806,24 @@ static void TDC_levinson(LC3_FLOAT *acf, LC3_INT32 len, LC3_FLOAT *out) } } -static LC3_FLOAT type_2_alpha_long(LC3_INT32 nbLostFramesInRow, LC3_INT32 frame_dms) +static LC3_FLOAT type_2_alpha_long(LC3_INT32 nbLostFramesInRow, LC3PLUS_FrameDuration frame_dms) { - if (nbLostFramesInRow <= 3*100.0/frame_dms){ - return LC3_POW(0.95,(nbLostFramesInRow + (100.0/frame_dms) - 1) * frame_dms/100.0); + if (nbLostFramesInRow <= 3*100.0/(frame_dms*1.25*10)){ + return LC3_POW(0.95,(nbLostFramesInRow + (100.0/(frame_dms*1.25*10)) - 1) * (frame_dms*1.25*10)/100.0); } else { - LC3_INT32 n_shift = (nbLostFramesInRow - 3*100.0/frame_dms) * 50/frame_dms; - return LC3_POW(0.7,(n_shift + 100.0/frame_dms - 1) * frame_dms/100.0); + LC3_INT32 n_shift = (nbLostFramesInRow - 3*100.0/(frame_dms*1.25*10)) * 50/(frame_dms*1.25*10); + return LC3_POW(0.7,(n_shift + 100.0/(frame_dms*1.25*10) - 1) * (frame_dms*1.25*10)/100.0); } } -LC3_FLOAT type_2_fadeout(LC3_INT32 nbLostFramesInRow, LC3_INT32 frame_dms) +LC3_FLOAT type_2_fadeout(LC3_INT32 nbLostFramesInRow, LC3PLUS_FrameDuration frame_dms) { - LC3_FLOAT selector = PLC_FADEOUT_TYPE_2_SELECTOR * 2 * 100/frame_dms; + LC3_FLOAT selector = PLC_FADEOUT_TYPE_2_SELECTOR * 2 * 100/(frame_dms*1.25*10); if (selector >= nbLostFramesInRow){ return type_2_alpha_long(nbLostFramesInRow, frame_dms); } else { - return LC3_POW(0.5,(nbLostFramesInRow + (100.0/frame_dms) - 1) * frame_dms/100.0); + return LC3_POW(0.5,(nbLostFramesInRow + (100.0/(frame_dms*1.25*10)) - 1) * (frame_dms*1.25*10)/100.0); } } diff --git a/lib_lc3plus/plc_tdc_tdac.c b/lib_lc3plus/plc_tdc_tdac.c index 5aa4248bdf..bcb0e1770f 100644 --- a/lib_lc3plus/plc_tdc_tdac.c +++ b/lib_lc3plus/plc_tdc_tdac.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/plc_update.c b/lib_lc3plus/plc_update.c index f45afb4062..1f331c00a4 100644 --- a/lib_lc3plus/plc_update.c +++ b/lib_lc3plus/plc_update.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/quantize_spec.c b/lib_lc3plus/quantize_spec.c index 568dbe1fe1..89aa6b9d2c 100644 --- a/lib_lc3plus/quantize_spec.c +++ b/lib_lc3plus/quantize_spec.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -231,11 +231,11 @@ void processQuantizeSpec_fl(LC3_FLOAT x[], LC3_FLOAT gain, LC3_INT xq[], LC3_INT c = (c & 15) * 16 + t; } - *nbits = (bits + 2047) >> 11; // Exactly same as ceil((LC3_FLOAT)*nbits / 2048.0); + *nbits = (bits + 2047) >> 11; /* same as ceil((LC3_FLOAT)*nbits / 2048.0);*/ if (mode >= 0) { - *nbits2 = (bits2 + 2047) >> 11; //ceil((LC3_FLOAT)*nbits2 / 2048.0); + *nbits2 = (bits2 + 2047) >> 11; /* ceil((LC3_FLOAT)*nbits2 / 2048.0); */ } else { diff --git a/lib_lc3plus/reorder_bitstream.c b/lib_lc3plus/reorder_bitstream.c index 3ab549d04b..4825497669 100644 --- a/lib_lc3plus/reorder_bitstream.c +++ b/lib_lc3plus/reorder_bitstream.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/resamp12k8.c b/lib_lc3plus/resamp12k8.c index 0909b67b9b..a5847c5e53 100644 --- a/lib_lc3plus/resamp12k8.c +++ b/lib_lc3plus/resamp12k8.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -12,11 +12,11 @@ #include "functions.h" void process_resamp12k8_fl(LC3_FLOAT x[], LC3_INT x_len, LC3_FLOAT mem_in[], LC3_INT mem_in_len, LC3_FLOAT mem_50[], LC3_FLOAT mem_out[], - LC3_INT mem_out_len, LC3_FLOAT y[], LC3_INT* y_len, LC3_INT fs_idx, LC3_INT frame_dms, LC3_INT fs) + LC3_INT mem_out_len, LC3_FLOAT y[], LC3_INT* y_len, LC3_INT fs_idx, LC3PLUS_FrameDuration frame_dms, LC3_INT fs) { - LC3_INT len_12k8, N12k8, i, k; + LC3_INT len_12k8 = 0, N12k8, i, k; LC3_FLOAT mac, bufdown[128], buf[120 + MAX_LEN]; LC3_INT32 index_int, index_frac, resamp_upfac, resamp_delay, resamp_off_int, resamp_off_frac; LC3_FLOAT u_11, u_21, u_1, u_2; @@ -25,18 +25,25 @@ void process_resamp12k8_fl(LC3_FLOAT x[], LC3_INT x_len, LC3_FLOAT mem_in[], LC3 switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + len_12k8 = LEN_12K8 / 8; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: len_12k8 = LEN_12K8 / 4; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: len_12k8 = LEN_12K8 / 2; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: len_12k8 = (LEN_12K8 / 4) * 3; break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: len_12k8 = LEN_12K8; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } *y_len = len_12k8; diff --git a/lib_lc3plus/residual_coding.c b/lib_lc3plus/residual_coding.c index 282b7655e7..1e788723f7 100644 --- a/lib_lc3plus/residual_coding.c +++ b/lib_lc3plus/residual_coding.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,13 +11,15 @@ #include "wmc_auto.h" #include "functions.h" -void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_INT L_spec, LC3_INT targetBits, LC3_INT nBits, uint8_t* resBits, LC3_INT* numResBits - , LC3_INT hrmode +void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_INT L_spec, LC3_INT targetBits, LC3_INT nBits, uint8_t* resBits, LC3_INT* numResBits, LC3_INT hrmode +#ifdef ENABLE_12p5_DMS_MODE + , LC3PLUS_FrameDuration frame_dms +#endif ) { LC3_INT n = 0, m = 0, k = 0; LC3_INT iter=0; - LC3_FLOAT offset; + LC3_FLOAT offset[3]; LC3_INT iter_max = 1; LC3_INT nz_idx[MAX_LEN]; LC3_INT N_nz = 0, idx = 0; @@ -33,12 +35,23 @@ void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_I assert(m <= MAX_RESBITS); - offset = .25; + offset[2] = .5; if (hrmode) { iter_max = EXT_RES_ITER_MAX; - } +#ifdef ENABLE_12p5_DMS_MODE + else if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + iter_max = 3; + offset[2] = .375; + } +#endif + + /* init offset */ + offset[0] = offset[2] / 2.; + offset[1] = (1.-offset[2]) / 2.; + for (k = 0; k < L_spec; k ++) { if (xq[k]) @@ -56,12 +69,12 @@ void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_I if (x[idx] >= (LC3_FLOAT)xq[idx] * gain) { resBits[n >> 3] |= 1 << (n & 7); - x[idx] -= gain * offset; + x[idx] -= gain * offset[x[idx] > 0]; } else { resBits[n >> 3] &= ~(1 << (n & 7)); - x[idx] += gain * offset; + x[idx] += gain * offset[x[idx] < 0]; } n++; @@ -69,7 +82,8 @@ void processResidualCoding_fl(LC3_FLOAT x[], LC3_INT xq[], LC3_FLOAT gain, LC3_I k++; } iter ++; - offset *= .5; + offset[0] *= .5; + offset[1] *= .5; } *numResBits = n; diff --git a/lib_lc3plus/residual_decoding.c b/lib_lc3plus/residual_decoding.c index 9970c094ba..dff8bd64d7 100644 --- a/lib_lc3plus/residual_decoding.c +++ b/lib_lc3plus/residual_decoding.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,8 +11,10 @@ #include "wmc_auto.h" #include "functions.h" -void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec, uint8_t prm[], LC3_INT resQBits - , LC3_INT hrmode +void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec, uint8_t prm[], LC3_INT resQBits, LC3_INT hrmode +#ifdef ENABLE_12p5_DMS_MODE + , LC3PLUS_FrameDuration frame_dms +#endif ) { LC3_INT k = 0, n = 0; @@ -33,9 +35,14 @@ void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec offset1 = 0.1875; offset2 = 0.3125; } - - if (hrmode) + +#ifdef ENABLE_12p5_DMS_MODE + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + iter_max = 3; + } +#endif + /* enumerat non-zero coefficients */ for (k = 0; k < L_spec; k ++) { @@ -44,6 +51,10 @@ void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec nz_idx[N_nz ++] = k; } } + + if (hrmode) + { + /* apply residual corrections */ while (n < resQBits && iter < iter_max) { @@ -71,26 +82,35 @@ void processResidualDecoding_fl(LC3_INT* bitsRead, LC3_FLOAT x[], LC3_INT L_spec } else { - while (k < L_spec && n < resQBits) { - if (x[k] != 0) { + UNUSED(offset); + + while (n < resQBits && iter < iter_max) { + for (k = 0; k < N_nz; k ++) + { + idx = nz_idx[k]; + if ((prm[n >> 3] & 1 << (n & 7)) == 0) { - if (x[k] > 0) { - x[k] -= offset1; + if (x[idx] > 0) { + x[idx] -= offset1; } else { - x[k] -= offset2; + x[idx] -= offset2; } } else { - if (x[k] > 0) { - x[k] += offset2; + if (x[idx] > 0) { + x[idx] += offset2; } else { - x[k] += offset1; + x[idx] += offset1; } } - n++; + if (++n >= resQBits) + { + break; + } } - - k++; + offset1 *= 0.5; + offset2 *= 0.5; + iter ++; } } *bitsRead = n; diff --git a/lib_lc3plus/setup_com_lc3plus.c b/lib_lc3plus/setup_com_lc3plus.c index f37af56a31..89a4082170 100644 --- a/lib_lc3plus/setup_com_lc3plus.c +++ b/lib_lc3plus/setup_com_lc3plus.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,6 +11,30 @@ #include "wmc_auto.h" #include "functions.h" +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE +/* tilt factor in gainOffset quantized and adjusted for low Fs and 1p25ms framing */ +LC3_INT16 calc_GGainOffset_1p25(LC3_INT16 total_bits, LC3_INT16 fs_idx) +{ + LC3_INT16 gain_off_tilt_1p25_Q19[6] = { 20480, 17408, 17476, 13107, 10486, 8738 }; /* vector of 1p25 tilts for NB to UB */ + /* Corresponding FLT = gain_off_tilt_1p25 = {0.039062500000000 0.033203125000000 0.033333333333333 0.025000000000000 0.020000000000000 0.016666666666667 }*/ + + + LC3_INT16 tmp1 = (LC3_INT16)( ( ((LC3_INT32)total_bits)*((LC3_INT32)gain_off_tilt_1p25_Q19[fs_idx]) ) >> (3 + 16) ); /*no rounding on purpose */ + LC3_INT16 tmp2 = 105 + 5 * (fs_idx + 1); + + tmp2 = -(MIN(115, tmp1) + tmp2); + +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE + if (fs_idx <= 1) + { /* only NB and WB additionally limited to -135 */ + tmp2 = MAX(tmp2, FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE); + } +#endif + + return tmp2; +} +#endif + LC3_FLOAT array_max_abs(LC3_FLOAT *in, LC3_INT32 len) { LC3_FLOAT max; diff --git a/lib_lc3plus/setup_dec_lc3plus.c b/lib_lc3plus/setup_dec_lc3plus.c index 9e9a5ea64e..67a86bb027 100644 --- a/lib_lc3plus/setup_dec_lc3plus.c +++ b/lib_lc3plus/setup_dec_lc3plus.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -34,7 +34,7 @@ int alloc_decoder(LC3PLUS_Dec* decoder, int samplerate, int channels) HANDLE_IIS_FFT handle_fft_phaseecu; HANDLE_IIS_FFT handle_ifft_phaseecu; LC3_FLOAT *q_old_res; - + LC3_INT32 * plc_longterm_advc_tdc = NULL, *plc_longterm_advc_ns = NULL; LC3_INT16 longterm_analysis_counter_max = 0, longterm_analysis_counter_max_bytebuffer = 0; @@ -51,16 +51,16 @@ int alloc_decoder(LC3PLUS_Dec* decoder, int samplerate, int channels) PhECU_xfp = balloc(decoder, &size, sizeof(LC3_FLOAT) *(frame_len * 16 / 10)); PhECU_X_sav_m = balloc(decoder, &size, sizeof(Complex) *(((frame_len * 16 / 10) / 2) + 1));/*MAX_PLC_LMSPEC*/ PhECU_plocs = balloc(decoder, &size, sizeof(LC3_INT32) * (((frame_len * 16 / 10) / 4) + 1 + 1)); /* BASOP Word16 *PhECU_plocs; */ - + handle_fft_phaseecu = balloc(decoder, &size, sizeof(IIS_FFT) * 1); handle_ifft_phaseecu = balloc(decoder, &size, sizeof(IIS_FFT) * 1); PhECU_f0est = balloc(decoder, &size, sizeof(LC3_FLOAT) * (((frame_len * 16 / 10) / 4) + 1)); /*BASOP Word32 *PhECU_f0est;*/ PhECU_mag_chg_1st = balloc(decoder, &size, sizeof(LC3_FLOAT) *MAX_LGW); /* BASOP Word16 PhECU_mag_chg_1st[MAX_LGW];*/ PhECU_Xavg = balloc(decoder, &size, sizeof(LC3_FLOAT) * MAX_LGW); /* BASOP Word16 PhECU_Xavg[MAX_LGW] ; */ - + sine_table1_phecu = balloc(decoder, &size, sizeof(LC3_FLOAT) * (((CODEC_FS(samplerate) * 16) / 1000) / 2 + 1)); sine_table2_phecu = balloc(decoder, &size, sizeof(LC3_FLOAT) * (((CODEC_FS(samplerate) * 16) / 1000) / 2 + 1)); - + longterm_analysis_counter_max = plc_fadeout_param_maxlen[0]; longterm_analysis_counter_max_bytebuffer = plc_fadeout_param_maxbytes[0]; @@ -71,6 +71,16 @@ int alloc_decoder(LC3PLUS_Dec* decoder, int samplerate, int channels) if (decoder) { decoder->channel_setup[ch] = setup; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_mem_continuation = 0; +#ifdef FIX_TX_RX_STRUCT_STEREO + setup->ltpf_rx_status[0] = 0; + setup->ltpf_rx_status[1] = 0; +#endif +#ifdef NEW_SIGNALLING_SCHEME_1p25 + setup->ltpfinfo_frame_cntr = -32768; +#endif +#endif setup->PlcAdvSetup = PlcAdvSetup; @@ -86,16 +96,16 @@ int alloc_decoder(LC3PLUS_Dec* decoder, int samplerate, int channels) setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_Xavg = PhECU_Xavg; setup->PlcAdvSetup->PlcPhEcuSetup.handle_fft_phaseecu = handle_fft_phaseecu; setup->PlcAdvSetup->PlcPhEcuSetup.handle_ifft_phaseecu = handle_ifft_phaseecu; - + setup->PlcAdvSetup->PlcPhEcuSetup.handle_fft_phaseecu->sine_table = sine_table1_phecu; setup->PlcAdvSetup->PlcPhEcuSetup.handle_ifft_phaseecu->sine_table = sine_table2_phecu; - + setup->PlcAdvSetup->longterm_analysis_counter_max = longterm_analysis_counter_max; setup->PlcAdvSetup->longterm_analysis_counter_max_bytebuffer = longterm_analysis_counter_max_bytebuffer; - + setup->PlcAdvSetup->plc_longterm_advc_tdc = plc_longterm_advc_tdc; setup->PlcAdvSetup->plc_longterm_advc_ns = plc_longterm_advc_ns; - + setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_Lprot = (CODEC_FS(samplerate) * 16) / 1000; real_fft_init(&(setup->PlcAdvSetup->PlcPhEcuSetup.PhEcu_Fft), setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_Lprot, &(setup->PlcAdvSetup->PlcPhEcuSetup.handle_fft_phaseecu)); real_ifft_init(&(setup->PlcAdvSetup->PlcPhEcuSetup.PhEcu_Ifft), setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_Lprot, &(setup->PlcAdvSetup->PlcPhEcuSetup.handle_ifft_phaseecu)); @@ -117,14 +127,14 @@ LC3PLUS_Error FillDecSetup(LC3PLUS_Dec* decoder, int samplerate, int channels, L decoder->fs_out = samplerate; decoder->fs_idx = FS2FS_IDX(decoder->fs); decoder->plcMeth = plc_mode; - + decoder->hrmode = hrmode != 0; decoder->channels = channels; - decoder->frame_ms = 10; - decoder->frame_dms = 100; + decoder->frame_ms = LC3PLUS_FRAME_DURATION_10MS; + decoder->frame_dms = LC3PLUS_FRAME_DURATION_10MS; decoder->BW_cutoff_bits = BW_cutoff_bits_all[decoder->fs_idx]; - + if (decoder->fs == 8000) { decoder->tilt = 14; } else if (decoder->fs == 16000) { @@ -143,14 +153,14 @@ LC3PLUS_Error FillDecSetup(LC3PLUS_Dec* decoder, int samplerate, int channels, L set_dec_frame_params(decoder); lc3plus_dec_set_ep_enabled(decoder, 0); - + return LC3PLUS_OK; } /* set frame config params */ void set_dec_frame_params(LC3PLUS_Dec* decoder) { - int ch = 0; + int ch = 0, idx = 0; int n; if (decoder->fs_idx == 5) @@ -168,34 +178,66 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) decoder->yLen = MIN(MAX_BW, decoder->frame_length); } +#ifndef FIX_TX_RX_STRUCT_STEREO + decoder->ltpf_rx_status[0] = 0; + decoder->ltpf_rx_status[1] = 0; +#endif + decoder->bands_number = 64; - if (decoder->frame_ms == 2.5) +#ifdef CR9_C_ADD_1p25MS + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + decoder->frame_length = decoder->frame_length >> 3; + decoder->yLen /= 8; + + if (decoder->hrmode) + { + assert(0); + } + else + { + decoder->bands_number = bands_number_1_25ms[decoder->fs_idx]; + } + + decoder->bands_offset = ACC_COEFF_PER_BAND_1_25ms[decoder->fs_idx]; + decoder->BW_cutoff_bits = 0; + decoder->cutoffBins = BW_cutoff_bin_all_1_25ms; + + decoder->imdct_win = MDCT_WINS_1_25ms[decoder->hrmode][decoder->fs_idx]; + decoder->imdct_laZeros = MDCT_la_zeroes_1_25ms[decoder->fs_idx]; + decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_1_25ms[decoder->fs_idx]; + + decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC_1_25ms[decoder->fs_idx]; + decoder->n_bandsPLC = decoder->frame_length; + } +#endif + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { decoder->frame_length = decoder->frame_length >> 2; decoder->yLen /= 4; if (decoder->hrmode) { decoder->bands_number = bands_number_2_5ms_HR[decoder->fs_idx]; - } + } else { decoder->bands_number = bands_number_2_5ms[decoder->fs_idx]; } } - if (decoder->frame_ms == 5) + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { decoder->frame_length = decoder->frame_length >> 1; decoder->yLen /= 2; decoder->bands_number = bands_number_5ms[decoder->fs_idx]; } - if (decoder->frame_ms == 7.5) + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { decoder->frame_length = (decoder->frame_length >> 2) * 3; - decoder->yLen = (decoder->yLen / 4) * 3; + decoder->yLen = (decoder->yLen / 4) * 3; if (decoder->hrmode) { decoder->bands_number = bands_number_7_5ms_HR[decoder->fs_idx]; - } + } else { decoder->bands_number = bands_number_7_5ms[decoder->fs_idx]; @@ -206,12 +248,18 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) { decoder->BW_cutoff_bits = 0; } +#ifdef CR9_C_ADD_1p25MS + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + decoder->BW_cutoff_bits = 0; + } +#endif else { decoder->BW_cutoff_bits = BW_cutoff_bits_all[decoder->fs_idx]; } - if (decoder->frame_ms == 10) + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { if (decoder->hrmode) { @@ -223,7 +271,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } decoder->cutoffBins = BW_cutoff_bin_all; } - else if (decoder->frame_ms == 2.5) + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { if (decoder->hrmode) { @@ -235,7 +283,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } decoder->cutoffBins = BW_cutoff_bin_all_2_5ms; } - else if (decoder->frame_ms == 5) + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { if (decoder->hrmode) { @@ -247,7 +295,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } decoder->cutoffBins = BW_cutoff_bin_all_5ms; } - else if (decoder->frame_ms == 7.5) + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { if (decoder->hrmode) { @@ -259,14 +307,14 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } decoder->cutoffBins = BW_cutoff_bin_all_7_5ms; } - + decoder->n_bandsPLC = MIN(decoder->frame_length, 80); - - if (decoder->frame_ms == 10) + + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC[decoder->fs_idx]; } - else if (decoder->frame_ms == 5) + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC_5ms[decoder->fs_idx]; @@ -275,7 +323,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) decoder->n_bandsPLC = 40; } } - else if (decoder->frame_ms == 2.5) + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC_2_5ms[decoder->fs_idx]; @@ -284,38 +332,56 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) decoder->n_bandsPLC = 60; } } - else if (decoder->frame_ms == 7.5) +#ifdef CR9_C_ADD_1p25MS + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC_1_25ms[decoder->fs_idx]; + + if (decoder->fs == 48000) + { + decoder->n_bandsPLC = 60; + } + } +#endif + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { decoder->bands_offsetPLC = ACC_COEFF_PER_BAND_PLC_7_5ms[decoder->fs_idx]; - + if (decoder->fs != 32000 && decoder->fs != 96000) { decoder->n_bandsPLC = 60; } } - + assert(decoder->bands_offsetPLC); - if (decoder->frame_ms == 10) { + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { decoder->imdct_win = MDCT_WINS_10ms[decoder->hrmode][decoder->fs_idx]; decoder->imdct_laZeros = MDCT_la_zeroes[decoder->fs_idx]; decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_10ms[decoder->fs_idx]; } - else if (decoder->frame_ms == 2.5) { + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { decoder->imdct_win = MDCT_WINS_2_5ms[decoder->hrmode][decoder->fs_idx]; decoder->imdct_laZeros = MDCT_la_zeroes_2_5ms[decoder->fs_idx]; decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_2_5ms[decoder->fs_idx]; } - else if (decoder->frame_ms == 5) { + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { decoder->imdct_win = MDCT_WINS_5ms[decoder->hrmode][decoder->fs_idx]; decoder->imdct_laZeros = MDCT_la_zeroes_5ms[decoder->fs_idx]; decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_5ms[decoder->fs_idx]; } - else if (decoder->frame_ms == 7.5) { + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { decoder->imdct_win = MDCT_WINS_7_5ms[decoder->hrmode][decoder->fs_idx]; decoder->imdct_laZeros = MDCT_la_zeroes_7_5ms[decoder->fs_idx]; decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_7_5ms[decoder->fs_idx]; } +#ifdef CR9_C_ADD_1p25MS + else if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { + decoder->imdct_win = MDCT_WINS_1_25ms[decoder->hrmode][decoder->fs_idx]; + decoder->imdct_laZeros = MDCT_la_zeroes_1_25ms[decoder->fs_idx]; + decoder->imdct_winLen = MDCT_WINDOWS_LENGTHS_1_25ms[decoder->fs_idx]; + } +#endif decoder->la_zeroes = decoder->imdct_laZeros; @@ -323,9 +389,9 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) for (ch = 0; ch < decoder->channels; ch++) { DecSetup* setup = decoder->channel_setup[ch]; - + setup->ltpf_mem_beta_idx = -1; - + setup->statePC.seed = 24607; if (decoder) { @@ -336,7 +402,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } else { dct4_init(&setup->dct4structImdct, decoder->frame_length); } - + setup->PlcNsSetup.cum_alpha = 1; setup->PlcNsSetup.seed = 24607; setup->alpha = 1; @@ -344,14 +410,14 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) { LC3_INT32 pitch_max = 0, pitch_ana_len = 0, tdc_synt_len = 0; pitch_max = ceil(228.0 * (LC3_FLOAT) decoder->fs / 12800.0); - pitch_ana_len = pitch_max + decoder->frame_length * (LC3_FLOAT) 100 / decoder->frame_dms; + pitch_ana_len = pitch_max + decoder->frame_length * (LC3_FLOAT) 100 / (decoder->frame_dms * 1.25 * 10); tdc_synt_len = 16 + 1 + pitch_max + ceil(decoder->frame_length / 2); setup->PlcAdvSetup->max_len_pcm_plc = MAX(pitch_ana_len, tdc_synt_len); setup->PlcAdvSetup->PlcTdcSetup.preemphFac = plc_preemph_fac[decoder->fs_idx]; setup->PlcAdvSetup->PlcTdcSetup.seed = 24607; setup->PlcAdvSetup->PlcTdcSetup.lpcorder = 16; - if (decoder->fs_idx == 0 && decoder->frame_dms == 25) + if ((decoder->fs_idx == 0 || decoder->frame_length <= 20) && decoder->frame_dms <= LC3PLUS_FRAME_DURATION_2p5MS) { setup->PlcAdvSetup->PlcTdcSetup.lpcorder = 8; } @@ -360,11 +426,12 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) setup->PlcAdvSetup->cum_fading_fast = 1; setup->PlcAdvSetup->cum_fading_slow = 1; setup->PlcAdvSetup->cum_fflcAtten = 1; - - setup->PlcAdvSetup->longterm_analysis_counter_max = plc_fadeout_param_maxlen[(decoder->frame_dms / 25) - 1]; - setup->PlcAdvSetup->longterm_analysis_counter_max_bytebuffer = plc_fadeout_param_maxbytes[(decoder->frame_dms / 25) - 1]; - if (decoder->fs_idx <= 4 && decoder->frame_dms == 100) + idx = (decoder->frame_dms * 1.25 * 10 / 25) - 1; + setup->PlcAdvSetup->longterm_analysis_counter_max = plc_fadeout_param_maxlen[idx]; + setup->PlcAdvSetup->longterm_analysis_counter_max_bytebuffer = plc_fadeout_param_maxbytes[idx]; + + if (decoder->fs_idx <= 4 && decoder->frame_dms == LC3PLUS_FRAME_DURATION_10MS) { setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_Lprot = (decoder->fs * 16) / 1000; /* 16 ms of samples at fs*/ @@ -386,7 +453,7 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_beta_mute = (16384.0/32768.0); setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_seed = 21845; - assert(decoder->frame_dms == 100); + assert(decoder->frame_dms == LC3PLUS_FRAME_DURATION_10MS); setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_LDWIN_OLAP = (decoder->frame_length / 4 ); /* 2.5 ms for regular 10 ms MDCT */ setup->PlcAdvSetup->PlcPhEcuSetup.PhECU_t_adv = ( @@ -400,9 +467,9 @@ void set_dec_frame_params(LC3PLUS_Dec* decoder) } } } - for (n=0; n < LC3_ROUND(PLC_FADEOUT_TYPE_1_IN_MS*10/decoder->frame_dms);n++){ + for (n=0; n < LC3_ROUND(PLC_FADEOUT_TYPE_1_IN_MS*10/(decoder->frame_dms*1.25*10));n++){ decoder->alpha_type_2_table[n] = type_2_fadeout(n, decoder->frame_dms); - } + } } LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) @@ -414,19 +481,26 @@ LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) { switch (decoder->frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + assert(0); + maxBytes = 210; + minBytes = MIN_NBYTES; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: maxBytes = 210; minBytes = MIN_NBYTES; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: maxBytes = 375; minBytes = MIN_NBYTES; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: maxBytes = 625; minBytes = MIN_NBYTES; - break; - case 100: + break; + case LC3PLUS_FRAME_DURATION_10MS: maxBytes = 625; minBytes = MIN_NBYTES; break; @@ -437,7 +511,7 @@ LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) else { minBytes = MIN_NBYTES; - maxBytes = MAX_NBYTES_100; /* for backward compatibility, MAX_NBYTES_100 is used for all frame lengths */ + maxBytes = MAX_NBYTES_100; /* for backward compatibility, MAX_NBYTES_100 is used for all frame lengths */ } channel_bytes = nBytes; @@ -448,12 +522,25 @@ LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) { return LC3PLUS_NUMBYTES_ERROR; } - + setup->targetBytes = channel_bytes; setup->total_bits = setup->targetBytes << 3; setup->enable_lpc_weighting = (setup->total_bits < 480); + +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE + if (decoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + setup->quantizedGainOff = calc_GGainOffset_1p25(setup->total_bits, decoder->fs_idx); /* enc/dec common function */ + } + else + { + setup->quantizedGainOff = + -(MIN(115, setup->total_bits / (10 * (decoder->fs_idx + 1))) + 105 + 5 * (decoder->fs_idx + 1)); + } +#else setup->quantizedGainOff = -(MIN(115, setup->total_bits / (10 * (decoder->fs_idx + 1))) + 105 + 5 * (decoder->fs_idx + 1)); +#endif if (decoder->hrmode && decoder->fs_idx == 5) { @@ -461,22 +548,28 @@ LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) } totalBits = setup->total_bits; - - if (decoder->frame_ms == 2.5) { + +#ifdef CR9_C_ADD_1p25MS + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { + setup->enable_lpc_weighting = setup->total_bits < 60; + totalBits = setup->total_bits * 8.0 * 0.42; + } +#endif + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { setup->enable_lpc_weighting = setup->total_bits < 120; totalBits = setup->total_bits * 4.0 * (1.0 - 0.4); } - if (decoder->frame_ms == 5) { + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { setup->enable_lpc_weighting = (setup->total_bits < 240); totalBits = setup->total_bits * 2 - 160; } - if (decoder->frame_ms == 7.5) { + if (decoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { setup->enable_lpc_weighting = (setup->total_bits < 360); totalBits = round(setup->total_bits * 10 / 7.5); } - - if (decoder->frame_length > 40 * ((LC3_FLOAT) (decoder->frame_dms) / 10.0)) { - setup->N_red_tns = 40 * ((LC3_FLOAT) (decoder->frame_dms) / 10.0); + + if (decoder->frame_length > 40 * ((LC3_FLOAT) (decoder->frame_dms * 1.25 * 10) / 10.0)) { + setup->N_red_tns = 40 * ((LC3_FLOAT) (decoder->frame_dms * 1.25 * 10) / 10.0); setup->fs_red_tns = 40000; } else { setup->N_red_tns = decoder->frame_length; @@ -484,29 +577,48 @@ LC3PLUS_Error update_dec_bitrate(LC3PLUS_Dec* decoder, int ch, int nBytes) } bitsTmp = totalBits; - + if (bitsTmp < 400 + (decoder->fs_idx - 1) * 80) { setup->ltpf_conf_beta = 0.4; setup->ltpf_conf_beta_idx = 0; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_conf_beta_max = 0.6; +#endif } else if (bitsTmp < 480 + (decoder->fs_idx - 1) * 80) { setup->ltpf_conf_beta = 0.35; setup->ltpf_conf_beta_idx = 1; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_conf_beta_max = 0.55; +#endif } else if (bitsTmp < 560 + (decoder->fs_idx - 1) * 80) { setup->ltpf_conf_beta = 0.3; setup->ltpf_conf_beta_idx = 2; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_conf_beta_max = 0.5; +#endif } else if (bitsTmp < 640 + (decoder->fs_idx - 1) * 80) { setup->ltpf_conf_beta = 0.25; setup->ltpf_conf_beta_idx = 3; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_conf_beta_max = 0.45; +#endif } else { setup->ltpf_conf_beta = 0; setup->ltpf_conf_beta_idx = -1; +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_conf_beta_max = 0; +#endif } +#ifdef CR9_C_ADD_1p25MS + setup->ltpf_gain_step = ( setup->ltpf_conf_beta_max - setup->ltpf_conf_beta ) / LTPF_ADAPTIVE_GAIN_RATE; +#endif + /* No LTPF in hrmode */ if (decoder->hrmode == 1) { setup->ltpf_conf_beta = 0; setup->ltpf_conf_beta_idx = -1; } - + return LC3PLUS_OK; } diff --git a/lib_lc3plus/setup_dec_lc3plus.h b/lib_lc3plus/setup_dec_lc3plus.h index c3d26ee8eb..5dd3e67613 100644 --- a/lib_lc3plus/setup_dec_lc3plus.h +++ b/lib_lc3plus/setup_dec_lc3plus.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -13,6 +13,7 @@ #include "options.h" #include "wmc_auto.h" #include "constants.h" +#include "lc3plus.h" /* Channel state and bitrate-derived values go in this struct */ typedef struct { @@ -38,6 +39,24 @@ typedef struct { LC3_INT scf_idx[SCF_MAX_PARAM]; uint8_t resBits[MAX_RESBITS_LEN]; LC3_INT tns_idx[TNS_NUMFILTERS_MAX * MAXLAG]; +#ifdef CR9_C_ADD_1p25MS + /* 1.25 continuation */ + LC3_INT16 ltpf_mem_continuation; + LC3_INT32 ltpf_param_mem_prev[3]; + LC3_INT16 ltpf_mem_pitch_prev; + LC3_INT16 ltpf_mem_pitch_fr_prev; + LC3_INT32 ltpf_mem_beta_idx_prev; + LC3_FLOAT ltpf_conf_beta_max; + LC3_INT16 ltpf_pitch_stability_counter; + LC3_FLOAT ltpf_gain_step; + LC3_FLOAT ltpf_mem_gain_prev; +#ifdef FIX_TX_RX_STRUCT_STEREO + LC3_INT32 ltpf_rx_status[2]; +#endif +#ifdef NEW_SIGNALLING_SCHEME_1p25 + LC3_INT32 ltpfinfo_frame_cntr; /* individual cntr for each channel */ +#endif +#endif LC3_FLOAT prev_fac_ns; LC3_FLOAT ltpf_mem_x[3 * MAX_LEN]; @@ -62,7 +81,7 @@ typedef struct { /* Constants and sampling rate derived values go in this struct */ struct LC3PLUS_Dec { - LC3_FLOAT alpha_type_2_table[PLC_FADEOUT_TYPE_1_IN_MS*10/25]; /* [80] */ + LC3_FLOAT alpha_type_2_table[PLC_FADEOUT_TYPE_1_IN_MS*100/125]; /* [160] */ DecSetup* channel_setup[MAX_CHANNELS]; const LC3_INT* W_fx; const LC3_INT* bands_offset; @@ -73,8 +92,8 @@ struct LC3PLUS_Dec { LC3_INT fs_idx; /* sampling rate index */ LC3_INT frame_length; /* sampling rate index */ LC3_INT channels; /* number of channels */ - LC3_FLOAT frame_ms; /* frame length in ms (wrong for 44.1) */ - LC3_INT frame_dms; /* frame length in ms * 10 (wrong for 44.1) */ + LC3PLUS_FrameDuration frame_ms; /* frame length in ms (wrong for 44.1) */ + LC3PLUS_FrameDuration frame_dms; /* frame length in ms * 10 (wrong for 44.1) */ LC3_INT last_size; /* size of last frame, without error protection */ LC3_INT ep_enabled; /* error protection enabled */ LC3_INT error_report; /* corrected errors in last frame or -1 on error */ @@ -109,6 +128,9 @@ struct LC3PLUS_Dec { int epmr; LC3_INT16 combined_channel_coding; int last_error; +#ifndef FIX_TX_RX_STRUCT_STEREO + LC3_INT ltpf_rx_status[2]; +#endif }; #endif diff --git a/lib_lc3plus/setup_enc_lc3plus.c b/lib_lc3plus/setup_enc_lc3plus.c index 80bd909ab3..438a721d06 100644 --- a/lib_lc3plus/setup_enc_lc3plus.c +++ b/lib_lc3plus/setup_enc_lc3plus.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -41,7 +41,7 @@ LC3PLUS_Error FillEncSetup(LC3PLUS_Enc* encoder, int samplerate, int channels encoder->fs = CODEC_FS(samplerate); encoder->fs_in = samplerate; encoder->fs_idx = FS2FS_IDX(encoder->fs); - encoder->frame_dms = 100; + encoder->frame_dms = LC3PLUS_FRAME_DURATION_10MS; if (encoder->fs_idx > 4) { encoder->fs_idx = 5; @@ -50,7 +50,7 @@ LC3PLUS_Error FillEncSetup(LC3PLUS_Enc* encoder, int samplerate, int channels encoder->hrmode = hrmode != 0; encoder->channels = channels; - encoder->frame_ms = 10; + encoder->frame_ms = LC3PLUS_FRAME_DURATION_10MS; encoder->envelope_bits = 38; encoder->global_gain_bits = 8; encoder->noise_fac_bits = 3; @@ -97,6 +97,15 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) int ch = 0; EncSetup* setup; +#ifdef CR9_C_ADD_1p25MS +#ifndef FIX_TX_RX_STRUCT_STEREO + encoder->Tx_ltpf = 0; +#endif +#endif +#ifdef FIX_FLOAT_LT_NORMCORR_INIT + encoder->long_term_norm_corr= (0xFFFF >> 2)/32768.0; +#endif + encoder->frame_length = ceil(encoder->fs * 10 / 1000); /* fs * 0.01*2^6 */ if (encoder->hrmode == 1) { @@ -129,7 +138,7 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) encoder->BW_cutoff_bits = BW_cutoff_bits_all[encoder->fs_idx]; } - if (encoder->frame_ms == 10) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { encoder->la_zeroes = MDCT_la_zeroes[encoder->fs_idx]; if (encoder->hrmode) { @@ -145,7 +154,7 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) encoder->attdec_damping = 0.5; encoder->attdec_hangover_thresh = 2; } - else if (encoder->frame_ms == 7.5) { + else if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { if (encoder->hrmode) { encoder->bands_offset = ACC_COEFF_PER_BAND_7_5ms_HR[encoder->fs_idx]; @@ -176,7 +185,7 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) encoder->near_nyquist_index = encoder->bands_number - 4; encoder->r12k8_mem_out_len = ceil(2.0 * ((LC3_FLOAT) encoder->frame_length / 2.0 - (LC3_FLOAT) encoder->la_zeroes) * 12800.0 / (LC3_FLOAT) encoder->fs - 8.0); } - else if (encoder->frame_ms == 5) { + else if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { encoder->frame_length = encoder->frame_length >> 1; encoder->yLen /= 2; encoder->stEnc_mdct_mem_len = encoder->frame_length - encoder->la_zeroes; @@ -193,8 +202,11 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) encoder->bands_offset = ACC_COEFF_PER_BAND_5ms[encoder->fs_idx]; } encoder->cutoffBins = BW_cutoff_bin_all_5ms; +#ifdef FIX_LTPF_PITCH_MEM_LEN + encoder->ltpf_mem_in_len = LTPF_MEMIN_LEN + LEN_12K8 / 2; +#endif } - else if (encoder->frame_ms == 2.5) { + else if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { encoder->la_zeroes = MDCT_la_zeroes_2_5ms[encoder->fs_idx]; if (encoder->hrmode) { @@ -219,12 +231,53 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) encoder->nSubdivisions = 2; encoder->near_nyquist_index = encoder->bands_number - 2; +#ifdef FIX_LTPF_PITCH_MEM_LEN + encoder->ltpf_mem_in_len = LTPF_MEMIN_LEN + (3 * (LEN_12K8 / 4)); +#else encoder->ltpf_mem_in_len = LTPF_MEMIN_LEN + (LEN_12K8 >> 2); +#endif + } +#ifdef CR9_C_ADD_1p25MS + else if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { + encoder->la_zeroes = MDCT_la_zeroes_1_25ms[encoder->fs_idx]; + if (encoder->hrmode) + { + assert(0); + } + else + { + encoder->bands_offset = ACC_COEFF_PER_BAND_1_25ms[encoder->fs_idx]; + } + encoder->cutoffBins = BW_cutoff_bin_all_1_25ms; + encoder->frame_length = encoder->frame_length >> 3; + encoder->yLen /= 8; + encoder->stEnc_mdct_mem_len = encoder->frame_length - encoder->la_zeroes; + if (encoder->hrmode) + { + assert(0); + } + else + { + encoder->bands_number = bands_number_1_25ms[encoder->fs_idx]; + encoder->BW_cutoff_bits = 0; /* transmit no bw bits */ + encoder->bw_ctrl_active = 1; + } + + encoder->nSubdivisions = 2; + encoder->near_nyquist_index = encoder->bands_number - 2; +#ifdef FIX_LTPF_PITCH_MEM_LEN + encoder->ltpf_mem_in_len = LTPF_MEMIN_LEN + (7 * (LEN_12K8 / 8)); +#else + encoder->ltpf_mem_in_len = LTPF_MEMIN_LEN + (LEN_12K8 >> 1) + 16; +#endif + encoder->r12k8_mem_out_len = 8; + + encoder->attdec_damping = 32767.0/32768.0; } +#endif for (ch = 0; ch < encoder->channels; ch++) { setup = encoder->channel_setup[ch]; - setup->olpa_mem_pitch = 17; setup->pitch_flag = 0; if (setup->mdctStruct.mem != NULL) { @@ -246,32 +299,46 @@ void set_enc_frame_params(LC3PLUS_Enc* encoder) LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) { int ch = 0, bitsTmp = 0, minBR = 0, maxBR = 0, totalBytes = 0; - LC3_INT channel_bytes = 0, max_bytes = 0; - + LC3_INT channel_bytes = 0; + #ifdef ENABLE_HR_MODE_FL +#ifdef CR12_D_FIX_BITRATE_LIMITS + LC3_INT fec_slot_bytes_min = 0, check_bytes = 0; +#else + LC3_INT max_bytes = 0; +#endif if (encoder->hrmode) { switch (encoder->frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + assert(0); maxBR = 672000; if (encoder->fs == 48000) {minBR = MIN_BR_25MS_48KHZ_HR;} else if (encoder->fs == 96000) {minBR = MIN_BR_25MS_96KHZ_HR;} else { return LC3PLUS_HRMODE_ERROR;} break; - case 50: +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: + maxBR = 672000; + if (encoder->fs == 48000) {minBR = MIN_BR_25MS_48KHZ_HR;} + else if (encoder->fs == 96000) {minBR = MIN_BR_25MS_96KHZ_HR;} + else { return LC3PLUS_HRMODE_ERROR;} + break; + case LC3PLUS_FRAME_DURATION_5MS: maxBR = 600000; if (encoder->fs == 48000) {minBR = MIN_BR_50MS_48KHZ_HR;} else if (encoder->fs == 96000) {minBR = MIN_BR_50MS_96KHZ_HR;} else { return LC3PLUS_HRMODE_ERROR;} break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: maxBR = 500000; if (encoder->fs == 48000) {minBR = MIN_BR_075DMS_48KHZ_HR;} else if (encoder->fs == 96000) {minBR = MIN_BR_075DMS_96KHZ_HR;} else {return LC3PLUS_HRMODE_ERROR;} break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: maxBR = 500000; if (encoder->fs == 48000) {minBR = MIN_BR_100MS_48KHZ_HR;} else if (encoder->fs == 96000) {minBR = MIN_BR_100MS_96KHZ_HR;} @@ -289,11 +356,17 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) switch (encoder->frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + minBR = MIN_BR_0125DMS; + maxBR = MAX_BR_0125DMS; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: minBR = MIN_BR_025DMS; maxBR = MAX_BR; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: minBR = MIN_BR_050DMS; maxBR = MAX_BR; /* have additional limitations for 5.0ms */ @@ -303,7 +376,7 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) default: break; } break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: minBR = MIN_BR_075DMS; maxBR = MAX_BR_075DMS; /* have additional limitations for 7.5ms */ @@ -315,7 +388,7 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) default: break; } break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: /* have additional limitations for 10ms */ minBR = MIN_BR_100DMS; maxBR = MAX_BR; @@ -327,7 +400,8 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) default: maxBR = MAX_BR; break; } break; - default: return LC3PLUS_FRAMEMS_ERROR; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + return LC3PLUS_FRAMEMS_ERROR; } maxBR *= (encoder->fs_in == 44100 ? 441. / 480 : 1); } @@ -346,8 +420,57 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) if (encoder->epmode > 0) { +#ifdef CR12_D_FIX_BITRATE_LIMITS +#ifdef ENABLE_HR_MODE_FL + if (encoder->hrmode){ + switch( encoder->frame_dms ) + { + case LC3PLUS_FRAME_DURATION_2p5MS: + if( encoder->fs_in == 48000){ + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_025DMS_48KHZ_HR; + } else { + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_025DMS_96KHZ_HR; + } + break; + case LC3PLUS_FRAME_DURATION_5MS: + if( encoder->fs_in == 48000){ + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_050DMS_48KHZ_HR; + } else { + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_050DMS_96KHZ_HR; + } + break; + case LC3PLUS_FRAME_DURATION_7p5MS: + if( encoder->fs_in == 48000){ + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_075DMS_48KHZ_HR; + } else { + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_075DMS_96KHZ_HR; + } + break; + case LC3PLUS_FRAME_DURATION_10MS: + if( encoder->fs_in == 48000){ + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_100DMS_48KHZ_HR; + } else { + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN_100DMS_96KHZ_HR; + } + break; + default: + return LC3PLUS_FRAMEMS_ERROR; + } + } + else +#endif + { + fec_slot_bytes_min = FEC_SLOT_BYTES_MIN; + } + + + check_bytes = bitrate * encoder->frame_length / ( 8 * encoder->fs_in * encoder->channels ); + maxBR = FEC_SLOT_BYTES_MAX * ( 8 * encoder->fs_in * encoder->channels ) / encoder->frame_length; + if ( check_bytes < fec_slot_bytes_min || bitrate > maxBR ) +#else max_bytes = bitrate * encoder->frame_length / (8 * encoder->fs_in * encoder->channels); if (max_bytes < FEC_SLOT_BYTES_MIN || max_bytes > FEC_SLOT_BYTES_MAX) +#endif /* CR12_D_FIX_BITRATE_LIMITS */ { encoder->lc3_br_set = 0; return LC3PLUS_BITRATE_ERROR; @@ -371,7 +494,7 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) totalBytes = bitrate * encoder->frame_length / (8 * encoder->fs_in); } - if (encoder->frame_dms <= 50) + if (encoder->frame_dms <= LC3PLUS_FRAME_DURATION_5MS) { encoder->tnsMaxOrder = 4; } else { @@ -400,8 +523,8 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) setup->n_pccw = fec_get_n_pccw(channel_bytes, encoder->epmode, encoder->combined_channel_coding); setup->n_pc = fec_get_n_pc(encoder->epmode, setup->n_pccw, channel_bytes); } - // reduce bandwith to 12kHz if bitrate is low - if (encoder->frame_dms == 100 && + /* reduce bandwith to 12kHz if bitrate is low */ + if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_10MS && ((setup->targetBytes < 40 && encoder->fs == 48000) || (setup->targetBytes < 36 && encoder->fs == 32000))) { @@ -413,12 +536,12 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) For a second channel with lower targetBytes, bandwidth is overwritten */ encoder->bandwidth = encoder->bandwidth_preset; } - encoder->bw_ctrl_cutoff_bin = encoder->bandwidth * encoder->frame_dms / 5000; + encoder->bw_ctrl_cutoff_bin = encoder->bandwidth * (encoder->frame_dms * 1.25 * 10) / 5000; encoder->bw_index = (encoder->bandwidth / 4000) - 1; setup->total_bits = setup->targetBytes << 3; setup->targetBitsInit = setup->total_bits - encoder->envelope_bits - encoder->global_gain_bits - encoder->noise_fac_bits - encoder->BW_cutoff_bits - - ceil(LC3_LOGTWO(encoder->frame_length / 2)) - 2 - 1; + getLastNzBits (encoder->frame_length) - 2 - 1; if (setup->total_bits > 1280) { setup->targetBitsInit = setup->targetBitsInit - 1; @@ -435,30 +558,45 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) setup->targetBitsAri = setup->total_bits; setup->enable_lpc_weighting = setup->total_bits < 480; - if (encoder->frame_ms == 7.5) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { setup->enable_lpc_weighting = setup->total_bits < 360; } - if (encoder->frame_ms == 5) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { setup->enable_lpc_weighting = setup->total_bits < 240; } - if (encoder->frame_ms == 2.5) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { setup->enable_lpc_weighting = setup->total_bits < 120; } - +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { + setup->enable_lpc_weighting = setup->total_bits < 60; + } +#endif +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE + if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + setup->quantizedGainOff = calc_GGainOffset_1p25(setup->total_bits, encoder->fs_idx); /* enc/dec common function */ + } + else + { + setup->quantizedGainOff = + -(MIN(115, setup->total_bits / (10 * (encoder->fs_idx + 1))) + 105 + 5 * (encoder->fs_idx + 1)); + } +#else setup->quantizedGainOff = -(MIN(115, setup->total_bits / (10 * (encoder->fs_idx + 1))) + 105 + 5 * (encoder->fs_idx + 1)); - +#endif if (encoder->hrmode && encoder->fs_idx == 5) { setup->quantizedGainOff = MAX(setup->quantizedGainOff, -181); } - if (encoder->frame_ms == 10 && ((encoder->fs_in >= 44100 && setup->targetBytes >= 100) || + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS && ((encoder->fs_in >= 44100 && setup->targetBytes >= 100) || (encoder->fs_in == 32000 && setup->targetBytes >= 81)) && setup->targetBytes < 340 && encoder->hrmode == 0) { setup->attack_handling = 1; } - else if (encoder->frame_dms == 75 && ((encoder->fs_in >= 44100 && setup->targetBytes >= 75) || + else if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_7p5MS && ((encoder->fs_in >= 44100 && setup->targetBytes >= 75) || (encoder->fs_in == 32000 && setup->targetBytes >= 61)) && setup->targetBytes < 150 && encoder->hrmode == 0) { setup->attack_handling = 1; @@ -477,13 +615,18 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) } bitsTmp = setup->total_bits; - if (encoder->frame_ms == 2.5) { +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { + bitsTmp = bitsTmp * 8.0 * 0.42; + } +#endif + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { bitsTmp = bitsTmp * 4.0 * (1.0 - 0.4); } - if (encoder->frame_ms == 5) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { bitsTmp = bitsTmp * 2 - 160; } - if (encoder->frame_ms == 7.5) { + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { bitsTmp = round(bitsTmp * 10 / 7.5); } @@ -508,68 +651,89 @@ LC3PLUS_Error update_enc_bitrate(LC3PLUS_Enc* encoder, int bitrate) } else { encoder->sns_damping = 0.6; if (encoder->fs_idx >= 4) { - if (encoder->frame_ms == 10) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { if (setup->total_bits > 4400) { encoder->sns_damping = 6881.0/32768.0; } } - if (encoder->frame_ms == 7.5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { if (setup->total_bits > 3*4400/4) { encoder->sns_damping = 5898.0/32768.0; } } - if (encoder->frame_ms == 5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { if (setup->total_bits > 4600/2) { encoder->sns_damping = 4915.0/32768.0; } } - if (encoder->frame_ms == 2.5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { if (setup->total_bits > 4600/4) { encoder->sns_damping = 4915.0/32768.0; } } +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + assert(0); + if (setup->total_bits > 4600/4) { + encoder->sns_damping = 4915.0/32768.0; + } + } +#endif } } if (encoder->hrmode && encoder->fs_idx >= 4) { - int real_rate = setup->targetBytes * 8000 / encoder->frame_ms; + int real_rate = setup->targetBytes * 8000 / (encoder->frame_ms * 1.25); setup->regBits = real_rate / 12500; if (encoder->fs_idx == 5) { - if (encoder->frame_ms == 10) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { setup->regBits +=2; } - if (encoder->frame_ms == 7.5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { setup->regBits +=1; } - if (encoder->frame_ms == 2.5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { setup->regBits -= 6; } +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + assert(0); + } +#endif } else { - if (encoder->frame_ms == 2.5) +#ifdef CR9_C_ADD_1p25MS + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) + { + assert(0); + } +#endif + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_2p5MS) { setup->regBits -= 6; } - else if (encoder->frame_ms == 5) + else if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_5MS) { setup->regBits += 0; } - if (encoder->frame_ms == 7.5) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_7p5MS) { setup->regBits +=2; } - if (encoder->frame_ms == 10) + if (encoder->frame_ms == LC3PLUS_FRAME_DURATION_10MS) { setup->regBits += 5; } diff --git a/lib_lc3plus/setup_enc_lc3plus.h b/lib_lc3plus/setup_enc_lc3plus.h index 7947a0e649..1f1656f365 100644 --- a/lib_lc3plus/setup_enc_lc3plus.h +++ b/lib_lc3plus/setup_enc_lc3plus.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -13,11 +13,19 @@ #include "options.h" #include "wmc_auto.h" #include "constants.h" +#include "lc3plus.h" /* Channel state and bitrate-derived values go in this struct */ typedef struct { LC3_FLOAT targetBitsOff; +#ifdef CR9_C_ADD_1p25MS + LC3_FLOAT ltpf_mem_normcorr[LEN_MEM_NORMCORR]; +#else LC3_FLOAT ltpf_mem_normcorr; +#endif +#ifdef FIX_TX_RX_STRUCT_STEREO + LC3_INT16 Tx_ltpf; +#endif LC3_FLOAT ltpf_mem_mem_normcorr; LC3_FLOAT attdec_filter_mem[2]; LC3_FLOAT attdec_acc_energy; @@ -83,8 +91,8 @@ struct LC3PLUS_Enc { LC3_INT frame_length; /* audio samples / frame */ LC3_INT channels; /* number of channels */ LC3_INT epmode; /* error protection mode */ - LC3_FLOAT frame_ms; /* frame length in ms (wrong for 44.1) */ - LC3_INT frame_dms; /* frame length in ms * 10 (wrong for 44.1) */ + LC3PLUS_FrameDuration frame_ms; /* enum for frame length in ms (wrong for 44.1) */ + LC3PLUS_FrameDuration frame_dms; /* enum for frame length in ms * 10 (wrong for 44.1) */ LC3_INT tilt; LC3_INT lc3_br_set; LC3_INT yLen; @@ -109,10 +117,14 @@ struct LC3PLUS_Enc { LC3_INT bw_ctrl_active; LC3_INT bw_ctrl_cutoff_bin; LC3_INT bw_index; - LC3_FLOAT sns_damping; LC3_INT attdec_nblocks; - LC3_FLOAT attdec_damping; LC3_INT attdec_hangover_thresh; +#ifndef FIX_TX_RX_STRUCT_STEREO + LC3_INT16 Tx_ltpf; +#endif + LC3_FLOAT long_term_norm_corr; + LC3_FLOAT attdec_damping; + LC3_FLOAT sns_damping; LC3_INT16 combined_channel_coding; LC3_INT16 epmr; diff --git a/lib_lc3plus/sns_compute_scf.c b/lib_lc3plus/sns_compute_scf.c index d81c61c42a..083175a7db 100644 --- a/lib_lc3plus/sns_compute_scf.c +++ b/lib_lc3plus/sns_compute_scf.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,18 +11,53 @@ #include "wmc_auto.h" #include "functions.h" -void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_INT smooth, LC3_FLOAT sns_damping, LC3_FLOAT attdec_damping_factor, LC3_INT fs_idx) +#ifdef CR9_C_ADD_1p25MS +static float limitShaping (LC3_FLOAT* xl4 ) { + LC3_FLOAT fac; + LC3_FLOAT score; + LC3_FLOAT min_fac; + LC3_FLOAT max_fac; + LC3_FLOAT start; + LC3_FLOAT stop; + + min_fac = 1.f; + max_fac = 0.3f; + start = 5.f; + stop = 8.f; + + score = ((2.f*(xl4[0]-xl4[1])) + (xl4[0]-xl4[2])) / 2.f; + score = fmin(fmax(score, start), stop); + + fac = (stop-score)/(stop-start); + return ((min_fac - max_fac) * fac + max_fac); +} +#endif + +void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_INT smooth, LC3_FLOAT sns_damping, LC3_FLOAT attdec_damping_factor, LC3_INT fs_idx +#ifdef CR9_C_ADD_1p25MS + , LC3PLUS_FrameDuration frame_dms, LC3_FLOAT *LT_normcorr, LC3_FLOAT normcorr +#endif + ) { LC3_INT bands_number, d, i, j, n, n2, n4, mapping[64]; - LC3_FLOAT x_tmp1[MAX_LEN], sum, mean, nf, gains_smooth[M], ratio; - LC3_FLOAT sum_gains_smooth; - const LC3_FLOAT* sns_preemph; + LC3_FLOAT x_tmp1[MAX_LEN], sum = 0, mean, nf, gains_smooth[M], ratio; + LC3_FLOAT sum_gains_smooth = 0; +#ifdef CR9_C_ADD_1p25MS + LC3_FLOAT fac; + LC3_FLOAT start; + LC3_FLOAT limiterGain; +#endif + const LC3_FLOAT *sns_preemph_adapt, *sns_preemph; + bands_number = xLen; - sum_gains_smooth = 0; sum = 0; sns_preemph = sns_preemph_all[fs_idx]; - bands_number = xLen; - assert(bands_number <= 64); +#ifdef CR9_C_ADD_1p25MS + limiterGain = 1.f; + sns_preemph_adapt = sns_preemph_adaptMaxTilt_all[fs_idx]; +#else + (void) sns_preemph_adapt; +#endif /* 5 ms */ if (bands_number < 64) { @@ -90,9 +125,29 @@ void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_I x[63] = 0.5 * x[63] + 0.25 * (x_tmp1[63] + x[63]); /* Pre-emphasis */ +#ifdef CR9_C_ADD_1p25MS + if (sns_preemph_adapt != NULL && frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { + *LT_normcorr = normcorr * 0.125f + *LT_normcorr * 0.875f; + + start = 0.8f; /* adaptive preemphasis active from start to 1.0 */ + fac = (fmax(*LT_normcorr - start, 0) * (1. / (1.-start))); + + for (i = 0; i < 64; i++) { + x[i] = x[i] * (sns_preemph[i] + fac*sns_preemph_adapt[i]); + } + } + else + { + for (i = 0; i < 64; i++) { + x[i] = x[i] * sns_preemph[i]; + } + } +#else for (i = 0; i < 64; i++) { x[i] = x[i] * sns_preemph[i]; } +#endif /* Noise floor at -40dB */ for (i = 0; i < 64; i++) { @@ -139,17 +194,57 @@ void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_I gains_smooth[n] = sum; sum_gains_smooth += sum; } - - +#ifdef CR9_C_ADD_1p25MS + /* limit shaping */ + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { + limiterGain *= limitShaping(gains_smooth); + } +#endif /* Remove mean and scaling */ mean = sum_gains_smooth / 16.0; for (i = 0; i < 16; i++) { +#ifdef CR9_C_ADD_1p25MS + gains[i] = limiterGain * sns_damping * (gains_smooth[i] - mean); +#else gains[i] = sns_damping * (gains_smooth[i] - mean); +#endif } /* Smoothing */ - if (smooth) { +#ifdef CR9_C_ADD_1p25MS_LRSNS + if (frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) + { /* smoothing loop for 1.25 ms */ + + const LC3_FLOAT A0 = 3.0 / 16.0; /* 2/16= 0.125, 3/16 = 0.1875 */ + const LC3_FLOAT A1 = (1.0 - 2 * A0); + const LC3_FLOAT A2 = A0; + + gains_smooth[0] = A0 * (gains[0] + gains[1]) * 0.5 + A1 * gains[0] + A2 * gains[1]; + /* BASOP-loop:: preload gains[-1] with 0.5*(gains[0]+gains[1]) */ + for (i = 1; i < (M - 1); i++) + { + gains_smooth[i] = A0 * gains[i - 1] + A1 * gains[i] + A2 * gains[i + 1]; + } + gains_smooth[M - 1] = A0 * gains[M - 2] + A1 * gains[M - 1] + A2 * 0.5 * (gains[M - 2] + gains[M - 1]); + /* BASOP-loop :: preload gains[M] with 0.5*(gains[M-2]+gains[M-1]) */ + sum = 0; + for (i = 0; i < M; i++) + { + sum += gains_smooth[i]; + } + + mean = sum / (LC3_FLOAT)M; + for (i = 0; i < M; i++) + { + gains[i] = attdec_damping_factor * (gains_smooth[i] - mean); + } + } + else if (smooth == 1) /* original attack smoothing */ +#else + if (smooth) +#endif + { gains_smooth[0] = (gains[0] + gains[1] + gains[2]) / 3.0; gains_smooth[1] = (gains[0] + gains[1] + gains[2] + gains[3]) / 4.0; diff --git a/lib_lc3plus/sns_interpolate_scf.c b/lib_lc3plus/sns_interpolate_scf.c index 15ae55c12a..d421154f53 100644 --- a/lib_lc3plus/sns_interpolate_scf.c +++ b/lib_lc3plus/sns_interpolate_scf.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -70,7 +70,8 @@ void processSnsInterpolateScf_fl(LC3_FLOAT* gains, LC3_INT encoder_side, LC3_INT } move_float(gains_int, tmp, bands_number); - } else { + } + else { assert(0 && "Unsupported number of bands!"); } } diff --git a/lib_lc3plus/sns_quantize_scf.c b/lib_lc3plus/sns_quantize_scf.c index 96d8cdb4b6..e91f0ad614 100644 --- a/lib_lc3plus/sns_quantize_scf.c +++ b/lib_lc3plus/sns_quantize_scf.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -11,8 +11,158 @@ #include "wmc_auto.h" #include "functions.h" +#ifdef CR9_C_ADD_1p25MS_LRSNS + +static LC3_INT16 get_lead_sign( LC3_UINT32* ind_in ); +static void mind2vec_one( LC3_UINT8 k_val_in, LC3_INT16 leading_sign, LC3_INT32* vec_out ); + +LC3_INT16 get_lead_sign( LC3_UINT32* ind_in ) +{ + LC3_INT16 leading_sign; + leading_sign = +1; + if ( ( ( *ind_in ) & 0x1 ) != 0 ) + { + leading_sign = -1; + } + ( *ind_in ) = ( *ind_in >> 1 ); + + return leading_sign; +} + +void mind2vec_one( LC3_UINT8 k_val_in, /* i: nb unit pulses */ + LC3_INT16 leading_sign, /* i: leading sign -1, 1 */ + LC3_INT32* vec_out /* o: updated pulse train */ ) +{ + LC3_INT32 amp; + amp = k_val_in; + if ( leading_sign < 0 ) + { + amp = -k_val_in; + } + *vec_out = amp; +} + +static LC3_UINT8 setval_update_sign( LC3_INT16 k_delta, LC3_UINT8 k_max_local_in, LC3_INT16* leading_sign, LC3_UINT32* ind_in, LC3_INT32* vec_out ); + +static void mind2vec_tab( LC3_UINT8 dim_in, LC3_UINT8 k_max_local, LC3_INT16 leading_sign, LC3_UINT32 ind, LC3_INT32* vec_out ); + +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS +static void MPVQdeenum( LC3_UINT8 dim_in, LC3_UINT8 k_val_in, LC3_INT32 LS_ind, LC3_INT32 MPVQ_ind, LC3_INT32* vec_out ); +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +LC3_UINT8 setval_update_sign( LC3_INT16 k_delta, /* i */ + LC3_UINT8 k_max_local_in, /* i */ + LC3_INT16* leading_sign, /* i/o */ + LC3_UINT32* ind_in, /* i/o */ + LC3_INT32* vec_out /* i/o */ ) +{ + LC3_UINT8 k_max_local_out; + k_max_local_out = k_max_local_in; + if ( k_delta != 0 ) + { + mind2vec_one( k_delta, *leading_sign, vec_out ); + *leading_sign = get_lead_sign( ind_in ); + k_max_local_out -= k_delta; + } + + return k_max_local_out; +} + +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +void mind2vec_tab( LC3_UINT8 dim_in, /* i: dimension */ + LC3_UINT8 k_max_local, /* i: nb unit pulses */ + LC3_INT16 leading_sign, /* i: leading sign */ + LC3_UINT32 ind, /* i: MPVQ-index */ + LC3_INT32* vec_out /* o: pulse train */ ) +{ + /* pvq_enc_A = MPVQ_offsets */ + LC3_UINT8 pos, k_acc; + LC3_UINT32 UL_tmp_offset; + LC3_INT32 UL_diff; + LC3_INT16 wrap_flag, k_delta; + const LC3_UINT32* h_row_ptr; + /* init */ + h_row_ptr = &( pvq_enc_A[( dim_in - 1 )][0] ); + k_acc = k_max_local; + + /* loop over positions */ + for ( pos = 0; pos < dim_in; pos++ ) + { + if ( ind != 0 ) + { + k_acc = k_max_local; + UL_tmp_offset = h_row_ptr[k_acc]; + + wrap_flag = ( ind < UL_tmp_offset ); + UL_diff = (LC3_INT32) ( (LC3_INT32) ind - (LC3_INT32) UL_tmp_offset ); + + while ( wrap_flag != 0 ) + { + k_acc--; + wrap_flag = ( ind < h_row_ptr[k_acc] ); + UL_diff = (LC3_INT32) ( (LC3_INT32) ind - (LC3_INT32) h_row_ptr[k_acc] ); + } + ind = UL_diff; + k_delta = k_max_local - k_acc; + } + else + { + mind2vec_one( k_max_local, leading_sign, &vec_out[pos] ); + break; + } + k_max_local = setval_update_sign( k_delta, k_max_local, &leading_sign, &ind, &vec_out[pos] ); + h_row_ptr -= 11; /* reduce dimension in MPVQ_offsets table */ + } +} + +#endif + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +void MPVQdeenum( LC3_UINT8 dim_in, /* i : dimension of vec_out */ + LC3_UINT8 k_val_in, /* i : number of unit pulses */ + LC3_INT32 LS_ind, /* i : leading sign index */ + LC3_INT32 MPVQ_ind, /* i : MPVQ shape index */ + LC3_INT32* vec_out /* o : PVQ integer pulse train */ ) +{ + LC3_INT32 leading_sign; + + + leading_sign = 1; + if ( LS_ind != 0 ) + { + leading_sign = -1; + } + + mind2vec_tab( dim_in, k_val_in, leading_sign, MPVQ_ind, vec_out ); +} + +/* local funcs*/ + +static void FESSdeenum(LC3_UINT8 dim_in, /* i : dimension of vec_out */ + LC3_UINT8 n_env, /* i : number envelopes */ + LC3_UINT8 n_shift, /* i : number shifts */ + LC3_UINT8 n_signs, /* i : number signs */ + LC3_INT32 env_ind, /* i:indx */ + LC3_INT32 shift_ind, /* i:indx */ + LC3_INT32 sign_ind, /* i:indx */ + LC3_INT32* vec_out /* o : FESS integer pulse train */); + +LC3_INT32 snsQuantScfEncLRSt1ABC(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT *min_mse_saveBCA_ptr, + LC3_INT32* ind_saveB_ptr, LC3_FLOAT* st1_vectors, + LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx); + +#endif /* CR9_C_ADD_1p25MS_LRSNS*/ + static void pvq_dec(LC3_INT k, LC3_INT m, LC3_INT LS_ind, LC3_INT MPVQ_ind, LC3_INT* pulses); -static LC3_INT find_last_indice_le(LC3_INT compare, const LC3_INT* array, LC3_INT len); +static LC3_INT find_last_indice_le(LC3_INT compare, const LC3_UINT32* array, LC3_INT len); static void idct_II(LC3_FLOAT* in, LC3_FLOAT* out, LC3_INT len); void idct_II(LC3_FLOAT* in, LC3_FLOAT* out, LC3_INT len) @@ -28,6 +178,87 @@ void idct_II(LC3_FLOAT* in, LC3_FLOAT* out, LC3_INT len) } } +#ifdef FIX_FLOAT_ENC_PVQ_PULSE_LOOP +static LC3_INT32 pvq_pulse_search_lc(LC3_FLOAT* xabs, LC3_FLOAT* ener, LC3_FLOAT* corr, LC3_INT32* y, LC3_INT32 start, LC3_INT32 end) +{ + Dyn_Mem_Deluxe_In( + LC3_INT32 i; + LC3_INT32 nBest; + LC3_FLOAT max_norm_ratio, cand_norm_ratio; + LC3_FLOAT currCorr; + LC3_INT32 currEnInt; + const LC3_FLOAT* isqrt_QxTab; + ); + + const LC3_INT16 isqrt_Q16tab[1 + 64] = { /* table generated using ISqrt16 function + shift to Q16 */ + 32767, 32767, 32767, 32767, 32766, 29308, 26754, 24770, 23169, 21844, + 20723, 19759, 18918, 18176, 17515, 16921, 16383, 15894, 15446, 15034, + 14654, 14300, 13972, 13665, 13377, 13107, 12852, 12612, 12385, 12169, + 11965, 11770, 11584, 11407, 11238, 11077, 10922, 10773, 10631, 10493, + 10361, 10234, 10112, 9993, 9879, 9769, 9662, 9559, 9459, 9362, + 9268, 9176, 9088, 9001, 8918, 8836, 8757, 8680, 8605, 8532, + 8460, 8390, 8323, 8256, 8191 + }; + LC3_FLOAT isqrt_Q16tabFlt[1 + 64]; + + const LC3_INT16 isqrt_Q15tab[1 + 6] = { + /* onepulse_search_tab based search , Q15 table generated using isqrt_Q16tab table for idx=[8(4*2),16(4*4), 20(4*5),24(4*6) ] */ + /* this slighly suboptimal inv_sqrt(x) table is used to enable optional exact use of the ROM saving BASOP ISqrt16() function */ + + 32767/*0*/, 32766/*1*/, 23169 /*2*/, 18918 /*3*/ , 16384/*4*/ , 14654 /*5*/, 13377/*6*/ + /* i.e. value in isqrt_Q15tab[n=0..6] == isqrt_Q16tab[4*n] */ + }; + LC3_FLOAT isqrt_Q15tabFlt[1 + 6]; + + for ( i = 0; i <= 64; i++ ) + { + isqrt_Q16tabFlt[i] = isqrt_Q16tab[i] / 65536.0; + } + for (i = 0; i <= 6; i++) + { + isqrt_Q15tabFlt[i] = isqrt_Q15tab[i] / 32768.0; + } + + + isqrt_QxTab = isqrt_Q16tabFlt; /* Q16 table valid for energies 4...64 */ + + assert( *ener >= 0.0 ); + *ener += 1.0; /* Added once for the entire loop */ + if (*ener <= 3.0 ) /* +1 for the inloop value of *ener was preadded before */ + { + isqrt_QxTab = isqrt_Q15tabFlt; /* energies: 1...6 */ + } + + nBest = -1; + max_norm_ratio = -1.0; + /* Iterative max search using tabulated inv sqrt */ + for ( i = start; i < end; i++) + { + currCorr = *corr + xabs[i]; + currEnInt = ((LC3_INT32) *ener) + (2 * y[i]); + + cand_norm_ratio = currCorr * isqrt_QxTab[currEnInt]; + + if ( cand_norm_ratio >= max_norm_ratio ) + { + nBest = i; + } + max_norm_ratio = LC3_FMAX(max_norm_ratio, cand_norm_ratio); /* always update */ + } + + *corr += xabs[nBest]; + *ener += (2 * y[nBest]); + + assert(nBest >= 0); + assert(nBest <=M); + assert(*ener <= 64.0); + y[nBest] += 1; /* Add the selected unit pulse */ + + Dyn_Mem_Deluxe_Out(); + return nBest; +} +#endif + static LC3_INT pvq_pulse_search(LC3_FLOAT *xabs, LC3_FLOAT *ener, LC3_FLOAT *corr, LC3_INT *y, LC3_INT start, LC3_INT end) { LC3_INT i; @@ -39,7 +270,7 @@ static LC3_INT pvq_pulse_search(LC3_FLOAT *xabs, LC3_FLOAT *ener, LC3_FLOAT *cor bestCorrSq = 0.0; bestEn = 0.0; - *ener += 1; // Added once for the entire loop + *ener += 1; /* Added once for the entire loop */ i = start; @@ -194,7 +425,7 @@ static void pvq_enc_search(LC3_FLOAT* x_in, LC3_INT y[4][M]) return; } -static inline LC3_FLOAT calc_mse(LC3_FLOAT *t2rot, LC3_FLOAT *y, LC3_FLOAT gain, LC3_INT N) +static LC3_FLOAT calc_mse(LC3_FLOAT *t2rot, LC3_FLOAT *y, LC3_FLOAT gain, LC3_INT N) { LC3_FLOAT mse; LC3_INT i; @@ -402,12 +633,12 @@ void process_snsQuantizesScf_Enc(LC3_FLOAT* env, LC3_INT* index, LC3_FLOAT* envq } } -LC3_INT find_last_indice_le(LC3_INT compare, const LC3_INT* array, LC3_INT len) +LC3_INT find_last_indice_le(LC3_INT compare, const LC3_UINT32* array, LC3_INT len) { LC3_INT idx = 0, i = 0; for (i = 0; i < len; i++) { - if (compare >= array[i]) { + if ((LC3_UINT32)compare >= array[i]) { idx++; } } @@ -510,3 +741,1345 @@ void process_snsQuantizesScf_Dec(LC3_INT* scf_idx, LC3_FLOAT* scf_q) scf_q[i] += st2_vector_idct[i] * sns_dec_gains[submode][scf_idx[3]]; } } + +#ifdef CR9_C_ADD_1p25MS_LRSNS + +/* 29/30 bits optimized search functions for PVQ and FESS */ +/* stage 2 submode shape 0: "splitLF" (N=5,K=6)(N=8,K=2) or (N=5,K=8)(N=8,K=0) , 4 gains */ +/* stage 2 submode shape 1: "full" FB (N=15,K=5), 4xfixed , 8 gains */ +/* stage 2 submode shape 2-5: "fixed env " (N=13-15,K=10-12), 4xfixed , 8 gains */ + +static void pvq_fess_enc_search(LC3_FLOAT* x_in /* i: 0...M-1 */ + , LC3_INT32 y[SNSLR_MAX_PVQ_SEARCH_CAND][M], /* o: [3]*[0...M-1] */ + LC3_INT32 *fixShapeNb /* o: [-1, 0...3] */ +) +{ + LC3_INT32 i, j, curr_cand; + LC3_INT32 NcandLF, Ngrp, Kgrp, Ktop_LF, Ntop_LF; + LC3_INT32 NcandFB, Kfull; + LC3_INT32 NcandFix, Kfix, Nsigns_fix; + + LC3_INT32 N, K, pulse_total, top_pulses; + LC3_FLOAT abs_sum, projfac; + LC3_FLOAT yy, xy; /* in-loop energy and corr */ + + LC3_FLOAT yy_n5k8, xy_n5k8; + LC3_FLOAT xabs[M]; + LC3_INT32 y_tmp_n5k8[M]; + LC3_INT32 y_tmp[M]; + LC3_INT16 n, fix_ind, shift_ind; + LC3_INT16 best_env_ind; + LC3_INT16 best_shift_ind; + LC3_INT16 best_ind; + LC3_INT16 pulse_total_n5k8; + LC3_FLOAT f_normcorr_fixenv[SNSLR_N_FIXENV][SNSLR_N_FIXENV_SHIFTS]; + LC3_FLOAT *p; + const LC3_INT32* env_ptr; + +#define SC 1 /*Starting coeff position of PVQ target , (we have no DC) */ + { + /* no DC coeff , only coeff 1..15 is quantized in the range 0..15 */ + NcandLF = 1; + + Ngrp = 5; /* PVQ(5, 6) = 10.94b , + P(8,2)=7b + P(2,0)=0b */ + Kgrp = 6; + Ntop_LF = 8; /* allow to encode less than M-sc -Ngrp */ + Ktop_LF = 2; /* fill a few unit pulses in the HF region */ + /* P(5,8)+P(10,0) will also fit in 13 bits */ + /* Fullband safety net */ + NcandFB = 1; + Kfull = 5; ; /* PVQ(15,5)= 16.65 bits , */ + + /*fixed env */ + NcandFix = 4; /* 4th only has 10 signs */ + Kfix = 12; /* number of signs */ + Nsigns_fix = 12; /* area to cover and number of signs */ + } + + for (i = SC; i < (M); i++) + { + xabs[i] = LC3_FABS(x_in[i]); + } + xabs[0] = 0; + + /* splitLF LFfocus subshape idx 0 */ + if (Kgrp > 0 && NcandLF > 0) + { + curr_cand = 0; + /* Projection to LF pyramid N=Ngrp, K=Kgrp */ + abs_sum = 0.0; + N = Ngrp; + K = Kgrp; + pulse_total = 0; + + for (i = SC; i < (SC + N); i++) + { + abs_sum += xabs[i]; + } + projfac = (K - 1) / abs_sum; + + if (abs_sum == 0.0) + { + projfac = 0.0; + } + + yy = xy = 0.0f; + for (i = SC; i < (SC + N); i++) + { + y_tmp[i] = LC3_FLOOR(xabs[i] * projfac); /* local projection within group over N coeffs used here */ + + pulse_total += y_tmp[i]; + + yy += (y_tmp[i] * y_tmp[i]); + xy += (xabs[i] * y_tmp[i]); + } + + /* LF Adding unit pulses up to K in LF */ + for (; pulse_total < K; pulse_total++) + { + pvq_pulse_search(xabs, &yy, &xy, y_tmp, SC, SC + N); + } + + { + /* allow (N=5,K=(6+2)=8) in low band only, higher part is then zeroed */ + /* can be multiplexed put in 13 bits */ + yy_n5k8 = yy; + xy_n5k8 = xy; + pulse_total_n5k8 = pulse_total; + + memset(y_tmp_n5k8, 0, sizeof(*y_tmp_n5k8)*M); + memcpy(y_tmp_n5k8, y_tmp, sizeof(*y_tmp)*(SC + N)); /* cpy LF initial search result for n5k6 to n5k8 output */ + + for (; pulse_total_n5k8 < (K + 2); pulse_total_n5k8++) + { + pvq_pulse_search(xabs, &yy_n5k8, &xy_n5k8, y_tmp_n5k8, SC, SC + N); + } + } + + + /* split HF : longer M vector */ + memset(y[curr_cand], 0, sizeof(*y_tmp)*M); /*zero full candidate vector */ + memcpy(&(y[curr_cand]), y_tmp, sizeof(*y_tmp)*(SC + N)); /* cpy LF-part to output */ + + + if (Ktop_LF && (M - Ngrp - SC >= Ntop_LF)) + { + + /* xy = 0.0; yy = 0.0; */ + memset(&(y_tmp[Ngrp]), 0, (M - SC - Ngrp) * sizeof(*y_tmp)); + top_pulses = 0; + + for (; top_pulses < Ktop_LF; top_pulses++) + { + pvq_pulse_search(xabs, &yy, &xy, y_tmp, SC + N, SC + N + Ntop_LF); + } + + for (j = SC + N; j < (SC + N + Ntop_LF); j++) + { + y[curr_cand][j] = y_tmp[j]; + } + } + + { + /* shape 0 : decide on best split alternative "0a" n5k6 + n8k2 + n2k0 or "0b" n5k8 + n10k0, + shape only assessed , i.e. assume same gain quantizer */ + if ((xy_n5k8*xy_n5k8)*yy > (xy*xy)*yy_n5k8) + { + /* use n5k8, with zeroed high band */ + /* i.e. better to put two additional pulses in the n5 low band than in the higher band */ + memcpy(&(y[curr_cand]), y_tmp_n5k8, sizeof(*y_tmp_n5k8)*(M)); /* cpy LFonly to output */ + } + } + + /* assign signs to the cand vector from x_in */ + for (i = SC; i < (M); i++) + { + if (x_in[i] < 0) + { + y[curr_cand][i] *= -1; + } + } + y[curr_cand][0] = 0; /* DC */ + + } + + /* shape 1 : Full band shape analysis */ + if (Kfull > 0 && NcandFB > 0) + { + curr_cand = 1; + N = M - SC; + K = Kfull; + + /* project */ + pulse_total = 0; + yy = xy = 0.0f; + abs_sum = 0.0; + for (i = SC; i < SC + N; i++) + { + abs_sum += xabs[i];/* over N */ + } + + projfac = (K - 1) / abs_sum; + if (abs_sum == 0.0) + { + projfac = 0.0; + } + + + memset(y_tmp, 0, sizeof(*y_tmp)*M); + for (i = SC; i < SC + N; i++) + { + y_tmp[i] = LC3_FLOOR(xabs[i] * projfac); /* projection over N coeffs */ + pulse_total += y_tmp[i]; + yy += (y_tmp[i] * y_tmp[i]); + xy += (xabs[i] * y_tmp[i]); + } + + for (; pulse_total < K; pulse_total++) + { + pvq_pulse_search(xabs, &yy, &xy, y_tmp, SC, SC + N); + } + memcpy(&(y[curr_cand]), y_tmp, sizeof(*y_tmp)*M); /* cpy to output */ + + /* Step: Add signs to each of the full vector from input */ + for (i = SC; i < M; i++) + { + if (x_in[i] < 0) + { + y[curr_cand][i] *= -1; ; + } + } + y[curr_cand][0] = 0; + } /* Fullband */ + + + { /* fixed envelopes shifted signs shapes 2 ... 5 */ + best_env_ind = -1; + best_shift_ind = -1; + + /* search fixed env shapes first */ + /* search shape of all fixed flat-ish envelopes using an optimal nb_env*nbshifts shape analysis */ + if (Kfix > 0 && NcandFix > 0) + { + curr_cand = 2; + N = Nsigns_fix; + K = Kfix; + + /* find the minimum shape error across all possible 4 fixed envelopes and all 4 shifts */ + /* maximise normalized cross correlation target*y *(1/sqrt(y^2)) to minimize shape error */ + + for (fix_ind = 0; fix_ind < SNSLR_N_FIXENV; fix_ind++) + { + for (shift_ind = 0; shift_ind < SNSLR_N_FIXENV_SHIFTS; shift_ind++) + { + f_normcorr_fixenv[fix_ind][shift_ind] = 0.0; + for (n = 0; n < signs_fix[fix_ind]; n++) + { + f_normcorr_fixenv[fix_ind][shift_ind] += (xabs[SC + n + shift_ind] * env_ptrs[fix_ind][n + shift_ind]); + } + /* energy normalize, for the specific shift and env, + otherwise it is not possible to compare among the shifted envelopes properly */ + + f_normcorr_fixenv[fix_ind][shift_ind] *= shift_en_norm_factors[fix_ind][shift_ind]; + } + } + + /* now actually pick the env[0,1,2,3] and env shift[0,1,2,3] option which maximizes the normcorr + (minimizes shape mse as all fixed envelopes have the same gain quantizer) */ + p = &(f_normcorr_fixenv[0][0]); + best_ind = 0; + for (n = 1; n < (SNSLR_N_FIXENV * SNSLR_N_FIXENV_SHIFTS); n++) + { + if (p[n] > p[best_ind]) + { + best_ind = n; + } + } + best_env_ind = best_ind / SNSLR_N_FIXENV_SHIFTS; + best_shift_ind = best_ind - best_env_ind * SNSLR_N_FIXENV; + assert(best_env_ind >= 0 && best_env_ind < SNSLR_N_FIXENV); + assert(best_shift_ind >= 0 && best_shift_ind < SNSLR_N_FIXENV_SHIFTS); + } + /* o: best_shift_ind; o: best_env_ind */ + + *fixShapeNb = best_env_ind; /* best normalized correlation out of the 4x4 = 16 envelope options */ + /*best shift ind, later re-established/found via output vector y[2] */ + + /* Fixed envelope "flat-ish" signband coding , including sign coding of shifted block */ + /* submode idx 2,3,4 , "1"/env 1(s0)+ 2(shift)+ 11bits(s1..s11) + always a 3 bits gain */ + /* submode idx 5 , "1"/env 1(s0)+ 2(shift)+ 9bits(s1..s9) + always a 3 bits gain */ + /* + 2, init_bell_12signs , [ 8,8,8, 7,7 ... ] + 3, decaying envelope 12 signs, [ 12,12,11,11, ... ] + 4, start_bell_12signs ,[ 7,7,8,8,8, 7,... ] + 5, early_bell_10signs,[ 6,6, 7,7,8,8,8,7,... ] + */ + + { + /* construct the selected fix shape with its corresponding shift */ + memset(y[curr_cand], 0, sizeof(LC3_INT32)*M); /* zero output */ + env_ptr = env_ptrs[best_env_ind]; + + Nsigns_fix = signs_fix[best_env_ind]; + + /* now assign unit amplitude and signs to each of the fixed env vector from x_in in the band */ + for (i = best_shift_ind; i < (Nsigns_fix + best_shift_ind); i++) + { + y[curr_cand][SC + i] = env_ptr[i]; + } + for (i = (SC + best_shift_ind); i < (SC + Nsigns_fix + best_shift_ind); i++) + { + if (x_in[i] < 0) + { + y[curr_cand][i] *= -1; + } + } + y[curr_cand][0] = 0; + } + } + + return; +} + +static void lrsns_quant_shape_gain( + LC3_FLOAT* t2rot, LC3_INT32 y[SNSLR_MAX_PVQ_SEARCH_CAND][M], + LC3_INT32* gain_idx, LC3_INT32 *shape_idx, LC3_FLOAT* y_norm, LC3_FLOAT* scq_gain, LC3_INT32 n_cand ) +{ + LC3_INT32 gidx, sidx; + LC3_FLOAT min_mse, mse; + + LC3_INT32 i; + LC3_INT16 start_shape; + LC3_INT16 end_shape; + LC3_INT32 gain_levels[SNSLR_MAX_PVQ_SEARCH_CAND]; + LC3_FLOAT yCur[SNSLR_MAX_PVQ_SEARCH_CAND][M]; + const LC3_FLOAT *lrsns_vq_gains[SNSLR_MAX_PVQ_SEARCH_CAND]; + + gain_levels[0] = 4; /* splitLF */ + gain_levels[1] = 8; /* full */ + gain_levels[2] = 8; /* Fix-env{ 0,1,2,3,4} */ + + lrsns_vq_gains[0] = &(lrsns_gains_Q11[0][0]); + lrsns_vq_gains[1] = &(lrsns_gains_Q11[1][0]); + lrsns_vq_gains[2] = &(lrsns_gains_Q11[2][0]); + + min_mse = LC3_CONST_FLOATMAX; + + + *gain_idx = 0; + *shape_idx = 0; + start_shape = 0; + end_shape = n_cand; + + for (sidx = start_shape; sidx < end_shape; sidx++) + { + /* Normalize the vectors */ + for (i = 0; i < M; i++) + { + yCur[sidx][i] = (LC3_FLOAT)y[sidx][i]; + } + pvq_enc_vec_normalize(yCur[sidx], M); + + for (gidx = 0; gidx < gain_levels[sidx]; gidx++) + { + mse = calc_mse(t2rot, yCur[sidx], lrsns_vq_gains[sidx][gidx], M); + + if (mse < min_mse) + { + *gain_idx = gidx; + *shape_idx = sidx; + min_mse = mse; + } + } + + } + + for (i = 0; i < M; i++) + { + y_norm[i] = yCur[*shape_idx][i]; + } + + *scq_gain = lrsns_vq_gains[*shape_idx][*gain_idx]; + + return; +} + +LC3_INT32 MSEsearchGeneric(LC3_FLOAT *scf, const LC3_FLOAT *sns_CB, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse) +{ + + LC3_FLOAT f_tmp, mse; + LC3_INT32 i, n, ind; + + ind = -1; + + *min_mse = (LC3_FLOAT)LC3_CONST_POW_2_100; + for (i = 0; i < cb_len; i++) + { + mse = 0; + for (n = 0; n < v_len; n++) + { + f_tmp = (scf[n] - sns_CB[i * v_len + n]); + mse += (f_tmp * f_tmp); + } + + if (mse < *min_mse) + { + *min_mse = mse; + ind = i; + } + } + assert(ind >= 0 && ind < cb_len); + + return ind; +} + +void snslr_st1B_vector_dec(LC3_INT16 idx, const LC3_INT16* LFCB, const LC3_INT16 *HFCB, const LC3_INT16* seg_cnt_cum, const LC3_INT16* idx12b_cb, LC3_FLOAT *st1B_vector) +{ + /* decompose the received 0 ... 169 index , into the correct intger and float st1B vector */ + LC3_INT32 i, seg; /*counters*/ + const LC3_INT16 *lf_cb, *hf_cb; + LC3_INT16 idx_12b, lf_sign, hf_sign; + LC3_INT16 idx_LF, idx_HF; + LC3_INT16 buf[M]; + LC3_INT16 st1B_W16Q11[M]; + LC3_FLOAT f_Q11_scale = (1.0 / 2048.0); + + assert(idx >= 0 && idx < 170); + seg = 0; + while (seg_cnt_cum[seg + 1] <= idx) { + seg++; + } + assert(seg >= 0 && seg < 4); + + idx_12b = idx12b_cb[idx]; /* from sequential value to a coded 12b index*/ + + lf_sign = 1; /* assume a 0 bit -> "+" */ + if ((0x0800 & idx_12b) != 0) { + lf_sign = -1; /* assume a 1 bit -> " -" */ + } + + hf_sign = 1; /* assume a 0 bit -> "+" */ + if ((0x0400 & idx_12b) != 0) { + hf_sign = -1; /* assume a 1 bit -> "-" */ + } + idx_LF = (0x03e0 & idx_12b) >> 5; + idx_HF = (0x001f & idx_12b); + + /* extseg0 f,f */ + lf_cb = &(LFCB[idx_LF*(M / 2)]); + hf_cb = &(HFCB[idx_HF*(M / 2)]); + for (i = 0; i < (M / 2); i++) + { + st1B_W16Q11[i] = lf_sign * lf_cb[i]; /* imult() or negate */ + st1B_W16Q11[M / 2 + i] = hf_sign * hf_cb[i]; + } + memcpy(buf, st1B_W16Q11, sizeof(*buf)*M); /* buffer cpy needed for reversal sections */ + + if ((seg & 0x0002) != 0) + { /* r,* */ /* flip LF */ + for (i = 0; i < (M / 2); i++) + { + st1B_W16Q11[i] = buf[(M / 2 - 1) - i]; + } + } + + if ((seg & 0x0001) != 0) + { /* *,r */ /* flip HF */ + for (i = 0; i < (M / 2); i++) + { + st1B_W16Q11[(M / 2) + i] = buf[(M - 1) - i]; + } + } + + /* Cfloat: convert the 16bit integer Q11 values from LFCB, and HFCB into floats */ + for (i = 0; i < M; i++) + { + st1B_vector[i] = ((LC3_FLOAT)st1B_W16Q11[i]) * f_Q11_scale; + } +} + +LC3_INT32 MSEsearchCbBIdxMap(const LC3_FLOAT *scf, const LC3_INT16 *LFCB, const LC3_INT16 *HFCB, const LC3_INT16 *seg_cnt_cum, const LC3_INT16* idx12b_cb, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse) +{ + LC3_INT32 seg, i, j; /*counters */ + LC3_INT32 L_mse; + LC3_INT16 scfLF_Q11[M], tmp_buf[M]; + LC3_INT16* scfHF_Q11; + const LC3_INT16 *lf_cb, *hf_cb; + LC3_INT16 err, best_ind, idx_12b, signbitLF, signbitHF, idx_LF, idx_HF; + LC3_INT32 L_mse_best = INT_MAX; /*a huge positive number */ + + UNUSED(cb_len); /*cb_len only used for assert/verification */ + + assert(v_len == M); + + scfHF_Q11 = (&scfLF_Q11[v_len / 2]); /* ptr init */ + + /*set up fwd,fwd search */ + for (i = 0; i < M; i++) + { + tmp_buf[i] = (LC3_INT16)LC3_ROUND(scf[i] * 2048.0); /* scf target moved to BASOP signed Word16Q11 integer domain */ + } + + best_ind = -1; /*for debug*/ + for (seg = 0; seg < 4; seg++) + { + memcpy(scfLF_Q11, tmp_buf, M * sizeof(*scfLF_Q11)); /* M * move16() */ + /*seg==0: fwd, fwd */ + /*seg==1: fwd, rev */ + /*seg==2: fwd, fwd */ + /*seg==3: rev, rev */ + + if ((seg & 0x0002) != 0) + { /* {r,*} */ /* flip LF */ + for (i = 0; i < (M / 2); i++) + { + scfLF_Q11[i] = tmp_buf[(M / 2 - 1) - i]; + } + } + if ((seg & 0x0001) != 0) + { /* {*,r} */ /* flip HF */ + for (i = 0; i < (M / 2); i++) + { + scfLF_Q11[M / 2 + i] = tmp_buf[(M - 1) - i]; + } + } + + for (i = seg_cnt_cum[seg]; i < seg_cnt_cum[seg + 1]; i++) + { + idx_12b = idx12b_cb[i]; /* indirect adressing lookup of 12b index pointing to LF and HF + individual sign swaps */ + signbitLF = (0x0800 & idx_12b); /* b11 logical */ + signbitHF = (0x0400 & idx_12b); /* b10 logical */ + idx_LF = (0x03e0 & idx_12b) >> 5; /* b9...b5 */ + idx_HF = (0x001f & idx_12b); /* b4..b0 lowest 5 bits */ + + lf_cb = &(LFCB[idx_LF * M / 2]); /* ptr init */ + hf_cb = &(HFCB[idx_HF * M / 2]); /* ptr init */ + + L_mse = 0; /* move32() */ /* we accumulate energies on the positive side */ + for (j = 0; j < (M / 2); j++) + { + err = (scfLF_Q11[j] - lf_cb[j]); /* a "+"sign, LF err in Q11 */ + if (signbitLF != 0) + { /* negate LF*/ + err = (scfLF_Q11[j] + lf_cb[j]); /* "-""-" --> "+" LF err in Q11 ,single BASOP */ + } + L_mse += (err * err); /* simulate L_mac0 , accumulate towards max positive side */ + + err = (scfHF_Q11[j] - hf_cb[j]); /* a "+"sign, HF err in Q11 */ + if (signbitHF != 0) + { /* negate HF */ + err = (scfHF_Q11[j] + hf_cb[j]); /* -- --> "+", LF err in Q11 ,single BASOP */ + } + L_mse += (err * err); /*simulate L_mac0 */ /* now total error */ + } + + L_mse_best = MIN(L_mse, L_mse_best); /* 1 cycle BASOP preupdate best */ + if ((L_mse - L_mse_best) == 0) + { + best_ind = i; /* update winner, single BASOP */ + } + } + }/* segment seg */ + + *min_mse = (LC3_FLOAT)((double)L_mse_best) / (2048.0*2048.0); /* Word32 mse -> Cfloat output calculation */ + + assert(best_ind >= 0 && best_ind < cb_len); + + return (LC3_INT32)best_ind; +} + +void snslr_st1C_vector_dec(LC3_INT16 idx, const LC3_INT8* CBW8, LC3_INT16 scaleQ4, LC3_INT16 inv_scaleQ15, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT *st1C_vector) +{ + /* decompose the received 0... 169 index , into the correct (integer and) float st1C vector */ + /* even in C-float the st1C coeffs are put into a S16Q11 final integers domain */ + /* Enables BE compatibility between {BASOP, float, double} arithmetic implmentations */ + + + LC3_INT32 i; /*counter*/ + const LC3_INT8 *cb; + LC3_INT32 L_tmp; + LC3_INT16 s_tmp, st1C_Q11[M]; + + UNUSED(v_len); + UNUSED(cb_len); + UNUSED(inv_scaleQ15); /* req for debugging only */ + assert(idx >= 0 && idx < cb_len); + assert(v_len == M); + + cb = &(CBW8[idx*M]); /* pointer init */ + for (i = 0; i < (M); i++) + { + /* BASOP: L_tmp = L_mult0((int16_t)cb[i], scaleQ4 ); */ /*S8Q7 * S15Q4 */ /*sign+7bit, sign+4 bits --> sign+11bit .lt sign+23 bits*/ + L_tmp = ((int16_t)cb[i] /*S8Q7*/ * scaleQ4 /* S16Q4 */); /* S32Q11 */ + + /* Cfloat: convert to Word16 Q11 integer as Word16 Q11 SNS domain bits, and then into floats */ + assert(L_tmp >= -32768L && L_tmp <= 32767L); /* INT16 domain check*/ + s_tmp = (int16_t)(L_tmp); + st1C_Q11[i] = s_tmp; + } + + /* Cfloat: convert the Word16 Q11 SNS domain bits, and then into floats */ + for (i = 0; i < M; i++) + { + st1C_vector[i] = ((LC3_FLOAT)st1C_Q11[i])*(1.0 / 2048.0); + } +} + +LC3_INT32 MSEsearchGenericScaledW8(LC3_FLOAT *scf, const LC3_INT8 *sns_CBW8, LC3_INT16 scaleQ4, LC3_INT16 inv_scaleQ15, LC3_INT32 v_len, LC3_INT32 cb_len, LC3_FLOAT* min_mse) +{ + /* scf float input values are typically in the range +12.0 to -12.0. + ROM table stored in WORD8 [+127,-128], format corresponding to ]+1.0 .. -1.0 ] + inv_scaleQ15, [downscaling value in Q15] applied before search + scaleQ12 upscaling value quantized in Q12, used in the mse calulation and in the common float and BASOP synthesis routines + + L_mse evaluated here in a positive integer Word32 domain to match BASOP + + Fuzzing/saturation considerations + max M==16 values (4 bits) yields 16*(256*256)=>2^(4+8+8)=2^20 .lt 2^31, + IntegerWord32 is a safe search domain + + a WC input analysis would be when half target entries are "-256" and half +255 + + */ + LC3_INT32 i, n, best_ind; /*counters*/ + LC3_INT32 L_mse, L_mse_best; + LC3_INT16 targetW16[M], err; + LC3_FLOAT f_tmp, f_scale; + const LC3_INT8 * cbW8; + + f_scale = ((LC3_FLOAT)inv_scaleQ15) / 32768.0; + for (i = 0; i < v_len; i++) { + f_tmp = (scf[i] * f_scale); + assert(f_tmp >= -4.0 && f_tmp < 4.0); /* check for about 2 bit integer margin in the target */ + targetW16[i] = (LC3_INT16)LC3_FLOOR(f_tmp*128.0); /*cast to INT16 W8Q7 */ + }; + /* in BASOP 1/32768 and *128 is handled by one single shift) */ + + L_mse_best = INT_MAX; /* largest possible positive number in INT32 */ + best_ind = -1; + + for (i = 0; i < cb_len; i++) { + L_mse = (0L); /* move32() */ + /* we accumulate energies on the positive side Word8 vectors do not have any issue with saturation (16*256*256) ==2^(4+8+8) .lt 2^31 MAX_INT */ + + cbW8 = &sns_CBW8[i*v_len]; /*ptr init */ + for (n = 0; n < v_len; n++) + { + err = (targetW16[n] - ((LC3_INT16)cbW8[n])); /* cast from Word8 to Word16 is not for free, + actual cost in a Word16 architecture is a L_and(x,0x00ffff) or a shr(x,8) */ + + L_mse += (err*err); /* L_mse = L_mac0(L_mse, err, err); */ + } + + if ((L_mse - L_mse_best) <= 0) + { /* a value closer to 0 */ + best_ind = i; /* single BASOP for best idx update */ + } + L_mse_best = MIN(L_mse, L_mse_best); /* always update best MSE using L_min() in the idx loop, reduces WC WMOPS */ + } + assert(best_ind >= 0 && best_ind < cb_len); + + + *min_mse = (LC3_FLOAT)((double)(L_mse_best)) / (128.0*128.0); /* Word32 L_mse in Q7 -> Cfloat output calculation */ + + f_tmp = (((double)scaleQ4) / 16.0); + *min_mse *= (f_tmp*f_tmp); /* make gain scaling a part of Word32 L_mse -> Cfloat output calculation */ + + return best_ind; +} + +/* LRSNS stage 1 functionality */ +LC3_INT32 snsQuantScfEncLRSt1ABC(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT *min_mse_saveBCA_ptr, + LC3_INT32* ind_saveB_ptr, LC3_FLOAT* st1_vectors, LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx) +{ + + LC3_INT32 i; + LC3_FLOAT *st1_vector, *st1_vectorA, *st1_vectorB, *st1_vectorC, target[M]; + LC3_FLOAT st1_vectorBC[M]; + LC3_FLOAT min_mse_saveA, min_mse_saveB, min_mse_saveC, min_mse_saveBC; + LC3_INT32 ind_saveA, ind_saveC; + const LC3_FLOAT *cb; + LC3_INT16 stage1_mode; /*0=A, 1=B, 2=C. -1==fail*/ + LC3_FLOAT min_mse_saveB_fxlike; + LC3_INT32 ind_saveB_fxlike = -1; + LC3_FLOAT st1_vectorB_idx[M]; + LC3_FLOAT st1C_lim; + LC3_FLOAT f_mse_tmp; + LC3_INT32 ind_saveC_ScaledW8 = -1; + LC3_FLOAT min_mse_saveC_ScaledW8; + +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY + UNUSED(ltpf_rx); +#endif + + stage1_mode = -1; /* output mode */ + + st1_vectorA = &(st1_vectors[0]); + st1_vectorB = &(st1_vectors[1 * M]); + st1_vectorC = &(st1_vectors[2 * M]); + st1_vector = &(st1_vectors[3 * M]); + + /* snslr stage1 B(170) and C(170), A(2) evaluation */ + /* b0-b8 b9 + segm , idx9b , stop bit, comment use + -----+--------+--------- + A | 510,511| n/a, 2 entries, 9 bit total + ------+--------+-------- + ------+--------+--------+------- + B | 0--169 | 1 , 170 entries, 10 bit total + ------+--------+-------- + C | 170-339| 1 , 170 entries, 10 bit total + ------+--------+--------+------------ + ------+--------+--------+------------ + B* | 340-509| 1 --> aux=1, 170, 3b+17b for stage2 'LR_full/LR_fix', 30 bit total + ------+--------+--------+------- + B* | 0--169 | 0 , --> aux=0, 170, 2b+17b for stage2 'LR_SplitLF' , 29 bit total + ------+--------+--------+------- + B* | 170-339| 0 , --> aux=1, 170, 2b+17b for stage2 'LR_SplitLF',29 bit total + ------+--------+--------+------- + B* | 340-509| 0 --> aux=0, 170, 3b+17b for stage2 'LR_full/LR_fix', 30 bit total + ------+--------+--------+------- + */ + + { /* stage 1 section A(2), a very small 2xM entry cb */ + cb = lrsns_st1A_topTab_1bitNoDC; + + memcpy(target, env, sizeof(LC3_FLOAT)*M); + + ind_saveA = MSEsearchGeneric(target, cb, M, 2, &min_mse_saveA); + memcpy(st1_vectorA, &(cb[ind_saveA*M]), sizeof(LC3_FLOAT)*M); + } + + min_mse_saveB = LC3_CONST_FLOATMAX; /*safety init*/ + + { /* stage1 section B(170) MSE analysis */ + memcpy(target, env, sizeof(LC3_FLOAT)*M); + *ind_saveB_ptr = -1; + + ind_saveB_fxlike = MSEsearchCbBIdxMap(target, st1SCF0_7_base5_32x8_Q11, st1SCF8_15_base5_32x8_Q11, + lrsns_st1B_merged170orderSortedSegmCum, lrsns_st1B_merged170orderSort12bitIdx, M, 170, &min_mse_saveB_fxlike); /*st1B LF,HF idx lookup search 170 ,170 Word16s , 0.34kB ROM */ + snslr_st1B_vector_dec(ind_saveB_fxlike, st1SCF0_7_base5_32x8_Q11, st1SCF8_15_base5_32x8_Q11, lrsns_st1B_merged170orderSortedSegmCum, lrsns_st1B_merged170orderSort12bitIdx, + st1_vectorB_idx); + + *ind_saveB_ptr = ind_saveB_fxlike; + min_mse_saveB = min_mse_saveB_fxlike; + memcpy(st1_vectorB, st1_vectorB_idx, sizeof(LC3_FLOAT)*M); /*use low ROM version */ + + { + /* remove DC that can remain in the LF,HF index stored stage cb B structure */ + /* a very slight offline decrease in perf 0.001 dB in AvSD when searching with DC above, + but it allows much better stage1 ROM-reuse performance + */ + LC3_FLOAT dc = snslr_remove_st1_DC_fQ11(st1_vectorB, M); /* inplace removal of dc in st1_vectorB */ + LC3_FLOAT f_tmp; + assert(min_mse_saveB >= 0.0); + + f_tmp = min_mse_saveB - M * (dc*dc); + min_mse_saveB = MAX(f_tmp, 0.0); /* truncation in DC removal can cause negative MSE, limit to 0 */ + } + } /* end of stage 1 section B , search */ + + + + st1C_lim = 3.97; /* corresponding to an SD limit of = 1.5 */ + f_mse_tmp = MIN(min_mse_saveA*0.875, min_mse_saveB); + + if (f_mse_tmp < st1C_lim) /* skip C search if SD is already low enough < 1.5dB) to save average WMOPS */ + + { + min_mse_saveC = 2 * 5.579999923706055; /*disable C selection in consecutive logic */ + ind_saveC = 0; /* a valid index that will not be used */ + } + else + { /* search stage1 C */ + /* search another set (pitch dependent section C) of 170 mean residual vectors */ + /* pitch_info rx selects a mean and then employ a trained residual 1x170W8 based harmonic outlier table */ + + /* float means are based on Word16 S16Q11 values, so that BASOP and float always may become BE in synthesis*/ + int16_t CBCmeanp_ind = pitch_rx; /* 0 or 1 */ + +#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY + if (pitch_rx != 0 && ltpf_rx != 0) + { + CBCmeanp_ind = CBCmeanp_ind + 1; /*LTPF_rx is also active */ + } +#else + /* CbC only dependent on LTP transmission on/off */ +#endif + + for (i = 0; i < M; i++) + { + target[i] = env[i] - lrsns_st1CTrainedMapMeans[CBCmeanp_ind][i]; + } + + ind_saveC_ScaledW8 = MSEsearchGenericScaledW8(target, lrsns_st1C_Both_Word8, + lrsns_st1C_Both_scaleQ4_7p4bits_fx[1], lrsns_st1C_Both_inv_scaleQ15_7p4bits_fx[1], + M, 170, &(min_mse_saveC_ScaledW8)); + + snslr_st1C_vector_dec(ind_saveC_ScaledW8, lrsns_st1C_Both_Word8, lrsns_st1C_Both_scaleQ4_7p4bits_fx[1], lrsns_st1C_Both_inv_scaleQ15_7p4bits_fx[1], + M, 170, st1_vectorC); + /* integer based decoder side synthesis of scaled W8 table for best possible interop */ + + ind_saveC = ind_saveC_ScaledW8; + min_mse_saveC = min_mse_saveC_ScaledW8; + + for (i = 0; i < M; i++) { + st1_vectorC[i] += lrsns_st1CTrainedMapMeans[CBCmeanp_ind][i]; /* Q11 means*/ + } + } + + /* BC stage1 comparison */ + /* initially assume B as stage 1 winner */ + min_mse_saveBC = min_mse_saveB; + L_index[0] = *ind_saveB_ptr; /* 0...169 */ + + memcpy(st1_vector, st1_vectorB, sizeof(LC3_FLOAT)*M); /* stage 1 segmentB result without DC copied as base for st2 */ + + + if (min_mse_saveC < min_mse_saveBC) + { /* C better than B */ + min_mse_saveBC = min_mse_saveC; + L_index[0] = 170 + ind_saveC; /* [2x 170] (9+1)b [170-339] , ">=170" is a signal to multiplexor */ + memcpy(st1_vector, st1_vectorC, sizeof(LC3_FLOAT)*M); + } + + memcpy(st1_vectorBC, st1_vector, sizeof(LC3_FLOAT)*M); + L_index[1] = -10; + assert(min_mse_saveBC >= 0.0); + + /* (9(A)<->10(BC) bit weighted comparison */ + *min_mse_saveBCA_ptr = min_mse_saveBC; + if (min_mse_saveA*0.875 < min_mse_saveBC) /* a minor favouring of the 9b vector results sqrt(0.875) => approx 0.6dB level domain */ + { + L_index[0] = 510 + ind_saveA; /* only [510, 511] possible */ + L_index[1] = -9; + + cb = lrsns_st1A_topTab_1bitNoDC; + memcpy(st1_vectorA, &(cb[ind_saveA*M]), sizeof(LC3_FLOAT)*M); + memcpy(st1_vector, st1_vectorA, sizeof(LC3_FLOAT)*M); + + *min_mse_saveBCA_ptr = min_mse_saveA * 0.875;; + } + + /* index0_saveBCA = index[0];*/ /* 0 ... 511 */ + /* index1_saveBCA = index[1];*/ /* -9 or -10 */ + + stage1_mode = 2; /* C , assumed */ + if (L_index[0] >= 510) + { + stage1_mode = 0; /* A */ + } + if (L_index[0] <= 169) { + stage1_mode = 1; /* B */ + } + + return stage1_mode; /* return best stage1 mode */ +} + +LC3_INT16 snsQuantScfEncLR(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT* envq, Dct2 dct2structSNS, LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx) +{ + LC3_INT32 i; + LC3_FLOAT min_mse_saveBCA; + LC3_INT32 ind_saveB; + LC3_INT16 st1_mode; + + LC3_FLOAT stage2_en1_norm_sub[M]; + LC3_FLOAT st1_vectors[(SNSLR_MAX_PVQ_SEARCH_CAND+1)*M], *st1_vectorA, *st1_vectorB, *st1_vectorC, *st1_vector; + LC3_FLOAT pvq_target_pre[M]; + LC3_FLOAT pvq_target[M]; +#ifdef LRSNS_WMC_FIX + LC3_INT32 y[SNSLR_MAX_PVQ_SEARCH_CAND][M]; /* o: [3]*[0...M-1] */ +#else + LC3_INT32 y_tmp[3*M]; +#endif + LC3_FLOAT stage2_en1_norm_pre_sub[M]; + LC3_FLOAT envq_st1B_st2[M]; + LC3_FLOAT mse_st1B_st2; + LC3_FLOAT mse_st1B_st2_dct_domain; + LC3_INT32 gain_idx, shape; + LC3_FLOAT scfq_gain; + LC3_INT32 fix_shape_idx; + LC3_INT16 envelope_bits; /* function output */ + LC3_INT32 fix_shift_ind, fix_shift_bits, fix_end_sign, LS_tmp_ind; + LC3_INT32 shape_local; + + UNUSED(fix_shift_bits); /* used for assert */ + +#ifndef LRSNS_WMC_FIX + LC3_INT32(*y)[M]; /* C-construct to allow matrix adressing into a scratch area */ +#endif + envelope_bits = -1; /* output : 9,10, 29/30 */ + gain_idx = 1; /* stage 2 gain idx range 0 ... 7 , or 0 ... 3 */ + shape = -1; /* stage 2 shape range 0 ... 5 */ + + st1_vectorA = &(st1_vectors[0]); + st1_vectorB = &(st1_vectors[1 * M]); + st1_vectorC = &(st1_vectors[2 * M]); + st1_vector = &(st1_vectors[3 * M]); + + +#ifndef LRSNS_WMC_FIX + y = (LC3_INT32(*)[M]) &(y_tmp[0]); /* y is an NxM Matrix Ptr */ +#endif + { /* stage1 A,B,C */ + + ind_saveB = -1; + min_mse_saveBCA = M * 32.0*32.0; + st1_mode = snsQuantScfEncLRSt1ABC( + env, L_index, &min_mse_saveBCA, &ind_saveB, st1_vectors, pitch_rx, ltpf_rx); + + if (ind_saveB < 0 || ind_saveB > 169) + { + assert(ind_saveB >= 0 && ind_saveB <= 169); /* idxB always needed in case stage2 is activated */ + } + + if (st1_mode == 0) + { + envelope_bits = 9; + memcpy(st1_vector, st1_vectorA, sizeof(LC3_FLOAT)*M); + assert(L_index[0] >= 510 && L_index[0] <= 511); + } + + if (st1_mode == 1) + { + envelope_bits = 10; + memcpy(st1_vector, st1_vectorB, sizeof(LC3_FLOAT)*M); + assert(L_index[0] >= 0 && L_index[0] <= 169); + } + + if (st1_mode == 2) + { + envelope_bits = 10; + memcpy(st1_vector, st1_vectorC, sizeof(LC3_FLOAT)*M); + assert(L_index[0] >= 170 && L_index[0] < 2 * 170); + } + + L_index[1] = -910; /* aux */ + L_index[2] = -envelope_bits; /* signal shape */ + + } + /* only run stage 2 when necessary */ + + { + LC3_FLOAT mse_lim_smooth; + mse_lim_smooth = (5.41); /* 1.75 SD */ + + mse_st1B_st2 = 2.0* min_mse_saveBCA + 1.0; /* indicate that st1B+st2 is not used by setting a higher MSE than st1BCA */ + + if (min_mse_saveBCA > mse_lim_smooth) + { + /* run and evaluate STAGE 2, using vector B as stage 1 */ + for (i = 0; i < M; i++) + { + pvq_target_pre[i] = env[i] - st1_vectorB[i]; + } + + dct2_apply(&dct2structSNS, pvq_target_pre, pvq_target); + + pvq_target[0] = 0.0; /* DC always zero */ + + fix_shape_idx = -1; + pvq_fess_enc_search(pvq_target, y, &fix_shape_idx); /* best shape search splitLF, full, best_fess_env */ + + assert(y[0][0] == 0 && y[1][0] == 0 && y[2][0] == 0); + lrsns_quant_shape_gain(pvq_target, y, &gain_idx, &shape, stage2_en1_norm_pre_sub, &scfq_gain, 2 + 1); + + if (shape == 2) + { + shape = 2 + fix_shape_idx; + } + + /* check if MSE after stage 2 is better already here in dct domain, avoid unnecessary IDCT-II calls */ + envq_st1B_st2[0] = 0; + for (i = 1; i < M; i++) + { + envq_st1B_st2[i] = (stage2_en1_norm_pre_sub[i] * scfq_gain); + } + + mse_st1B_st2_dct_domain = calc_mse(pvq_target, envq_st1B_st2, 1.0f, M); + + if (min_mse_saveBCA < mse_st1B_st2_dct_domain) + { + /* no need for an IDCT as stage2 was worse than only stage1 */ + mse_st1B_st2 = mse_st1B_st2_dct_domain; + } + else + { + /* Inverse transform */ + idct_II(stage2_en1_norm_pre_sub, stage2_en1_norm_sub, M); + + for (i = 0; i < M; i++) + { + envq_st1B_st2[i] = st1_vectorB[i] + (stage2_en1_norm_sub[i] * scfq_gain); + } + mse_st1B_st2 = calc_mse(env, envq_st1B_st2, 1.0f, M); + } + } + } /*end of stage2 search */ + + /* post-evaluate if one of (st1B, st1C, st1A) was actually better than st1B+stage2 */ + if (mse_st1B_st2 < min_mse_saveBCA) + { /* use stage1B + st2 at 29b/30b bits total */ + L_index[0] = ind_saveB; + L_index[1] = 2930; /* later stage2 aux value LS_splitLF or LS_full or s0, put here as a 0 or 1 */ + L_index[2] = shape; /* 0=splitLF, 1=full, ( 2=fixEnv0, 3=fixEnv1, 4: fixEnv2, 5: fixEnv3 ) */ + L_index[3] = gain_idx; /* gain_idx with a shape dependent number of levels (4 or 8 levels ) */ + + memcpy(envq, envq_st1B_st2, sizeof(LC3_FLOAT)*M); + memcpy(st1_vector, st1_vectorB, sizeof(LC3_FLOAT)*M); /* save final st1B result, st1 in combination with stage 2, for verification */ + envelope_bits = 29; /* 'LR_splitLF' */ + if (shape > 0) + { + envelope_bits += 1; /*30 'LR_full/LR_fixenv' */ + } + + { + /* DBG check values */ + assert(shape >= 0); + assert(envelope_bits >= 29); + assert(L_index[0] <= 170); /*only B allowed */ + assert(L_index[1] >= 0); + + assert(gain_idx >= 0); /*index*/ + assert(scfq_gain > 0.0); /* value */ + } + } + else + { /* stick to stage1(best of BCA) at 9 or 10 bits */ + assert(L_index[1] < 0 && L_index[0] >= 0 && L_index[0] < 512); + envelope_bits = ((L_index[0] >= 510) ? 9 : 10); + shape = -envelope_bits; /* signal an invalid shape number to enc-entropy */ + + memcpy(envq, st1_vector, M * sizeof(LC3_FLOAT)); /* output */ + memset(stage2_en1_norm_sub, 0, M * sizeof(*stage2_en1_norm_sub)); + scfq_gain = 0.0f; + gain_idx = -1; /* L_index sentinel */ + L_index[2] = shape; + } + + /******************************************************************/ + /* signal to enc_entropy for LRSNS semi-fractional multiplexing */ + /******************************************************************/ + /* integer multiplexing 29/30 bit modes into intermediate unmuxed integer indeces 0...7 */ + /* a bit of fractional multiplexing for these indeces 0...7 is done later, in function enc_entropy() */ + if (shape >= 0) + { /* stage 2 multiplexing manipulations */ + + + if (shape == 0) + { /* splitLF */ + LC3_INT32 n5k = 0; + for (i = 0; i < 5; i++) + { + n5k += abs(y[shape][1 + i]); + } + + if (n5k == 6) + { + MPVQ_enum(5, &(y[shape][1]), &(L_index[4]), &LS_tmp_ind); /* P(N=5,K=6) (10)=10 bit L_index */ + L_index[1] = LS_tmp_ind; /* set the aux bit for the splitLF path, plant the first LS as aux */ + assert((L_index[4] >= 0) && (L_index[4] < (SNSLR_NPVQ_L5K6 >> 1))); + + MPVQ_enum(8, &(y[shape][1 + 5]), &(L_index[5]), &LS_tmp_ind); + L_index[5] = (L_index[5] << 1) + LS_tmp_ind; /* A full PVQ 7 bit index for the P(N=8,K=2) B config*/ + assert((L_index[5] >= 0) && (L_index[5] < (1 << 7))); + } + else + { + MPVQ_enum(5, &(y[shape][1]), &(L_index[4]), &LS_tmp_ind); /* PVQ(N=5,K=8) (12.x in total, i.e. LS+ 11.x ) */ + L_index[1] = LS_tmp_ind; /* aux bit for the splitLF path , plant the first LS as aux */ + assert((L_index[4] >= 0) && (L_index[4] < (SNSLR_NPVQ_L5K8 >> 1))); + L_index[5] = -8; /* signal LF PVQ(5,8) and zeroed HF(10,0) */ + } + } + if (shape == 1) + { /* full (15,5) , LS kept separated */ + /* indicate a stage2 path in the 9 bit stage1 index */ + MPVQ_enum(15, &(y[shape][1]), &(L_index[4]), &LS_tmp_ind); /* mPVQ 16.66 bits in index[4], and LS 1 bit in index[1] */ + L_index[1] = LS_tmp_ind; /*aux bit location, 0 or 1 , we plant the LS there */ + assert((L_index[4] >= 0) && (L_index[4] < (SNSLR_NPVQ_L15K5 >> 1))); + } + if (shape >= 2 && shape <= 5) + { + /* fixEnv0, fixEnv1, fixEnv2, fixEnv3 */ + /* send the fixed env subshape mode to enc_entropy */ + L_index[4] = (shape - 2); /* env shape, 0-->"1" , 1--> "env1" */ /* L_index[2] has original shape 0...5 */ + + shape_local = 2; /*a single y shape vector for all fixed env */ + + fix_shift_ind = 0; + while (y[shape_local][1 + fix_shift_ind] == 0) + { + fix_shift_ind += 1; + } + fix_shift_bits = 2; + + assert(fix_shift_ind < (1 << fix_shift_bits)); + + L_index[1] = (y[shape_local][1 + fix_shift_ind /* + 0*/] < 0); /* aux_bit : 0 (or 1) , will indicate the s0 sign in the FESS fix shape */ + + + fix_end_sign = 12; + if (shape == 5) + { + assert(L_index[4] == 3); + fix_end_sign = (fix_end_sign - 2); /* shape 4 has 2 bits shift and a total of 10 signs =2^10*2^2 = 2^12 = 4096 */ + } + + L_index[5] = fix_shift_ind; /* the two shift bits will be pushed up to b11,b12 , for 11 signs s1-s11 */ + + for (int sign_ind = 1; sign_ind < fix_end_sign; sign_ind++) /* push the remaining sequential signs s1-s11(or s1-s9), into a single idx */ + { /* s1 is in the MSB, and s11 is in the lsb*/ + L_index[5] = L_index[5] << 1; + if (y[shape_local][1 + fix_shift_ind + sign_ind] < 0) /*"1" means negative, "0" means positive */ + { + L_index[5] += 1; + } + } + assert(L_index[5] >= 0 && L_index[5] < (1 << (fix_shift_bits + (fix_end_sign - 1)))); + } + } /* end of stage2 premultiplexing */ + + { + assert(envelope_bits == 9 || envelope_bits == 10 || envelope_bits == 29 || envelope_bits == 30); + } + + return envelope_bits; +} + +void FESSdeenum(LC3_UINT8 dim_in, /* i : dimension of vec_out */ + LC3_UINT8 n_env, /* i : number of envelopes */ + LC3_UINT8 n_shift, /* i : number shifts */ + LC3_UINT8 n_signs, /* i : number signs */ + LC3_INT32 env_ind, /* i:indx */ + LC3_INT32 shift_ind, /* i:indx */ + LC3_INT32 sign_ind, /* i:indx */ + LC3_INT32* vec_out /* o : FESS integer pulse train */) +{ + + LC3_INT32 i; + LC3_INT32 sign_val; + + assert(n_env >= 1 && n_env <= 4); + assert(env_ind >= 0 && env_ind < n_env); + assert(shift_ind >= 0 && shift_ind < n_shift); + + UNUSED(n_env); + UNUSED(n_shift); + memset(vec_out, 0, sizeof(*vec_out)*dim_in); + + for (i = (shift_ind + n_signs - 1); i >= shift_ind; i--) + { + /* low numbered coeff signs are in the msb's */ + /* high numbered coeff signs are in the lsb's */ + assert(i < dim_in); + sign_val = 1 - 2 * (sign_ind & 0x01); + sign_ind = (sign_ind >> 1); + vec_out[i] = sign_val * env_ptrs[env_ind][i]; /* vec_out[i] = sign_val * amps[env_ind*(M - 1) + i]; */ + } +} + +void snsQuantScfDecLR(LC3_INT32* sns_vq_idx, LC3_FLOAT* scf_q, LC3_INT32 pitch_rx, LC3_INT32 ltpf_rx) +{ + LC3_INT32 i; + LC3_INT32 mPVQ_ind; /* can be 16-17 bits */ + LC3_INT16 shape_idx, gain_idx, cb_idx, aux_idx, LS_ind; + LC3_INT16 env_ind, shift_ind, sign_ind, n_signs; + + LC3_INT32 Y_shape_j[M]; + LC3_FLOAT Xq_shape_j[M], Xq_shape_j_idct[M], sum; + const LC3_FLOAT *cb; + const LC3_FLOAT *gainTab; + LC3_FLOAT st1_scf_q[M]; + LC3_INT16 CBCmeanp_ind = pitch_rx; /* 0 or 1 */ + const LC3_FLOAT *mean_cb; + LC3_INT16 sign_mask = 0x07ff; + + +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY + UNUSED(ltpf_rx); +#endif + + sum = 0; + memset(Y_shape_j, 0, sizeof(LC3_INT32) * M); + + gainTab = &(lrsns_gains_Q11[0][0]); /* gcc warning init */ + gain_idx = 0; /* gcc warning init */ + + cb_idx = sns_vq_idx[0]; + aux_idx = sns_vq_idx[1]; + shape_idx = sns_vq_idx[2]; /* analysis order shape idx -9,-10, 0,1, 2,3,4,5 */ + gain_idx = sns_vq_idx[3]; /* stage 2 gain */ + + /* Stage1 cand */ + if (shape_idx == -9) + { + /* minminal 2*16 SNS codebook, no DC */ + cb = lrsns_st1A_topTab_1bitNoDC; + memcpy(scf_q, &(cb[cb_idx * M]), sizeof(LC3_FLOAT) * M); + } + else if (shape_idx == -10) + { /* 0..339 */ /* stage 1 only, transmitted in 9+1= 10 bits */ + if (cb_idx < 170) + { /*Stage 1B */ + snslr_st1B_vector_dec(cb_idx, st1SCF0_7_base5_32x8_Q11, st1SCF8_15_base5_32x8_Q11, lrsns_st1B_merged170orderSortedSegmCum, lrsns_st1B_merged170orderSort12bitIdx, scf_q); + snslr_remove_st1_DC_fQ11(scf_q, M); + } + else + { + cb_idx -= 170; + /* Stage 1C harm outlier CB with a pitch dependent mean */ + /* Q11 values , so that BASOP and float always becomes BE in synthesis*/ + assert(cb_idx >= 0 && cb_idx < 170); + snslr_st1C_vector_dec(cb_idx, lrsns_st1C_Both_Word8, lrsns_st1C_Both_scaleQ4_7p4bits_fx[1], lrsns_st1C_Both_inv_scaleQ15_7p4bits_fx[1], + M, 170, scf_q + ); + + + /* cbC add harmonic mean , based on pitch_info availability */ + pitch_rx = sns_vq_idx[3]; /* LTP active flag directly from dec_entropy */ +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY + ltpf_rx = 0; /* CB_C has no dependency on LTPF active flag */ +#else + ltpf_rx = sns_vq_idx[4]; /* LTPF active flag LTP active flag directly from dec_entropy */ +#endif + CBCmeanp_ind = pitch_rx; /* 0 or 1 */ +#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY + if (pitch_rx != 0 && ltpf_rx != 0) + { + CBCmeanp_ind = CBCmeanp_ind + 1; /* high corr ltpf_rx is also active */ + } +#endif + + mean_cb = lrsns_st1CTrainedMapMeans[CBCmeanp_ind]; /* point to pitch dependent mean */ + for (i = 0; i < M; i++) + { + scf_q[i] += mean_cb[i]; + } + /* remove_DC() call is not required for section C */ + /* a very small DC can still exist though, due to Q7+Q4 quantization of values */ + } + } + else + { /* 0...169 */ /* st1B* used with a stage 2 shape submode */ + assert(shape_idx >= 0); + snslr_st1B_vector_dec(cb_idx, st1SCF0_7_base5_32x8_Q11, st1SCF8_15_base5_32x8_Q11, lrsns_st1B_merged170orderSortedSegmCum, lrsns_st1B_merged170orderSort12bitIdx, scf_q); + snslr_remove_st1_DC_fQ11(scf_q, M); /* DC needs removal for st1 part B */ + } + memcpy(st1_scf_q, scf_q, sizeof(LC3_FLOAT) * M); /* keep track of stage 1 contribution */ + + if (shape_idx >= 0) /* stage 2 shapes 0,1, 2,3,4,5 ( negative idx used for stage1 only */ + { + /* Stage 2 SNS VQ decoding */ + /* Decode shape_j */ + Y_shape_j[0] = 0; /* no DCT-II DC-coeff decoded */ + + switch (shape_idx) + { + case 0: /* splitLF 29 bits total */ + LS_ind = aux_idx; + mPVQ_ind = sns_vq_idx[4]; /* mPVQ(5,6) or mPVQ(5,8) */ + + if (sns_vq_idx[5] >= 0) + { + MPVQdeenum(5, 6, LS_ind, mPVQ_ind, &Y_shape_j[1]); + LS_ind = sns_vq_idx[5] & 0x1; + mPVQ_ind = sns_vq_idx[5] >> 1; + MPVQdeenum(8, 2, LS_ind, mPVQ_ind, &Y_shape_j[1 + 5]); + } + else + { + MPVQdeenum(5, 8, LS_ind, mPVQ_ind, &Y_shape_j[1]); + } + gainTab = &(lrsns_gains_Q11[0][0]);/* 4 levels in 2 bits */ + + break; + case 1: /* full 30 bits total */ + LS_ind = aux_idx; + mPVQ_ind = sns_vq_idx[4]; + MPVQdeenum(15, 5, LS_ind, mPVQ_ind, &Y_shape_j[1]); + gainTab = &(lrsns_gains_Q11[1][0]); /* 8 levels in 3 bits */ + + break; + case 2: /* fix env 0 , init_bell 12 signs */ + case 3: /* fix env 1 , decay 24-->13 12 signs */ + case 4: /* fix env 2 , start bell 12 signs */ + case 5: /* fix env 3 , early bell 10 signs */ + LS_ind = aux_idx; /* s0 */ + env_ind = sns_vq_idx[4]; + assert(env_ind == (shape_idx - 2)); + + n_signs = 12; /* including s0 */ + if (env_ind == 3) { + n_signs -= 2; + } + sign_mask = (sign_mask >> (12 - n_signs)); + + shift_ind = sns_vq_idx[5] >> (n_signs - 1); + sign_ind = sns_vq_idx[5] & sign_mask; + + /* put s0 , right next to s1 , to make the sign decoding loop easier */ + sign_ind = (sign_ind)+(LS_ind << (n_signs - 1)); /* s0 put as MSB at 12th position 2^11 , lsb at 2^0 */ + + /*FixEnvShiftSigns deenumeration */ + FESSdeenum(15, 4, 4, n_signs, env_ind, shift_ind, sign_ind, &Y_shape_j[1]); /*30b , 4xenv,4xshifts, 10 or12 signs, over 15 positions,*/ + gainTab = &(lrsns_gains_Q11[2][0]);; /* 8 levels in 3 bits */ + /* fix_envshift_nb = env_ind * 4 + shift_ind; */ /* index for fast normalization lookup */ + break; + default: + + break; + } + + /* Unit energy normalization of the received shape */ + for (i = 0; i < M; i++) + { + sum += (Y_shape_j[i] * Y_shape_j[i]); + } + + sum = 1.0 / LC3_SQRT(sum); /* all shapes will have tabled inv_sqrt() divisions as factors in BASOP */ + + for (i = 0; i < M; i++) + { + Xq_shape_j[i] = Y_shape_j[i] * sum; + } + + /* Reconstruction of the quantized SNS scale factors */ + idct_II(Xq_shape_j, Xq_shape_j_idct, M); + for (i = 0; i < M; i++) { + scf_q[i] += Xq_shape_j_idct[i] * gainTab[gain_idx]; + } + } + else + { /* -9, -10 */ + /* LRSNS stage 1 variations only */ + memcpy(scf_q, st1_scf_q, sizeof(LC3_FLOAT) * M); + } +} + +/* LRSNS integer precision based function needed in both encoder and decoder */ +LC3_FLOAT snslr_remove_st1_DC_fQ11(LC3_FLOAT *scfq, LC3_INT32 len) +{ + LC3_INT32 i; /*Counter*/ + LC3_INT32 L_dcQ11; + LC3_FLOAT f_dcQ11 = 0.0; + L_dcQ11 = 0L; + + for (i = 0; i < len; i++) { + L_dcQ11 += (LC3_INT32)(scfq[i] * 2048.0f); /* BE simulation of DC Q11 summation of truncated values in BASOP, preferably BE in synthesis in FLP/BASOP decoder */ + } + + assert(len == M); + { + L_dcQ11 = L_dcQ11 >> 4; /* make the average in integer domain , no rounding applied before shift, on purpose */ + f_dcQ11 = ((LC3_FLOAT)L_dcQ11) *(1.0f / 2048.0f); /* now a Q11 value to match the overall generic Q11 BASOP scaling of stage1 variables */ + } + + for (i = 0; i < len; i++) + { + scfq[i] -= f_dcQ11; /* result update */ + } + return f_dcQ11; /* output used for encoder side mse update*/ +} + +#endif diff --git a/lib_lc3plus/structs.h b/lib_lc3plus/structs.h index 357531c3e8..b446c3a201 100644 --- a/lib_lc3plus/structs.h +++ b/lib_lc3plus/structs.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -195,6 +195,9 @@ typedef struct { LC3_INT16 overall_counter; LC3_INT8 longterm_counter_byte_position; LC3_INT8 longterm_counter_bit_position; +#ifdef CR13_C_RESET_CLASSIFIER_AFTER_BAD_FRAMES + LC3_INT16 numberOfGoodFrames; +#endif } PlcAdvSetup; #endif diff --git a/lib_lc3plus/tns_coder.c b/lib_lc3plus/tns_coder.c index 9e3c3f6f20..3dbd1e9aa0 100644 --- a/lib_lc3plus/tns_coder.c +++ b/lib_lc3plus/tns_coder.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * @@ -142,7 +142,7 @@ void poly2rc(LC3_FLOAT* a, LC3_FLOAT* out, LC3_INT len) } -void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, LC3_INT fs, LC3_INT N, LC3_INT frame_dms, LC3_INT nBits, +void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, LC3_INT fs, LC3_INT N, LC3PLUS_FrameDuration frame_dms, LC3_INT nBits, LC3_INT* order_out, LC3_INT* rc_idx, LC3_INT* tns_numfilters, LC3_INT* bits_out , LC3_INT16 near_nyquist_flag ) @@ -159,16 +159,15 @@ void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, L /* Init */ - if (fs >= 32000 && frame_dms >= 50) { + if (fs >= 32000 && frame_dms >= LC3PLUS_FRAME_DURATION_5MS) { numfilters = 2; } else { numfilters = 1; } /* 40 * frame_dms / 10 = 4 * frame_dms */ - if (N > 4 * frame_dms) - { - N = 4 * frame_dms; + if (N > 40 * ((LC3_FLOAT) (frame_dms*1.25*10) / 10.0)) { + N = 40 * ((LC3_FLOAT) (frame_dms*1.25*10) / 10.0); fs = 40000; } @@ -184,27 +183,35 @@ void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, L switch (frame_dms) { - case 25: +#ifdef CR9_C_ADD_1p25MS + case LC3PLUS_FRAME_DURATION_1p25MS: + maxOrder = 4; + nSubdivisions = 2.0; + break; +#endif + case LC3PLUS_FRAME_DURATION_2p5MS: maxOrder = 4; nSubdivisions = 2.0; break; - case 50: + case LC3PLUS_FRAME_DURATION_5MS: maxOrder = 4; nSubdivisions = 2.0; break; - case 75: + case LC3PLUS_FRAME_DURATION_7p5MS: maxOrder = 8; nSubdivisions = 3; break; - case 100: + case LC3PLUS_FRAME_DURATION_10MS: maxOrder = 8; nSubdivisions = 3.0; break; + case LC3PLUS_FRAME_DURATION_UNDEFINED: + assert(0); } minPredictionGain = 1.5; - if (nBits >= 4.8 * frame_dms) { + if (nBits >= 4.8 * ((LC3_FLOAT) frame_dms * 1.25 * 10)) { order = order1_tns; } else { order = order2_tns; @@ -293,7 +300,7 @@ void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, L if (tns == 1) { minPGfac = 0.85; maxPG = 2; - if (nBits >= 4.8 * frame_dms) { + if (nBits >= 4.8 * frame_dms*1.25*10) { maxPG = minPredictionGain; } @@ -335,11 +342,11 @@ void processTnsCoder_fl(LC3_FLOAT* x, LC3_INT bw_cutoff_idx, LC3_INT bw_fcbin, L order_out[f] = order_tmp; - // Disable TNS if order is 0: + /* Disable TNS if order is 0: */ if (order_out[f] == 0) { tns = 0; - // Jump to else statement + /* Jump to else statement */ goto tns_disabled; } tmp = order[order_out[f] - 1]; diff --git a/lib_lc3plus/tns_decoder.c b/lib_lc3plus/tns_decoder.c index 05f8036fc4..37e831d464 100644 --- a/lib_lc3plus/tns_decoder.c +++ b/lib_lc3plus/tns_decoder.c @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/lib_lc3plus/util.h b/lib_lc3plus/util.h index 410b7db44b..05fd54467b 100644 --- a/lib_lc3plus/util.h +++ b/lib_lc3plus/util.h @@ -1,5 +1,5 @@ /****************************************************************************** -* ETSI TS 103 634 V1.5.1 * +* ETSI TS 103 634 V1.6.1 * * Low Complexity Communication Codec Plus (LC3plus) * * * * Copyright licence is solely granted through ETSI Intellectual Property * diff --git a/scripts/lc3plus_lib_setup/get_lc3plus.sh b/scripts/lc3plus_lib_setup/get_lc3plus.sh index f654768707..05d1e7d444 100755 --- a/scripts/lc3plus_lib_setup/get_lc3plus.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus.sh @@ -3,45 +3,36 @@ # This script shall only be used by automated continuous integration systems printf "Cleaning old version of LC3plus\n" -rm -rf lib_lc3plus +rm -rf lib_lc3plus/* printf "Downloading LC3plus code\n" -if true; then - # Waiting for official ETSI release. - # TODO: add new URL, remove `if false` when package goes public - curl -o ./lc3plus_sources.zip https://www.etsi.org/deliver/etsi_ts/103600_103699/103634/01.05.01_60/ts_103634v010501p0.zip - unzip lc3plus_sources.zip -d . - rm lc3plus_sources.zip - cp -r "ETSI_Release/LC3plus_ETSI_src_v5f640bd48cc_20240516/src/floating_point" lib_lc3plus - rm -r ETSI_Release -else - # Temp solution for downloading WIP ETSI package - # LC3_ETSI_REPO_URL must be define before running the script - git clone "$LC3_ETSI_REPO_URL" - cp -r lc3_etsi_release/src/floating_point lib_lc3plus - rm -rf lc3_etsi_release -fi + +curl -o ./lc3plus_sources.zip https://www.etsi.org/deliver/etsi_ts/103600_103699/103634/01.06.01_60/ts_103634v010601p0.zip +unzip -o lc3plus_sources.zip -d . -x "__MACOSX/*" "package.info" +rm lc3plus_sources.zip +cp -r LC3plus_ETSI_src_v527e88bdddb_20251022/src/floating_point/* lib_lc3plus +rm -r LC3plus_ETSI_src_v527e88bdddb_20251022 # Remove unneeded files printf "Removing unneeded files\n" -rm lib_lc3plus/codec_exe.c # Only used for executable -rm lib_lc3plus/makefile # Build handled at IVAS level -rm -r lib_lc3plus/msvc # Build handled at IVAS level +rm lib_lc3plus/codec_exe.c # Only used for executable +rm lib_lc3plus/makefile # Build handled at IVAS level +rm -r lib_lc3plus/msvc # Build handled at IVAS level rm lib_lc3plus/plc_noise_substitution0.c # Empty file -rm lib_lc3plus/tinywavein_c.h # Only used for executable -rm lib_lc3plus/tinywaveout_c.h # Only used for executable +rm lib_lc3plus/tinywavein_c.h # Only used for executable +rm lib_lc3plus/tinywaveout_c.h # Only used for executable # Limit file permissions printf "Limiting file permissions\n" -find lib_lc3plus -type f -print0 | \ -xargs -0 -I {} \ -chmod -x {} +find lib_lc3plus -type f -print0 | + xargs -0 -I {} \ + chmod -x {} # include options.h in all C files printf "Including options.h and wmc_auto.h in C files\n" -find lib_lc3plus -name '*.[ch]' -type f -print0 | \ -xargs -0 -I {} \ -sed -i ' +find lib_lc3plus -name '*.[ch]' -type f -print0 | + xargs -0 -I {} \ + sed -i ' # range: from 1st line to first match 1,/^#include/ { # insert lines before first match @@ -53,17 +44,16 @@ sed -i ' # Remove whitespace from preprocessor commands printf "Removing whitespace from preprocessor commands\n" -find lib_lc3plus -name '*.[ch]' -type f -print0 | \ -xargs -0 -I {} \ -sed -i 's/^#[[:space:]]\+/#/' {} +find lib_lc3plus -name '*.[ch]' -type f -print0 | + xargs -0 -I {} \ + sed -i 's/^#[[:space:]]\+/#/' {} -# fix for sanitizer issues -sed -i 's/st->low << 8/(LC3_INT)((LC3_UINT32)st->low << 8)/' lib_lc3plus/ari_codec.c -sed -i 's/~3/(uintptr_t)(~3)/' lib_lc3plus/lc3plus.c +# define CR9_C_ADD_1p25MS manually to avoid redefinition of NPRM_RESQ +sed -i 's|#include "defines\.h" /\* Required for CR9_C_ADD_1p25MS \*/|#define CR9_C_ADD_1p25MS /\* including defines.h redefines NPRM_RESQ, so define this here manually \*/|' lib_lc3plus/lc3plus.h # Add .clang-format file to lib_lc3plus to disable formatting there printf "Disabling clang-format in lib_lc3plus directory\n" printf ' DisableFormat: true SortIncludes: Never -' >> lib_lc3plus/.clang-format +' >>lib_lc3plus/.clang-format diff --git a/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh b/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh new file mode 100644 index 0000000000..3b7065c8b5 --- /dev/null +++ b/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +# This script shall only be used by automated continuous integration systems + +set -ux + +printf "Cleaning old version of LC3plus\n" +rm -rf lib_lc3plus/* + +printf "Downloading LC3plus code\n" + +curl -o ./lc3plus_sources.zip https://www.etsi.org/deliver/etsi_ts/103600_103699/103634/01.06.01_60/ts_103634v010601p0.zip +unzip -o lc3plus_sources.zip -d . -x "__MACOSX/*" "package.info" +rm lc3plus_sources.zip +cp -r LC3plus_ETSI_src_v527e88bdddb_20251022/src/fixed_point/* lib_lc3plus +rm -r LC3plus_ETSI_src_v527e88bdddb_20251022 + +cd lib_lc3plus + +printf "Updating LC3plus code for IVAS BASOP compatibility\n" + +# rename files +mv basic_op/dynmem.[ch] . +mv basop_mpy.c basop_mpy_lc3plus.c +mv basop_mpy.h basop_mpy_lc3plus.h +mv basop_util.c basop_util_lc3plus.c +mv basop_util.h basop_util_lc3plus.h +mv fft.c fft_lc3plus.c +mv rom_basop_util.c rom_basop_util_lc3plus.c +mv rom_basop_util.h rom_basop_util_lc3plus.h + +# remove unneeded files +rm makefile +rm codec_exe.c +rm ccConvert.c +rm tinywavein_c.h +rm tinywaveout_c.h +rm -r basic_op +rm -r msvc +rm -r msvc_ccc + +# update header include guards to avoid conflict +sed -i 's|__BASOP_UTIL_H__|__BASOP_UTIL_LC3PLUS_H__|g' basop_util_lc3plus.h +sed -i 's|__BASOP_UTIL_ROM_H__|__BASOP_UTIL_ROM_LC3PLUS_H__|g' rom_basop_util_lc3plus.h + +# update includes for renamed files +sed -i 's|#include "basop_mpy.h"|#include "basop_mpy_lc3plus.h"|g' *.[ch] +sed -i 's|#include "basop_util.h"|#include "basop_util_lc3plus.h"|g' *.[ch] +sed -i 's|#include "rom_basop_util.h"|#include "rom_basop_util_lc3plus.h"|g' *.[ch] + +# deactivate DYNMEM_COUNT, conflicts with WMC tool +sed -i 's|#define DYNMEM_COUNT|// #define DYNMEM_COUNT /* conflicts with WMC tool */|g' defines.h + +# change BASOP_sub_start/end to push/pop_wmops wrapped in #ifdef WMOPS +sed -E -i 's|BASOP(_sub){1,2}_start|push_wmops|g' *.[ch] +sed -E -i 's|BASOP(_sub){1,2}_end|pop_wmops|g' *.[ch] +sed -E -i 's|^(.*p[uo][sp]h?_wmops.*)$|#ifdef WMOPS\n\1\n#endif|g' *.[ch] + +# rename conflicting BASOPs +sed -i 's|Isqrt|Isqrt_lc3plus|g' *.[ch] +sed -i 's|\bMpy_32_16(|Mpy_32_16_lc3plus(|g' *.[ch] +sed -i 's|\bMpy_32_32(|Mpy_32_32_lc3plus(|g' *.[ch] + +# suppress #pragma message() +sed -i 's/\#pragma message(/\/\/ \#pragma message(/g' lc3plus.c + +# add ENABLE_HR_MODE to lc3plus.h +sed -i '/\#define LC3PLUS_H/a\#define ENABLE_HR_MODE' lc3plus.h + +# add macros missing from IVAS at the bottom of the file +sed -i '/\#endif \/\* __BASOP_UTIL_LC3PLUS_H__ \*\//i\ +/* Macros missing from IVAS BASOP, used only in LC3plus */\ +#define L_shl_pos(x, y) (L_shl((x), (y)))\ +#define L_shr_pos(x, y) (L_shr((x), (y)))\ +#define L_shr_pos_pos(x, y) (L_shr((x), (y)))\ +\ +#define L_shr_r_pos(x, shift) (L_shr_r(x, shift))\ +\ +#define shl_pos(x, y) (shl((x), (y)))\ +#define shr_pos(x, y) (shr((x), (y)))\ +#define shr_pos_pos(x, y) (shr((x), (y)))\ +\ +#define lshl_pos(x, y) (lshl(x, y))\ +#define UL_lshr_pos(x, y) (UL_lshr(x, y))\ +#define UL_lshl_pos(x, y) (UL_lshl(x, y))' basop_util_lc3plus.h + +# locally redefine duplicate symbols +sed -i '/\#endif \/\* DEFINES_H \*\//i\ +#define FIX_IVAS_LC3PLUS_DUPLICATES\ +#ifdef FIX_IVAS_LC3PLUS_DUPLICATES\ +#define abs_s_sat abs_s\ +#define BASOP_cfft BASOP_cfft_lc3plus\ +#define BASOP_getTables BASOP_getTables_lc3plus\ +#define BASOP_Util_Add_Mant32Exp BASOP_Util_Add_Mant32Exp_lc3plus\ +#define BASOP_Util_Cmp_Mant32Exp BASOP_Util_Cmp_Mant32Exp_lc3plus\ +#define BASOP_Util_Divide1616_Scale BASOP_Util_Divide1616_Scale_lc3plus\ +#define BASOP_Util_Divide3216_Scale BASOP_Util_Divide3216_Scale_lc3plus\ +#define BASOP_Util_InvLog2 BASOP_Util_InvLog2_lc3plus\ +#define BASOP_Util_Log2 BASOP_Util_Log2_lc3plus\ +#define Copy_Scale_sig Copy_Scale_sig_lc3plus\ +#define exp2_tab_long exp2_tab_long_lc3plus\ +#define exp2w_tab_long exp2w_tab_long_lc3plus\ +#define exp2x_tab_long exp2x_tab_long_lc3plus\ +#define get_size_mpvq_calc_offset_fx get_size_mpvq_calc_offset_fx_lc3plus\ +#define getScaleFactor16 getScaleFactor16_lc3plus\ +#define getScaleFactor32 getScaleFactor32_lc3plus\ +#define i_mult DEPR_i_mult\ +#define Inv16 Inv16_lc3plus\ +#define InvDiffTable InvDiffTable_lc3plus\ +#define InvIntTable InvIntTable_lc3plus\ +#define InvTable InvTable_lc3plus\ +#define ISqrt16 ISqrt16_lc3plus\ +#define ISqrtDiffTable ISqrtDiffTable_lc3plus\ +#define ISqrtTable ISqrtTable_lc3plus\ +#define L_abs_sat L_abs\ +#define ldCoeff ldCoeff_lc3plus\ +#define Norm32Norm Norm32Norm_lc3plus\ +#define POW_ATT_TABLE0 POW_ATT_TABLE0_lc3plus\ +#define POW_ATT_TABLE1 POW_ATT_TABLE1_lc3plus\ +#define RotVector_320 RotVector_320_lc3plus\ +#define RotVector_480 RotVector_480_lc3plus\ +#define Scale_sig Scale_sig_lc3plus\ +#define SineTable320 SineTable320_lc3plus\ +#define SineWindow120 SineWindow120_lc3plus\ +#define SineWindow160 SineWindow160_lc3plus\ +#define SineWindow180 SineWindow180_lc3plus\ +#define SineWindow20 SineWindow20_lc3plus\ +#define SineWindow30 SineWindow30_lc3plus\ +#define SineWindow320 SineWindow320_lc3plus\ +#define SineWindow40 SineWindow40_lc3plus\ +#define SineWindow60 SineWindow60_lc3plus\ +#define SineWindow80 SineWindow80_lc3plus\ +#define Sqrt16 Sqrt16_lc3plus\ +#define SqrtDiffTable SqrtDiffTable_lc3plus\ +#define SqrtTable SqrtTable_lc3plus\ +#define Tab_esc_nb Tab_esc_nb_lc3plus\ +#define tnsAcfWindow tnsAcfWindow_lc3plus\ +#endif /* FIX_IVAS_LC3PLUS_DUPLICATES */' defines.h + +cd - + +# Limit file permissions +printf "Limiting file permissions\n" +find lib_lc3plus -type f -print0 | + xargs -0 -I {} \ + chmod -x {} + +# include options.h and wmc_auto.h in all C files +printf "Including options.h and wmc_auto.h in C files\n" +find lib_lc3plus -name '*.[ch]' -type f -print0 | + xargs -0 -I {} \ + sed -i ' +# range: from 1st line to first match +1,/^#include/ { + # insert lines before first match + /^#include/ i\ +#include "options.h"\ +#include "wmc_auto.h" +} +' {} + +# Remove whitespace from preprocessor commands +printf "Removing whitespace from preprocessor commands\n" +find lib_lc3plus -name '*.[ch]' -type f -print0 | + xargs -0 -I {} \ + sed -i 's/^#[[:space:]]\+/#/' {} + +# Add .clang-format file to lib_lc3plus to disable formatting there +printf "Disabling clang-format in lib_lc3plus directory\n" +printf 'DisableFormat: true +SortIncludes: Never +' >lib_lc3plus/.clang-format diff --git a/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test_payload_format.c b/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test_payload_format.c index 613441c8ac..5685dd1704 100644 --- a/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test_payload_format.c +++ b/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test_payload_format.c @@ -38,7 +38,6 @@ the United Nations Convention on Contracts on the International Sales of Goods. #include "isar_lc3plus_common.h" #include "options.h" -#ifdef ISAR_BITSTREAM_UPDATE_LC3PLUS /* included by ivas_lc3plus_unit_test.c */ @@ -448,4 +447,3 @@ static int run_all_payload_tests( void ) } return 0; } -#endif -- GitLab From b0864e1addb1bd7e1c6d3b93e4dddc0bbd900d0a Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 28 Oct 2025 17:27:32 +0100 Subject: [PATCH 307/340] apply the optimization-prevention approach in all of DFT stereo --- lib_com/edct.c | 24 ++++++++++++++++++++++++ lib_com/options.h | 1 + lib_dec/ivas_stereo_dft_dec.c | 16 ++++++++++++++++ lib_dec/ivas_stereo_dft_plc.c | 9 +++++++++ lib_enc/ivas_stereo_dft_enc.c | 10 +++++++++- lib_enc/ivas_stereo_dft_enc_itd.c | 9 +++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib_com/edct.c b/lib_com/edct.c index c336ccdd81..09e6a99765 100644 --- a/lib_com/edct.c +++ b/lib_com/edct.c @@ -275,8 +275,14 @@ void edxt( { for ( k = Nm1 >> 1; k > 0; k-- ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float angle_tmp = scale * k; + const float wRe = cosf( angle_tmp ); + const float wIm = sinf( angle_tmp ); +#else const float wRe = cosf( scale * k ); const float wIm = sinf( scale * k ); +#endif y[k] /*pt 1*/ = wRe * re[k] + wIm * im[k]; y[length - k] = wIm * re[k] - wRe * im[k]; @@ -287,8 +293,14 @@ void edxt( { for ( k = Nm1 >> 1; k > 0; k-- ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float angle_tmp = scale * k; + const float wRe = cosf( angle_tmp ); + const float wIm = sinf( angle_tmp ); +#else const float wRe = cosf( scale * k ); const float wIm = sinf( scale * k ); +#endif y[Nm1 - k] = wRe * re[k] + wIm * im[k]; y[k - 1] = wIm * re[k] - wRe * im[k]; @@ -304,8 +316,14 @@ void edxt( { for ( k = Nm1 >> 1; k > 0; k-- ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float angle_tmp = scale * k; + const float wRe = cosf( angle_tmp ) * 0.5f; + const float wIm = sinf( angle_tmp ) * 0.5f; +#else const float wRe = cosf( scale * k ) * 0.5f; const float wIm = sinf( scale * k ) * 0.5f; +#endif re[k] = wRe * x[k] + wIm * x[length - k]; im[k] = wRe * x[length - k] - wIm * x[k]; @@ -316,8 +334,14 @@ void edxt( { for ( k = Nm1 >> 1; k > 0; k-- ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float angle_tmp = scale * k; + const float wRe = cosf( angle_tmp ) * 0.5f; + const float wIm = sinf( angle_tmp ) * 0.5f; +#else const float wRe = cosf( scale * k ) * 0.5f; const float wIm = sinf( scale * k ) * 0.5f; +#endif re[k] = wRe * x[Nm1 - k] + wIm * x[k - 1]; im[k] = wRe * x[k - 1] - wIm * x[Nm1 - k]; diff --git a/lib_com/options.h b/lib_com/options.h index c33b9eba21..795523096a 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -186,6 +186,7 @@ #define NONBE_1412_AVOID_ROUNDING_AZ_ELEV /* FhG: Avoid rounding when passing azimuth and elevation to efap_determine_gains() */ #define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ #define NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS /* FhG: fix non-BE in DFT stereo encoder between optimization levels */ +#define NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 /* FhG: fix even more non-BEnesses */ /* ##################### End NON-BE switches ########################### */ diff --git a/lib_dec/ivas_stereo_dft_dec.c b/lib_dec/ivas_stereo_dft_dec.c index 47f880ded3..7b86528878 100644 --- a/lib_dec/ivas_stereo_dft_dec.c +++ b/lib_dec/ivas_stereo_dft_dec.c @@ -1384,8 +1384,16 @@ void stereo_dft_dec( if ( pgIpd[0] != 0.f ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float pgIpd_tmp; + + pgIpd_tmp = pgIpd[0]; + c0 = cosf( pgIpd_tmp ); + s0 = sinf( pgIpd_tmp ); +#else c0 = cosf( pgIpd[0] ); s0 = sinf( pgIpd[0] ); +#endif for ( i = hStereoDft->band_limits[b]; i < hStereoDft->band_limits[b + 1]; i++ ) { /*rotate L*/ @@ -1566,8 +1574,16 @@ void stereo_dft_dec( /* Active Upmix */ if ( pgIpd[0] != 0.f ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float pgIpd_tmp; + + pgIpd_tmp = pgIpd[0]; + c0 = cosf( pgIpd_tmp ); + s0 = sinf( pgIpd_tmp ); +#else c0 = cosf( pgIpd[0] ); s0 = sinf( pgIpd[0] ); +#endif for ( i = hStereoDft->band_limits[b]; i < hStereoDft->band_limits[b + 1]; i++ ) { /*rotate L*/ diff --git a/lib_dec/ivas_stereo_dft_plc.c b/lib_dec/ivas_stereo_dft_plc.c index 2186aa1b1c..f311b60aff 100644 --- a/lib_dec/ivas_stereo_dft_plc.c +++ b/lib_dec/ivas_stereo_dft_plc.c @@ -251,6 +251,9 @@ void stereo_dft_res_subst_spec( /* Apply phase adjustment of identified peaks, including Np=1 peak neighbors on each side */ for ( i = *num_plocs - 1; i >= 0; i-- ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float corr_phase_tmp; +#endif if ( k == 0 ) { /* For 1st subframe, apply reversed time ECU to get correct analysis window */ @@ -268,8 +271,14 @@ void stereo_dft_res_subst_spec( conj_sign = 1.0f; } +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + corr_phase_tmp = corr_phase; + cos_F = cosf( corr_phase_tmp ); + sin_F = sinf( corr_phase_tmp ); +#else cos_F = cosf( corr_phase ); sin_F = sinf( corr_phase ); +#endif idx = max( 0, plocs[i] - Np ); /* Iterate over plocs[i]-1:plocs[i]+1, considering the edges of the spectrum */ while ( ( idx < plocs[i] + Np + 1 ) && ( idx < L_res ) ) diff --git a/lib_enc/ivas_stereo_dft_enc.c b/lib_enc/ivas_stereo_dft_enc.c index b583690cf3..8faa0c1558 100755 --- a/lib_enc/ivas_stereo_dft_enc.c +++ b/lib_enc/ivas_stereo_dft_enc.c @@ -60,7 +60,7 @@ static FILE *pF = NULL; #define STEREO_DFT_NRG_PAST_MAX_BAND 9 #define STEREO_DFT_NRG_PAST_MAX_BAND_LB 4 -#define STEREO_DFT_DMX_CROSSOVER ( int16_t )( 132 * ( (float) ( STEREO_DFT_N_NS_ENC ) / STEREO_DFT_N_NS ) + 0.5f ) /* crossover bin between binwise and bandwise DMX */ +#define STEREO_DFT_DMX_CROSSOVER (int16_t) ( 132 * ( (float) ( STEREO_DFT_N_NS_ENC ) / STEREO_DFT_N_NS ) + 0.5f ) /* crossover bin between binwise and bandwise DMX */ #define ITD_VAD_E_BAND_N_INIT 200000 #define ITD_SID_PREV_FRAMES 5 @@ -1453,8 +1453,16 @@ void stereo_dft_enc_process( if ( pgIpd[0] != 0.f ) { +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float pgIpd_tmp; + + pgIpd_tmp = pgIpd[0]; + c = cosf( pgIpd_tmp ); + s = sinf( pgIpd_tmp ); +#else c = cosf( pgIpd[0] ); s = sinf( pgIpd[0] ); +#endif for ( i = hStereoDft->band_limits_dmx[b]; i < hStereoDft->band_limits_dmx[b + 1]; i++ ) { /*rotate L*/ diff --git a/lib_enc/ivas_stereo_dft_enc_itd.c b/lib_enc/ivas_stereo_dft_enc_itd.c index a095124f78..839cc3b104 100644 --- a/lib_enc/ivas_stereo_dft_enc_itd.c +++ b/lib_enc/ivas_stereo_dft_enc_itd.c @@ -869,9 +869,18 @@ void stereo_dft_enc_compute_itd( if ( hCPE->element_mode == IVAS_CPE_DFT && ( hItd->td_itd[k_offset] - hItd->td_itd[k_offset - 1] ) ) { float alphaD, c, s, c1, s1, ctmp, vtmp; +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + volatile float alphaD_tmp; +#endif alphaD = -2.f * EVS_PI * ( (float) hItd->td_itd[k_offset] - hItd->td_itd[k_offset - 1] ) / hStereoDft->NFFT; +#ifdef NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS_2 + alphaD_tmp = alphaD; + c1 = cosf( alphaD_tmp ); + s1 = sinf( alphaD_tmp ); +#else c1 = cosf( alphaD ); s1 = sinf( alphaD ); +#endif c = 1.f; /* cos(0) */ s = 0.f; /* sin(0) */ -- GitLab From 9f89c8fe3b981384e386414ef5294c46bcd56410 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Tue, 28 Oct 2025 17:38:12 +0100 Subject: [PATCH 308/340] apply clang-format --- lib_enc/ivas_stereo_dft_enc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 lib_enc/ivas_stereo_dft_enc.c diff --git a/lib_enc/ivas_stereo_dft_enc.c b/lib_enc/ivas_stereo_dft_enc.c old mode 100755 new mode 100644 index 8faa0c1558..e59dfa7c98 --- a/lib_enc/ivas_stereo_dft_enc.c +++ b/lib_enc/ivas_stereo_dft_enc.c @@ -60,7 +60,7 @@ static FILE *pF = NULL; #define STEREO_DFT_NRG_PAST_MAX_BAND 9 #define STEREO_DFT_NRG_PAST_MAX_BAND_LB 4 -#define STEREO_DFT_DMX_CROSSOVER (int16_t) ( 132 * ( (float) ( STEREO_DFT_N_NS_ENC ) / STEREO_DFT_N_NS ) + 0.5f ) /* crossover bin between binwise and bandwise DMX */ +#define STEREO_DFT_DMX_CROSSOVER ( int16_t )( 132 * ( (float) ( STEREO_DFT_N_NS_ENC ) / STEREO_DFT_N_NS ) + 0.5f ) /* crossover bin between binwise and bandwise DMX */ #define ITD_VAD_E_BAND_N_INIT 200000 #define ITD_SID_PREV_FRAMES 5 -- GitLab From 64f5af6e2afb6e62814f6a415a10f3c4d0dd04fb Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 28 Oct 2025 18:01:11 +0100 Subject: [PATCH 309/340] update integration script to avoid appending to .clang-format each run --- lib_lc3plus/.clang-format | 7 ------- scripts/lc3plus_lib_setup/get_lc3plus.sh | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib_lc3plus/.clang-format b/lib_lc3plus/.clang-format index a47b539e41..47a38a93f2 100644 --- a/lib_lc3plus/.clang-format +++ b/lib_lc3plus/.clang-format @@ -1,9 +1,2 @@ - -DisableFormat: true -SortIncludes: Never - -DisableFormat: true -SortIncludes: Never - DisableFormat: true SortIncludes: Never diff --git a/scripts/lc3plus_lib_setup/get_lc3plus.sh b/scripts/lc3plus_lib_setup/get_lc3plus.sh index 05d1e7d444..cda145e101 100755 --- a/scripts/lc3plus_lib_setup/get_lc3plus.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus.sh @@ -56,4 +56,4 @@ printf "Disabling clang-format in lib_lc3plus directory\n" printf ' DisableFormat: true SortIncludes: Never -' >>lib_lc3plus/.clang-format +' >lib_lc3plus/.clang-format -- GitLab From 80a94777b961851903eb96e7553e793e5d8a1072 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Tue, 28 Oct 2025 18:41:55 +0100 Subject: [PATCH 310/340] add new file com_entropy.c to MSVC solution --- Workspace_msvc/lib_lc3plus.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Workspace_msvc/lib_lc3plus.vcxproj b/Workspace_msvc/lib_lc3plus.vcxproj index 3f1527357a..7b434ae10a 100644 --- a/Workspace_msvc/lib_lc3plus.vcxproj +++ b/Workspace_msvc/lib_lc3plus.vcxproj @@ -107,6 +107,7 @@ + -- GitLab From 17e581b0d6c9c20e0d5f7c1a7f052c7f045ad690 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 29 Oct 2025 11:04:04 +0100 Subject: [PATCH 311/340] [fix] IVAS LC3plus unit test; add new helper function to translate integer framesize to newly introduced enum --- lib_isar/isar_lc3plus_common.c | 30 ++++++++++ lib_isar/isar_lc3plus_common.h | 3 + lib_isar/isar_lc3plus_dec.c | 2 +- lib_isar/isar_lc3plus_enc.c | 2 +- .../lc3plus_basop/ivas_lc3plus_unit_test.c | 56 +++++++++---------- .../lc3plus_float/ivas_lc3plus_unit_test.c | 38 ++++++------- 6 files changed, 82 insertions(+), 49 deletions(-) diff --git a/lib_isar/isar_lc3plus_common.c b/lib_isar/isar_lc3plus_common.c index a186908990..d7d5bf0253 100644 --- a/lib_isar/isar_lc3plus_common.c +++ b/lib_isar/isar_lc3plus_common.c @@ -92,3 +92,33 @@ ivas_error IVAS_LC3PLUS_LC3plusRtpErrToIvasErr( return IVAS_ERR_UNKNOWN; } + +/*-----------------------------------------------------------------------------------------* + * Function IVAS_LC3PLUS_UsToLC3plusFrameDuration() + * + * + *-----------------------------------------------------------------------------------------*/ + +LC3PLUS_FrameDuration IVAS_LC3PLUS_UsToLC3plusFrameDuration( + const int16_t lc3PlusFrameDurationUs ) +{ + switch ( lc3PlusFrameDurationUs ) + { +#ifdef CR9_C_ADD_1p25MS /* TODO LC3plus: verify switch stripping on update */ + case 1250: + return LC3PLUS_FRAME_DURATION_1p25MS; +#endif + case 2500: + return LC3PLUS_FRAME_DURATION_2p5MS; + case 5000: + return LC3PLUS_FRAME_DURATION_5MS; + case 7500: + return LC3PLUS_FRAME_DURATION_7p5MS; + case 10000: + return LC3PLUS_FRAME_DURATION_10MS; + default: + break; + } + + return LC3PLUS_FRAME_DURATION_UNDEFINED; +} diff --git a/lib_isar/isar_lc3plus_common.h b/lib_isar/isar_lc3plus_common.h index eb3e7b0ad2..3b52e5b3f1 100644 --- a/lib_isar/isar_lc3plus_common.h +++ b/lib_isar/isar_lc3plus_common.h @@ -61,4 +61,7 @@ ivas_error ISAR_LC3PLUS_LC3plusErrToIvasErr( const LC3PLUS_Error lc3PlusError ); /*! utility function to convert LC3PLUS_Errors to the suitable ivas_error */ ivas_error IVAS_LC3PLUS_LC3plusRtpErrToIvasErr( const LC3PLUS_RTP_ERR lc3PlusRtpError ); +/*! utility function to convert a value in microseconds to an LC3PLUS_FrameDuration */ +LC3PLUS_FrameDuration IVAS_LC3PLUS_UsToLC3plusFrameDuration( const int16_t lc3PlusFrameDuration ); + #endif /* ISAR_LC3PLUS_COM_H */ diff --git a/lib_isar/isar_lc3plus_dec.c b/lib_isar/isar_lc3plus_dec.c index c5d78f4446..0455144381 100644 --- a/lib_isar/isar_lc3plus_dec.c +++ b/lib_isar/isar_lc3plus_dec.c @@ -135,7 +135,7 @@ ivas_error ISAR_LC3PLUS_DEC_Open( return IVAS_ERROR( ISAR_LC3PLUS_LC3plusErrToIvasErr( err ), "lc3plus_dec_init failed\n" ); } - err = lc3plus_dec_set_frame_dms( ( *handle )->handles[iCh], config.lc3plus_frame_duration_us / 100 ); + err = lc3plus_dec_set_frame_dms( ( *handle )->handles[iCh], IVAS_LC3PLUS_UsToLC3plusFrameDuration( config.lc3plus_frame_duration_us ) ); if ( LC3PLUS_OK != err ) { ISAR_LC3PLUS_DEC_Close( handle ); diff --git a/lib_isar/isar_lc3plus_enc.c b/lib_isar/isar_lc3plus_enc.c index c6e2d31334..4be070b957 100644 --- a/lib_isar/isar_lc3plus_enc.c +++ b/lib_isar/isar_lc3plus_enc.c @@ -168,7 +168,7 @@ ivas_error ISAR_LC3PLUS_ENC_Open( return IVAS_ERROR( ISAR_LC3PLUS_LC3plusErrToIvasErr( err ), "lc3plus_enc_init failed\n" ); } - err = lc3plus_enc_set_frame_dms( ( *handle )->handles[iCh], config.lc3plus_frame_duration_us / 100 ); + err = lc3plus_enc_set_frame_dms( ( *handle )->handles[iCh], IVAS_LC3PLUS_UsToLC3plusFrameDuration( config.lc3plus_frame_duration_us ) ); if ( err != LC3PLUS_OK ) { ISAR_LC3PLUS_ENC_Close( handle ); diff --git a/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test.c b/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test.c index 7cdda56342..6833a90fea 100644 --- a/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test.c +++ b/scripts/split_rendering/lc3plus_basop/ivas_lc3plus_unit_test.c @@ -41,7 +41,7 @@ the United Nations Convention on Contracts on the International Sales of Goods. #include "lc3plus.h" #define MAX_SAMPLES_PER_CHANNEL 960 / 4 -#define DEFAULT_BPS 256000 +#define DEFAULT_BPS 256000 #ifndef PCM_SAMPLE_TYPEDEF_DEFINED #define PCM_SAMPLE_TYPEDEF_DEFINED @@ -93,23 +93,23 @@ static int encodeAndDecodeOneStereoFrame( LC3PLUS_CONFIG config, uint32_t bps ) uint8_t *bitstream_out = malloc( bitstreamSizePerIvasFrame ); memset( bitstream_out, 0, bitstreamSizePerIvasFrame ); - int perChannelBitrate = lc3plus_enc_get_real_bitrate(encHandle->handles[0]); - int perLc3plusFrameDataBlockOctets = encHandle->num_ftds * perChannelBitrate / 8 / (1000*1000/config.lc3plus_frame_duration_us); - int targetOctets = bps / 8 / (1000*1000/config.isar_frame_duration_us); - printf("IVAS-FS=%i LC3plus-FS=%i ch=%i targetBps=%i targetOctets=%i\n", config.isar_frame_duration_us, config.lc3plus_frame_duration_us, config.channels, bps, targetOctets); - printf(" coreBps=%i corePerChBps=%i coreCodecOctets=%i nFtds=%i \n", perChannelBitrate * encHandle->num_encs, perChannelBitrate, perLc3plusFrameDataBlockOctets, encHandle->num_ftds); + int perChannelBitrate = lc3plus_enc_get_real_bitrate( encHandle->handles[0] ); + int perLc3plusFrameDataBlockOctets = encHandle->num_ftds * perChannelBitrate / 8 / ( 1000 * 1000 / config.lc3plus_frame_duration_us ); + int targetOctets = bps / 8 / ( 1000 * 1000 / config.isar_frame_duration_us ); + printf( "IVAS-FS=%i LC3plus-FS=%i ch=%i targetBps=%i targetOctets=%i\n", config.isar_frame_duration_us, config.lc3plus_frame_duration_us, config.channels, bps, targetOctets ); + printf( " coreBps=%i corePerChBps=%i coreCodecOctets=%i nFtds=%i \n", perChannelBitrate * encHandle->num_encs, perChannelBitrate, perLc3plusFrameDataBlockOctets, encHandle->num_ftds ); int pfOctets = bitstreamSizePerIvasFrame - perLc3plusFrameDataBlockOctets; - int pfBps = pfOctets * 8 * (1000*1000 / config.isar_frame_duration_us); - printf(" payloadFormatBps=%i payloadFormatOctets=%i \n\n", pfBps, pfOctets); - if(pfBps <= 0) + int pfBps = pfOctets * 8 * ( 1000 * 1000 / config.isar_frame_duration_us ); + printf( " payloadFormatBps=%i payloadFormatOctets=%i \n\n", pfBps, pfOctets ); + if ( pfBps <= 0 ) { ISAR_LC3PLUS_ENC_Close( &encHandle ); return err; } Word16 Q_in[16]; - memset(Q_in, 0, sizeof(Q_in) ); - err = ISAR_LC3PLUS_ENC_Encode( encHandle, pcm_in, bitstream_out, bitstreamSizePerIvasFrame, Q_in ); + memset( Q_in, 0, sizeof( Q_in ) ); + err = ISAR_LC3PLUS_ENC_Encode( encHandle, pcm_in, bitstream_out, bitstreamSizePerIvasFrame, Q_in ); if ( IVAS_ERR_OK != err ) { ISAR_LC3PLUS_ENC_Close( &encHandle ); @@ -218,19 +218,19 @@ static int tryOpenEncoderWithInvalidBitrate( void ) { return 1; } - limitedBitrate = lc3plus_enc_get_real_bitrate(encHandle->handles[0]); - if(limitedBitrate != 320000) + limitedBitrate = lc3plus_enc_get_real_bitrate( encHandle->handles[0] ); + if ( limitedBitrate != 320000 ) { return 1; } - ISAR_LC3PLUS_ENC_Close(&encHandle); + ISAR_LC3PLUS_ENC_Close( &encHandle ); err = ISAR_LC3PLUS_ENC_Open( config, invalid_low_bps, &encHandle ); /* setting an invalid bitrate should trigger an error - which is what we expect */ if ( IVAS_ERR_LC3PLUS_INVALID_BITRATE != err ) { return 1; } - ISAR_LC3PLUS_ENC_Close(&encHandle); + ISAR_LC3PLUS_ENC_Close( &encHandle ); return 0; } @@ -295,13 +295,13 @@ static int tryCallEncoderApiWithInvalidParams( void ) } ISAR_LC3PLUS_ENC_Close( &invalidEncHandle ); Word16 Q_in[16]; - memset(Q_in, 0, sizeof(Q_in) ); - if ( IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, invalidPcm_in, invalidBitstream_out, bsSize, Q_in ) ) + memset( Q_in, 0, sizeof( Q_in ) ); + if ( IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, invalidPcm_in, invalidBitstream_out, bsSize, Q_in ) ) { return 1; } - memset(Q_in, 0, sizeof(Q_in) ); - if ( IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, pcm_in, invalidBitstream_out, bsSize, Q_in) || IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, invalidPcm_in, bitstream_out, bsSize, Q_in ) || IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, pcm_in, bitstream_out, bsSize, Q_in ) ) + memset( Q_in, 0, sizeof( Q_in ) ); + if ( IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, pcm_in, invalidBitstream_out, bsSize, Q_in ) || IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, invalidPcm_in, bitstream_out, bsSize, Q_in ) || IVAS_ERR_UNEXPECTED_NULL_POINTER != ISAR_LC3PLUS_ENC_Encode( invalidEncHandle, pcm_in, bitstream_out, bsSize, Q_in ) ) { return 1; } @@ -448,7 +448,7 @@ static int encodeOneFrame( void ) uint8_t *bitstream_out = malloc( bitstreamSizePerIvasFrame ); memset( bitstream_out, 0, bitstreamSizePerIvasFrame ); Word16 Q_in[16]; - memset(Q_in, 0, sizeof(Q_in) ); + memset( Q_in, 0, sizeof( Q_in ) ); err = ISAR_LC3PLUS_ENC_Encode( encHandle, pcm, bitstream_out, bitstreamSizePerIvasFrame, Q_in ); if ( IVAS_ERR_OK != err ) @@ -488,8 +488,8 @@ static int encodeAndDecodeOneMonoFrame( void ) uint8_t *bitstream_out = malloc( bitstreamSizePerIvasFrame ); memset( bitstream_out, 0, bitstreamSizePerIvasFrame ); Word16 Q_in[16]; - memset(Q_in, 0, sizeof(Q_in) ); - err = ISAR_LC3PLUS_ENC_Encode( encHandle, pcm_in, bitstream_out, bitstreamSizePerIvasFrame, Q_in ); + memset( Q_in, 0, sizeof( Q_in ) ); + err = ISAR_LC3PLUS_ENC_Encode( encHandle, pcm_in, bitstream_out, bitstreamSizePerIvasFrame, Q_in ); if ( IVAS_ERR_OK != err ) return err; ISAR_LC3PLUS_ENC_Close( &encHandle ); @@ -582,25 +582,25 @@ static int encodeAndDecodeOneStereoFrameIvas20msLc3plus2_5ms_48kHz( void ) static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_80kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 82*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 82 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_96kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 98*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 98 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_124kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 126*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 126 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_800kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 800*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 800 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_204800bpsPerChannel( void ) @@ -633,8 +633,8 @@ int main( int argc, char *argv[] ) { - (void)argc; - (void)argv; + (void) argc; + (void) argv; int ret = 0; ret = openCloseEncoder(); if ( ret != 0 ) diff --git a/scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test.c b/scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test.c index c15f14b4cc..8c2f1a1495 100644 --- a/scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test.c +++ b/scripts/split_rendering/lc3plus_float/ivas_lc3plus_unit_test.c @@ -41,7 +41,7 @@ the United Nations Convention on Contracts on the International Sales of Goods. #include "lc3plus.h" #define MAX_SAMPLES_PER_CHANNEL 960 / 4 -#define DEFAULT_BPS 256000 +#define DEFAULT_BPS 256000 static int encodeAndDecodeOneStereoFrame( LC3PLUS_CONFIG config, uint32_t bps ) { @@ -88,15 +88,15 @@ static int encodeAndDecodeOneStereoFrame( LC3PLUS_CONFIG config, uint32_t bps ) uint8_t *bitstream_out = malloc( bitstreamSizePerIvasFrame ); memset( bitstream_out, 0, bitstreamSizePerIvasFrame ); - int perChannelBitrate = lc3plus_enc_get_real_bitrate(encHandle->handles[0]); - int perLc3plusFrameDataBlockOctets = encHandle->num_ftds * perChannelBitrate / 8 / (1000*1000/config.lc3plus_frame_duration_us); - int targetOctets = bps / 8 / (1000*1000/config.isar_frame_duration_us); - printf("IVAS-FS=%i LC3plus-FS=%i ch=%i targetBps=%i targetOctets=%i\n", config.isar_frame_duration_us, config.lc3plus_frame_duration_us, config.channels, bps, targetOctets); - printf(" coreBps=%i corePerChBps=%i coreCodecOctets=%i nFtds=%i \n", perChannelBitrate * encHandle->num_encs, perChannelBitrate, perLc3plusFrameDataBlockOctets, encHandle->num_ftds); + int perChannelBitrate = lc3plus_enc_get_real_bitrate( encHandle->handles[0] ); + int perLc3plusFrameDataBlockOctets = encHandle->num_ftds * perChannelBitrate / 8 / ( 1000 * 1000 / config.lc3plus_frame_duration_us ); + int targetOctets = bps / 8 / ( 1000 * 1000 / config.isar_frame_duration_us ); + printf( "IVAS-FS=%i LC3plus-FS=%i ch=%i targetBps=%i targetOctets=%i\n", config.isar_frame_duration_us, config.lc3plus_frame_duration_us, config.channels, bps, targetOctets ); + printf( " coreBps=%i corePerChBps=%i coreCodecOctets=%i nFtds=%i \n", perChannelBitrate * encHandle->num_encs, perChannelBitrate, perLc3plusFrameDataBlockOctets, encHandle->num_ftds ); int pfOctets = bitstreamSizePerIvasFrame - perLc3plusFrameDataBlockOctets; - int pfBps = pfOctets * 8 * (1000*1000 / config.isar_frame_duration_us); - printf(" payloadFormatBps=%i payloadFormatOctets=%i \n\n", pfBps, pfOctets); - if(pfBps <= 0) + int pfBps = pfOctets * 8 * ( 1000 * 1000 / config.isar_frame_duration_us ); + printf( " payloadFormatBps=%i payloadFormatOctets=%i \n\n", pfBps, pfOctets ); + if ( pfBps <= 0 ) { ISAR_LC3PLUS_ENC_Close( &encHandle ); return err; @@ -211,19 +211,19 @@ static int tryOpenEncoderWithInvalidBitrate( void ) { return 1; } - limitedBitrate = lc3plus_enc_get_real_bitrate(encHandle->handles[0]); - if(limitedBitrate != 320000) + limitedBitrate = lc3plus_enc_get_real_bitrate( encHandle->handles[0] ); + if ( limitedBitrate != 320000 ) { return 1; } - ISAR_LC3PLUS_ENC_Close(&encHandle); + ISAR_LC3PLUS_ENC_Close( &encHandle ); err = ISAR_LC3PLUS_ENC_Open( config, invalid_low_bps, &encHandle ); /* setting an invalid bitrate should trigger an error - which is what we expect */ if ( IVAS_ERR_LC3PLUS_INVALID_BITRATE != err ) { return 1; } - ISAR_LC3PLUS_ENC_Close(&encHandle); + ISAR_LC3PLUS_ENC_Close( &encHandle ); return 0; } @@ -567,13 +567,13 @@ static int encodeAndDecodeOneStereoFrameIvas20msLc3plus2_5ms_48kHz( void ) static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_80kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 82*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 82 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_96kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 98*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 98 * 1000 ); } #ifdef LC3PLUS_LEA_COMPAT_BITRATES_48_6 @@ -583,13 +583,13 @@ static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_124kbpsPerChannel #endif { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 126*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 126 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_800kbpsPerChannel( void ) { LC3PLUS_CONFIG config = { .lc3plus_frame_duration_us = 10 * 1000, .isar_frame_duration_us = 10 * 1000, .channels = 2, .samplerate = 48000, .high_res_mode_enabled = 0 }; - return encodeAndDecodeOneStereoFrame( config, config.channels * 800*1000 ); + return encodeAndDecodeOneStereoFrame( config, config.channels * 800 * 1000 ); } static int encodeAndDecodeOneStereoFrameIvas10msLc3_10ms_48kHz_204800bpsPerChannel( void ) @@ -622,8 +622,8 @@ int main( int argc, char *argv[] ) { - (void)argc; - (void)argv; + (void) argc; + (void) argv; int ret = 0; ret = openCloseEncoder(); if ( ret != 0 ) -- GitLab From 74fc377d1d7f6d597675d63218f3afa3b11e9996 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 29 Oct 2025 11:56:30 +0100 Subject: [PATCH 312/340] add ivas-linux-fast tag to long selftest job it runs into timeout on slower runners --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71b9496761..0e97552180 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1488,6 +1488,8 @@ test-long-self-test: stage: compare resource_group: ivas-long-self-test-resource timeout: "50 minutes" + tags: + - ivas-linux-fast rules: - if: $CI_PIPELINE_SOURCE == 'web' && $MANUAL_PIPELINE_TYPE == 'test-long-self-test' allow_failure: -- GitLab From ca6d7a51ceafd51693b95ae0388c991c5a1203e2 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 29 Oct 2025 14:12:00 +0100 Subject: [PATCH 313/340] update LC3plus integration script and remove unsupported frame durations from helper func --- lib_isar/isar_lc3plus_common.c | 6 ------ lib_lc3plus/.clang-format | 1 + lib_lc3plus/defines.h | 2 -- lib_lc3plus/lc3plus.h | 2 +- scripts/lc3plus_lib_setup/get_lc3plus.sh | 4 ++-- 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib_isar/isar_lc3plus_common.c b/lib_isar/isar_lc3plus_common.c index d7d5bf0253..f7fe0db32e 100644 --- a/lib_isar/isar_lc3plus_common.c +++ b/lib_isar/isar_lc3plus_common.c @@ -104,16 +104,10 @@ LC3PLUS_FrameDuration IVAS_LC3PLUS_UsToLC3plusFrameDuration( { switch ( lc3PlusFrameDurationUs ) { -#ifdef CR9_C_ADD_1p25MS /* TODO LC3plus: verify switch stripping on update */ - case 1250: - return LC3PLUS_FRAME_DURATION_1p25MS; -#endif case 2500: return LC3PLUS_FRAME_DURATION_2p5MS; case 5000: return LC3PLUS_FRAME_DURATION_5MS; - case 7500: - return LC3PLUS_FRAME_DURATION_7p5MS; case 10000: return LC3PLUS_FRAME_DURATION_10MS; default: diff --git a/lib_lc3plus/.clang-format b/lib_lc3plus/.clang-format index 47a38a93f2..dc13f4a6e0 100644 --- a/lib_lc3plus/.clang-format +++ b/lib_lc3plus/.clang-format @@ -1,2 +1,3 @@ + DisableFormat: true SortIncludes: Never diff --git a/lib_lc3plus/defines.h b/lib_lc3plus/defines.h index ab34eb73e7..6d86c44bc2 100644 --- a/lib_lc3plus/defines.h +++ b/lib_lc3plus/defines.h @@ -389,8 +389,6 @@ typedef int32_t LC3_INT32; #endif /* CR9_C_ADD_1p25MS */ -/* RESIDUAL CODING */ -#define NPRM_RESQ 5 * MAX_LEN /* MDCT */ #define MDCT_MEM_LEN_MAX (MAX_LEN - ((180 * MAX_LEN) / 480)) diff --git a/lib_lc3plus/lc3plus.h b/lib_lc3plus/lc3plus.h index 802c8b83d3..955e22fe78 100644 --- a/lib_lc3plus/lc3plus.h +++ b/lib_lc3plus/lc3plus.h @@ -25,7 +25,7 @@ #include "options.h" #include "wmc_auto.h" -#define CR9_C_ADD_1p25MS /* including defines.h redefines NPRM_RESQ */ +#include "defines.h" /* Required for CR9_C_ADD_1p25MS */ #ifndef _MSC_VER #include diff --git a/scripts/lc3plus_lib_setup/get_lc3plus.sh b/scripts/lc3plus_lib_setup/get_lc3plus.sh index cda145e101..adb6e3aafb 100755 --- a/scripts/lc3plus_lib_setup/get_lc3plus.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus.sh @@ -48,8 +48,8 @@ find lib_lc3plus -name '*.[ch]' -type f -print0 | xargs -0 -I {} \ sed -i 's/^#[[:space:]]\+/#/' {} -# define CR9_C_ADD_1p25MS manually to avoid redefinition of NPRM_RESQ -sed -i 's|#include "defines\.h" /\* Required for CR9_C_ADD_1p25MS \*/|#define CR9_C_ADD_1p25MS /\* including defines.h redefines NPRM_RESQ, so define this here manually \*/|' lib_lc3plus/lc3plus.h +# delete define of NPRM_RESQ - not used anywhere in LC3plus but conflicts with IVAS +sed -i '/^\/\* RESIDUAL CODING \*\/$/d; /^#define NPRM_RESQ 5 \* MAX_LEN$/d' lib_lc3plus/defines.h # Add .clang-format file to lib_lc3plus to disable formatting there printf "Disabling clang-format in lib_lc3plus directory\n" -- GitLab From 02ead286bd1a01900b313079f12b99430dbd91b7 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 29 Oct 2025 15:05:09 +0100 Subject: [PATCH 314/340] back to storing delay in milliseconds, not subframe indexes --- lib_rend/lib_rend.c | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 5df2ea9230..5bc8a82399 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -1525,7 +1525,7 @@ static ivas_error alignInputDelay( { inputIsm = (input_ism *) inputBase; #ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER - inputIsm->ism_metadata_delay_ms = (int16_t) roundf( inputIsm->ism_metadata_delay_ms + maxGlobalDelayNs / 1e6f / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); + inputIsm->ism_metadata_delay_ms = (int16_t) roundf( inputIsm->ism_metadata_delay_ms + maxGlobalDelayNs / 1e6f ); #else inputIsm->ism_metadata_delay_ms = maxGlobalDelayNs / 1e6f; #endif @@ -5492,22 +5492,19 @@ static ivas_error renderIsmToBinaural( { float tmpTDRendBuffer[MAX_OUTPUT_CHANNELS][L_FRAME48k]; ivas_error error; -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; -#endif push_wmops( "renderIsmToBinaural" ); -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / (float) BINAURAL_RENDERING_FRAME_SIZE_MS ); +#else ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); #endif + copyBufferTo2dArray( ismInput->base.inputBuffer, tmpTDRendBuffer ); -#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER - if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, ismInput->ism_metadata_delay_ms, -#else if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, ism_md_subframe_update_ext, -#endif *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpTDRendBuffer ) ) != IVAS_ERR_OK ) { return error; @@ -5703,25 +5700,21 @@ static ivas_error renderIsmToBinauralReverb( { float tmpRendBuffer[MAX_OUTPUT_CHANNELS][L_FRAME48k]; ivas_error error; -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; -#endif push_wmops( "renderIsmToBinauralRoom" ); -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / (float) BINAURAL_RENDERING_FRAME_SIZE_MS ); +#else ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); #endif copyBufferTo2dArray( ismInput->base.inputBuffer, tmpRendBuffer ); if ( ( error = ivas_td_binaural_renderer_ext( &ismInput->tdRendWrapper, ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, ismInput->hReverb, -#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER - ismInput->ism_metadata_delay_ms, *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpRendBuffer ) ) != IVAS_ERR_OK ) -#else ism_md_subframe_update_ext, *ismInput->base.ctx.pOutSampleRate, outAudio.config.numSamplesPerChannel, tmpRendBuffer ) ) != IVAS_ERR_OK ) -#endif { return error; } @@ -5888,20 +5881,20 @@ static ivas_error renderIsmToSplitBinaural( float tmpBinaural_CldfbIm[MAX_HEAD_ROT_POSES * BINAURAL_CHANNELS][CLDFB_NO_COL_MAX][CLDFB_NO_CHANNELS_MAX]; int16_t output_frame = ismInput->base.inputBuffer.config.numSamplesPerChannel; COMBINED_ORIENTATION_HANDLE pCombinedOrientationData; -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER int16_t ism_md_subframe_update_ext; -#endif push_wmops( "renderIsmToSplitBinaural" ); pSplitRendWrapper = ismInput->base.ctx.pSplitRendWrapper; pMultiBinPoseData = &pSplitRendWrapper->multiBinPoseData; -#ifndef RENDERER_MD_SYNC_DELAY_TO_INTEGER /* Metadata Delay to sync with audio delay converted from ms to 5ms (1000/50/4) subframe index */ +#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER + ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / (float) BINAURAL_RENDERING_FRAME_SIZE_MS ); +#else ism_md_subframe_update_ext = (int16_t) roundf( ismInput->ism_metadata_delay_ms / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); - #endif + pCombinedOrientationData = *ismInput->base.ctx.pCombinedOrientationData; if ( pMultiBinPoseData->poseCorrectionMode == ISAR_SPLIT_REND_POSE_CORRECTION_MODE_CLDFB ) @@ -5951,11 +5944,7 @@ static ivas_error renderIsmToSplitBinaural( /* Render */ if ( ( error = ivas_td_binaural_renderer_ext( ( pos_idx == 0 ) ? &ismInput->tdRendWrapper : &ismInput->splitTdRendWrappers[pos_idx - 1], ismInput->base.inConfig, NULL, ismInput->base.ctx.pCombinedOrientationData, &ismInput->currentPos, -#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER - NULL, ismInput->ism_metadata_delay_ms, *ismInput->base.ctx.pOutSampleRate, output_frame, tmpProcessing ) ) != IVAS_ERR_OK ) -#else NULL, ism_md_subframe_update_ext, *ismInput->base.ctx.pOutSampleRate, output_frame, tmpProcessing ) ) != IVAS_ERR_OK ) -#endif { return error; } @@ -7736,11 +7725,7 @@ ivas_error IVAS_REND_SetIsmMetadataDelay( for ( i = 0; i < RENDERER_MAX_ISM_INPUTS; ++i ) { -#ifdef RENDERER_MD_SYNC_DELAY_TO_INTEGER - hIvasRend->inputsIsm[i].ism_metadata_delay_ms = (int16_t) roundf( sync_md_delay / ( 1000.f / FRAMES_PER_SEC / MAX_PARAM_SPATIAL_SUBFRAMES ) ); -#else hIvasRend->inputsIsm[i].ism_metadata_delay_ms = sync_md_delay; -#endif } return IVAS_ERR_OK; @@ -8005,7 +7990,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From b9b31e1e3b3b4020b6561ea96bf0c3e97fcdd5f6 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Wed, 29 Oct 2025 15:18:08 +0100 Subject: [PATCH 315/340] clang-format --- lib_rend/lib_rend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_rend/lib_rend.c b/lib_rend/lib_rend.c index 5bc8a82399..4bb1024aae 100644 --- a/lib_rend/lib_rend.c +++ b/lib_rend/lib_rend.c @@ -7990,7 +7990,7 @@ ivas_error IVAS_REND_GetSplitBinauralBitstream( Cldfb_RealBuffer_Binaural, Cldfb_ImagBuffer_Binaural, #endif - (const int16_t) ( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), + ( const int16_t )( ( BINAURAL_MAXBANDS * hIvasRend->sampleRateOut ) / 48000 ), tmpBinaural, 1, cldfb_in_flag, -- GitLab From 922595ae7b15d75b079b31f57095bd32810b5f5f Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 29 Oct 2025 16:15:08 +0100 Subject: [PATCH 316/340] update LC3plus integration script and code --- lib_lc3plus/ari_codec.c | 4 +-- lib_lc3plus/dec_entropy.c | 4 +-- lib_lc3plus/defines.h | 24 ++++++++--------- lib_lc3plus/enc_lc3_fl.c | 4 +-- lib_lc3plus/estimate_global_gain.c | 30 ++++++++++----------- lib_lc3plus/lc3plus.c | 6 +++-- lib_lc3plus/ltpf_decoder.c | 12 ++++++--- lib_lc3plus/setup_com_lc3plus.c | 2 +- lib_lc3plus/sns_compute_scf.c | 2 +- lib_lc3plus/sns_quantize_scf.c | 20 +++++++------- scripts/lc3plus_lib_setup/get_lc3plus.sh | 6 +++-- scripts/lc3plus_lib_setup/get_lc3plus_fx.sh | 6 +++-- scripts/prepare_instrumentation.sh | 2 +- 13 files changed, 66 insertions(+), 56 deletions(-) diff --git a/lib_lc3plus/ari_codec.c b/lib_lc3plus/ari_codec.c index 62e39130fc..4721a4c1e6 100644 --- a/lib_lc3plus/ari_codec.c +++ b/lib_lc3plus/ari_codec.c @@ -153,7 +153,7 @@ static LC3_INT16 pc_check_bytes(LC3_INT32* bp, Decoder_State_fl* st_fl, LC3_INT3 #ifdef WMOPS pop_wmops(); -#endif +#endif return 0; } @@ -199,7 +199,7 @@ static LC3_INT16 pc_check_bytes_ac_decode(LC3_INT32* bp, Decoder_State_fl* st_fl #ifdef WMOPS pop_wmops(); -#endif +#endif return 0; } #endif diff --git a/lib_lc3plus/dec_entropy.c b/lib_lc3plus/dec_entropy.c index 0d40df1020..ae39df0169 100644 --- a/lib_lc3plus/dec_entropy.c +++ b/lib_lc3plus/dec_entropy.c @@ -519,7 +519,7 @@ void readSNSData_fl(LC3_UINT8* ptr, LC3_INT32* bfi, LC3_INT32* mask_side_local, } /*10+ bits*/ } #endif -#ifdef LRSNS_PC_SIGNAL_FIX +#ifdef LRSNS_PC_SIGNAL_FIX assert(*bfi == 0 || *bfi == 1 ); /* local SNS BFI-flag output check */ #endif } @@ -571,7 +571,7 @@ void processDecoderEntropy_fl(LC3_UINT8* bytes, LC3_INT numbytes, LC3_INT* mask_ plc_trigger_last_nz = 1; /* Last non-zero tuple */ plc_trigger_SNS1 = 1; /* SNS-VQ 2nd stage MPVQ data (24-25 bits) */ -#ifdef LRSNS_10MS_BFISIGNAL_FIX +#ifdef LRSNS_10MS_BFISIGNAL_FIX plc_trigger_SNS2 = 1; /* SNS-VQ 2nd stage MPVQ data (10-16 bits) */ #else plc_trigger_SNS2 = 2; /* SNS-VQ 2nd stage MPVQ data (10-16 bits) */ diff --git a/lib_lc3plus/defines.h b/lib_lc3plus/defines.h index 6d86c44bc2..de7b1be260 100644 --- a/lib_lc3plus/defines.h +++ b/lib_lc3plus/defines.h @@ -123,23 +123,23 @@ typedef uint32_t LC3_UINT32; #define FIX_BOTH_1p25_WB_GLOBGAINOFFSET_NONBE /* 1p25 curve tilt calulation corrected and made into BASOP */ #define FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE -135 /* 1p25 curve tilt calulation limited to value -135 kbps for NB&WB */ -#define FIX_BOTH_1p25_TEST_NEW_GG_EST2 /* GG_EST2_will use bands with two coeffs in each instead of four, for 1p25 WB and 1p25 SSWB , note only active for regular, hrmode==0 */ +#define FIX_BOTH_1p25_TEST_NEW_GG_EST2 /* GG_EST2_will use bands with two coeffs in each instead of four, for 1p25 WB and 1p25 SSWB , note only active for regular, hrmode==0 */ #ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 -#define FIX_BOTH_1p25_GG_EST_SWB_FB /* 1.25ms GG_EST update for SWB/FB, better RD curve float and BASOP , active for hrmode==0 */ +#define FIX_BOTH_1p25_GG_EST_SWB_FB /* 1.25ms GG_EST update for SWB/FB, better RD curve float and BASOP , active for hrmode==0 */ #endif /* defines to activate 2 or 3 tuple 1.25ms loops for all or any of WB,SSWB,SWB,FB */ #define FIX_1p25_GG_EST_TUPLES /* 1.25 ms will use 2 or 3 tuples, note only active for regular, hrmode==0 */ #ifdef FIX_1p25_GG_EST_TUPLES -#define GG_1p25_WB_TUPLES 2 -#define GG_1p25_SSWB_TUPLES 2 -#define GG_1p25_SWB_TUPLES 2 -#define GG_1p25_FB_TUPLES 3 -#define GG_1p25_MAX_TUPLES MAX(MAX(GG_1p25_WB_TUPLES ,GG_1p25_SSWB_TUPLES ), MAX(GG_1p25_SWB_TUPLES ,GG_1p25_FB_TUPLES)) /* used to control common energy loop */ - -#ifndef FIX_BOTH_1p25_ALLOC_SPECTRUM -#define FIX_BOTH_1p25_ALLOC_SPECTRUM +#define GG_1p25_WB_TUPLES 2 +#define GG_1p25_SSWB_TUPLES 2 +#define GG_1p25_SWB_TUPLES 2 +#define GG_1p25_FB_TUPLES 3 +#define GG_1p25_MAX_TUPLES MAX(MAX(GG_1p25_WB_TUPLES ,GG_1p25_SSWB_TUPLES ), MAX(GG_1p25_SWB_TUPLES ,GG_1p25_FB_TUPLES)) /* used to control common energy loop */ + +#ifndef FIX_BOTH_1p25_ALLOC_SPECTRUM +#define FIX_BOTH_1p25_ALLOC_SPECTRUM #endif #endif @@ -295,9 +295,9 @@ typedef int32_t LC3_INT32; #define G192_ONE 0x0081 #define READ_G192FER /* Allow C executable to also read G192 formatted FER files */ -#ifdef DEBUG +#ifdef DEBUG #ifdef READ_G192FER -#define READ_G192_FER_BYTE /* Allow C executable to also read G192 byte formatted FER files 0x20=BAD , 0x21=Good */ +#define READ_G192_FER_BYTE /* Allow C executable to also read G192 byte formatted FER files 0x20=BAD , 0x21=Good */ #endif #endif diff --git a/lib_lc3plus/enc_lc3_fl.c b/lib_lc3plus/enc_lc3_fl.c index a1a4af8418..7d2ec43d32 100644 --- a/lib_lc3plus/enc_lc3_fl.c +++ b/lib_lc3plus/enc_lc3_fl.c @@ -11,7 +11,7 @@ #include "wmc_auto.h" #include "functions.h" -#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM +#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM #include #endif @@ -39,7 +39,7 @@ static void Enc_LC3PLUS_Channel_fl(LC3PLUS_Enc* encoder, int channel, int32_t* s h_EncSetup = encoder->channel_setup[channel]; memset(bytes, 0, sizeof(uint8_t) * h_EncSetup->targetBytes); -#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM +#ifdef FIX_BOTH_1p25_ALLOC_SPECTRUM if (encoder->frame_dms == LC3PLUS_FRAME_DURATION_1p25MS) { assert(MAX_LEN >= (GG_1p25_MAX_TUPLES - 1) + (encoder->yLen)); diff --git a/lib_lc3plus/estimate_global_gain.c b/lib_lc3plus/estimate_global_gain.c index efa998a9ad..956402e733 100644 --- a/lib_lc3plus/estimate_global_gain.c +++ b/lib_lc3plus/estimate_global_gain.c @@ -23,24 +23,24 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC LC3_FLOAT g_min, x_max, tmp, ind, ind_min, target, ener; LC3_FLOAT en[MAX_LEN / 4]; LC3_FLOAT reg_val = 4.656612873077393e-10; -#ifdef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_1p25_GG_EST_TUPLES LC3_INT tuples[1 + 4] = { -1, GG_1p25_WB_TUPLES, GG_1p25_SSWB_TUPLES, GG_1p25_SWB_TUPLES, GG_1p25_FB_TUPLES }; LC3_INT bw_idx; #endif -#ifdef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_1p25_GG_EST_TUPLES LC3_INT32 lg_extra; if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) { assert((MAX_LEN) >= ((lg / GG_1p25_MAX_TUPLES) + 1)*GG_1p25_MAX_TUPLES); /* en size check, for 1p25ms max tuple size*/ } #else -#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 assert( (MAX_LEN / 4) >= ((8*4 + 2) / 2)); /* en size check, for 1p25ms SSWB */ #endif #endif -#ifdef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_1p25_GG_EST_TUPLES lg_extra = 0; bw_idx = MIN((lg / 10) - 1, 4); @@ -54,7 +54,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC } } #else -#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS +#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS LC3_INT32 lg_extra; lg_extra = lg - 4 * (lg / 4); @@ -111,7 +111,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC assert(LC3_POW(10, ind_min / 28.0) >= g_min); assert(ind_min <= (255 + quantizedGainOff)); -#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS +#ifdef FIX_FLOAT_ENC_QUANTIZE_1P25MS_512KBPS N = lg + lg_extra; #else N = lg; @@ -119,7 +119,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC #ifndef FIX_1p25_GG_EST_TUPLES -#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 /* increase to at least 10 analysis bands for WB, SSWB 1p25ms */ if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS && lg <= 30) { @@ -195,7 +195,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC #ifdef FIX_1p25_GG_EST_TUPLES -#if GG_1p25_MAX_TUPLES == 2 +#if GG_1p25_MAX_TUPLES == 2 /* the tuple/2-block with halved limits, is here separated from the 4-block loop , optionally they can be parametrized into one function */ if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) @@ -271,7 +271,7 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC } else -#if GG_1p25_MAX_TUPLES == 3 +#if GG_1p25_MAX_TUPLES == 3 /* the 3tuple -block with modified limits, is here separated from the quadruple/4-block loop , optionally they can be parametrized into one function */ if (frame_ms == LC3PLUS_FRAME_DURATION_1p25MS) @@ -352,14 +352,14 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC } else -#endif /* end 2, 3 tuple loops */ +#endif /* end 2, 3 tuple loops */ -#ifdef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_1p25_GG_EST_TUPLES { /* brackets for 4 tuple loop */ assert((((lg + lg_extra) / 4) * 4) == (lg + lg_extra)); #endif -#ifndef FIX_1p25_GG_EST_TUPLES -#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 +#ifndef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 { #endif #endif @@ -416,11 +416,11 @@ void processEstimateGlobalGain_fl(LC3_FLOAT x[], LC3_INT lg, LC3_INT nbitsSQ, LC ind = MAX(ind_min, offset) - quantizedGainOff; } -#ifdef FIX_1p25_GG_EST_TUPLES +#ifdef FIX_1p25_GG_EST_TUPLES } /* end bracket for 4 tuple loop */ #else -#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 +#ifdef FIX_BOTH_1p25_TEST_NEW_GG_EST2 } #endif #endif diff --git a/lib_lc3plus/lc3plus.c b/lib_lc3plus/lc3plus.c index 473b9b7c02..594683cd2c 100644 --- a/lib_lc3plus/lc3plus.c +++ b/lib_lc3plus/lc3plus.c @@ -77,8 +77,10 @@ static int lc3plus_frame_size_supported(LC3PLUS_FrameDuration frame_dms) case LC3PLUS_FRAME_DURATION_7p5MS: /* fallthru */ case LC3PLUS_FRAME_DURATION_10MS: return 1; - default: return 0; + default: break; } + + return 0; } static int null_in_list(void **list, int n) @@ -169,7 +171,7 @@ int lc3plus_enc_get_real_bitrate(const LC3PLUS_Enc *encoder) bitrate = (int) ((totalBytes*8L) * 1000000L + (frame_ns - 1) ) / (frame_ns); #else bitrate = (totalBytes * 80000.0 + encoder->frame_dms - 1) / encoder->frame_dms; -#endif +#endif if (encoder->fs_in == 44100) { int rem = bitrate % 480; diff --git a/lib_lc3plus/ltpf_decoder.c b/lib_lc3plus/ltpf_decoder.c index f627e3fe89..48aa9a2d70 100644 --- a/lib_lc3plus/ltpf_decoder.c +++ b/lib_lc3plus/ltpf_decoder.c @@ -14,21 +14,25 @@ #ifdef CR9_C_ADD_1p25MS static LC3_INT16 get_continuation (LC3_INT32 fading_case, LC3PLUS_FrameDuration frame_dms, LC3_INT32 pos, LC3_INT32 total) { + LC3_INT16 retval; + if ( frame_dms != LC3PLUS_FRAME_DURATION_1p25MS ) { - return 0; + retval = 0; } else { if ( pos == total ) { - return 0; + retval = 0; } else { - return fading_case; + retval = fading_case; } } + + return retval; } #endif @@ -171,7 +175,7 @@ void process_ltpf_decoder_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* y, LC3_INT f else if ((concealMethod > 0) #ifdef CR9_C_ADD_1p25MS && (*mem_continuation == 0) -#endif +#endif ) { if (conf_beta_idx < 0) { if (mem_param[1] && *mem_beta_idx >= 0) diff --git a/lib_lc3plus/setup_com_lc3plus.c b/lib_lc3plus/setup_com_lc3plus.c index 89a4082170..055b6a0047 100644 --- a/lib_lc3plus/setup_com_lc3plus.c +++ b/lib_lc3plus/setup_com_lc3plus.c @@ -24,7 +24,7 @@ LC3_INT16 calc_GGainOffset_1p25(LC3_INT16 total_bits, LC3_INT16 fs_idx) tmp2 = -(MIN(115, tmp1) + tmp2); -#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE +#ifdef FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE if (fs_idx <= 1) { /* only NB and WB additionally limited to -135 */ tmp2 = MAX(tmp2, FIX_BOTH_1p25_WB_GLOBGAINOFFSET_LOWLIM_NONBE); diff --git a/lib_lc3plus/sns_compute_scf.c b/lib_lc3plus/sns_compute_scf.c index 083175a7db..6358985e8b 100644 --- a/lib_lc3plus/sns_compute_scf.c +++ b/lib_lc3plus/sns_compute_scf.c @@ -143,7 +143,7 @@ void processSnsComputeScf_fl(LC3_FLOAT* x, LC3_INT xLen, LC3_FLOAT* gains, LC3_I x[i] = x[i] * sns_preemph[i]; } } -#else +#else for (i = 0; i < 64; i++) { x[i] = x[i] * sns_preemph[i]; } diff --git a/lib_lc3plus/sns_quantize_scf.c b/lib_lc3plus/sns_quantize_scf.c index e91f0ad614..9079217128 100644 --- a/lib_lc3plus/sns_quantize_scf.c +++ b/lib_lc3plus/sns_quantize_scf.c @@ -178,7 +178,7 @@ void idct_II(LC3_FLOAT* in, LC3_FLOAT* out, LC3_INT len) } } -#ifdef FIX_FLOAT_ENC_PVQ_PULSE_LOOP +#ifdef FIX_FLOAT_ENC_PVQ_PULSE_LOOP static LC3_INT32 pvq_pulse_search_lc(LC3_FLOAT* xabs, LC3_FLOAT* ener, LC3_FLOAT* corr, LC3_INT32* y, LC3_INT32 start, LC3_INT32 end) { Dyn_Mem_Deluxe_In( @@ -742,7 +742,7 @@ void process_snsQuantizesScf_Dec(LC3_INT* scf_idx, LC3_FLOAT* scf_q) } } -#ifdef CR9_C_ADD_1p25MS_LRSNS +#ifdef CR9_C_ADD_1p25MS_LRSNS /* 29/30 bits optimized search functions for PVQ and FESS */ /* stage 2 submode shape 0: "splitLF" (N=5,K=6)(N=8,K=2) or (N=5,K=8)(N=8,K=0) , 4 gains */ @@ -1408,7 +1408,7 @@ LC3_INT32 snsQuantScfEncLRSt1ABC(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT * LC3_INT32 ind_saveC_ScaledW8 = -1; LC3_FLOAT min_mse_saveC_ScaledW8; -#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY UNUSED(ltpf_rx); #endif @@ -1498,7 +1498,7 @@ LC3_INT32 snsQuantScfEncLRSt1ABC(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT * /* float means are based on Word16 S16Q11 values, so that BASOP and float always may become BE in synthesis*/ int16_t CBCmeanp_ind = pitch_rx; /* 0 or 1 */ -#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY +#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY if (pitch_rx != 0 && ltpf_rx != 0) { CBCmeanp_ind = CBCmeanp_ind + 1; /*LTPF_rx is also active */ @@ -1587,7 +1587,7 @@ LC3_INT16 snsQuantScfEncLR(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT* envq, LC3_FLOAT st1_vectors[(SNSLR_MAX_PVQ_SEARCH_CAND+1)*M], *st1_vectorA, *st1_vectorB, *st1_vectorC, *st1_vector; LC3_FLOAT pvq_target_pre[M]; LC3_FLOAT pvq_target[M]; -#ifdef LRSNS_WMC_FIX +#ifdef LRSNS_WMC_FIX LC3_INT32 y[SNSLR_MAX_PVQ_SEARCH_CAND][M]; /* o: [3]*[0...M-1] */ #else LC3_INT32 y_tmp[3*M]; @@ -1605,7 +1605,7 @@ LC3_INT16 snsQuantScfEncLR(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT* envq, UNUSED(fix_shift_bits); /* used for assert */ -#ifndef LRSNS_WMC_FIX +#ifndef LRSNS_WMC_FIX LC3_INT32(*y)[M]; /* C-construct to allow matrix adressing into a scratch area */ #endif envelope_bits = -1; /* output : 9,10, 29/30 */ @@ -1618,7 +1618,7 @@ LC3_INT16 snsQuantScfEncLR(LC3_FLOAT* env, LC3_INT32* L_index, LC3_FLOAT* envq, st1_vector = &(st1_vectors[3 * M]); -#ifndef LRSNS_WMC_FIX +#ifndef LRSNS_WMC_FIX y = (LC3_INT32(*)[M]) &(y_tmp[0]); /* y is an NxM Matrix Ptr */ #endif { /* stage1 A,B,C */ @@ -1896,7 +1896,7 @@ void snsQuantScfDecLR(LC3_INT32* sns_vq_idx, LC3_FLOAT* scf_q, LC3_INT32 pitch_r LC3_INT16 sign_mask = 0x07ff; -#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY UNUSED(ltpf_rx); #endif @@ -1938,13 +1938,13 @@ void snsQuantScfDecLR(LC3_INT32* sns_vq_idx, LC3_FLOAT* scf_q, LC3_INT32 pitch_r /* cbC add harmonic mean , based on pitch_info availability */ pitch_rx = sns_vq_idx[3]; /* LTP active flag directly from dec_entropy */ -#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY +#ifdef LRSNS_CBC_NO_LTPF_DEPENDENCY ltpf_rx = 0; /* CB_C has no dependency on LTPF active flag */ #else ltpf_rx = sns_vq_idx[4]; /* LTPF active flag LTP active flag directly from dec_entropy */ #endif CBCmeanp_ind = pitch_rx; /* 0 or 1 */ -#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY +#ifndef LRSNS_CBC_NO_LTPF_DEPENDENCY if (pitch_rx != 0 && ltpf_rx != 0) { CBCmeanp_ind = CBCmeanp_ind + 1; /* high corr ltpf_rx is also active */ diff --git a/scripts/lc3plus_lib_setup/get_lc3plus.sh b/scripts/lc3plus_lib_setup/get_lc3plus.sh index adb6e3aafb..1aa5dc7b3a 100755 --- a/scripts/lc3plus_lib_setup/get_lc3plus.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus.sh @@ -45,8 +45,10 @@ find lib_lc3plus -name '*.[ch]' -type f -print0 | # Remove whitespace from preprocessor commands printf "Removing whitespace from preprocessor commands\n" find lib_lc3plus -name '*.[ch]' -type f -print0 | - xargs -0 -I {} \ - sed -i 's/^#[[:space:]]\+/#/' {} + xargs -0 -I {} sh -c ' + sed -i "s/^#[[:space:]]\+/#/" "$1" + sed -i "s/^#\(define\|undef\|ifdef\|ifndef\|endif\|if\|else\|elif\|include\)[[:space:]]\+/#\1 /" "$1" + ' _ {} # delete define of NPRM_RESQ - not used anywhere in LC3plus but conflicts with IVAS sed -i '/^\/\* RESIDUAL CODING \*\/$/d; /^#define NPRM_RESQ 5 \* MAX_LEN$/d' lib_lc3plus/defines.h diff --git a/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh b/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh index 3b7065c8b5..389fe76cad 100644 --- a/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus_fx.sh @@ -162,8 +162,10 @@ find lib_lc3plus -name '*.[ch]' -type f -print0 | # Remove whitespace from preprocessor commands printf "Removing whitespace from preprocessor commands\n" find lib_lc3plus -name '*.[ch]' -type f -print0 | - xargs -0 -I {} \ - sed -i 's/^#[[:space:]]\+/#/' {} + xargs -0 -I {} sh -c ' + sed -i "s/^#[[:space:]]\+/#/" "$1" + sed -i "s/^#\(define\|undef\|ifdef\|ifndef\|endif\|if\|else\|elif\|include\)[[:space:]]\+/#\1 /" "$1" + ' _ {} # Add .clang-format file to lib_lc3plus to disable formatting there printf "Disabling clang-format in lib_lc3plus directory\n" diff --git a/scripts/prepare_instrumentation.sh b/scripts/prepare_instrumentation.sh index 3698b12b26..906d85e785 100755 --- a/scripts/prepare_instrumentation.sh +++ b/scripts/prepare_instrumentation.sh @@ -156,7 +156,7 @@ if [ $ISAR -eq 1 ]; then echo " #define ENABLE_HR_MODE //#define DYNMEM_COUNT - #define CR10_A_ATTENUATION_CURVE_SELECTOR + //#define USE_LC3_OPERATORS #define SUBSET_NB #define SUBSET_WB #define SUBSET_SSWB -- GitLab From 98e89f1ee212bf720aafcf304e7efb2fbe549b53 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 29 Oct 2025 16:15:19 +0100 Subject: [PATCH 317/340] disable check for LC3plus code changes job --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71b9496761..f0185ccdc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1060,7 +1060,7 @@ check-first-frame-is-sid: expose_as: "logs-sidstart" expire_in: "5 days" -lc3plus-ensure-no-code-changes: +.lc3plus-ensure-no-code-changes: extends: - .test-job-linux - .rules-merge-request-to-main -- GitLab From 7c6f4137a787255a9c4ca834d090d393edba20f7 Mon Sep 17 00:00:00 2001 From: Jan Kiene Date: Thu, 30 Oct 2025 09:04:42 +0100 Subject: [PATCH 318/340] add job dependency that is needed for smoke test --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0e97552180..26d844b257 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2236,7 +2236,9 @@ sanitizer-test-osba-planar-hoa3-ism4: expire_in: 1 week coverage-merge: - extends: .rules-main-scheduled + extends: + - .rules-main-scheduled + - .test-job-linux-needs-testv-dir tags: - ivas-linux-fast stage: test -- GitLab From 45d7cd967a2f1c316fea72ef26b2fe8a0cf3a7e1 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Thu, 30 Oct 2025 10:42:40 +0200 Subject: [PATCH 319/340] Revert "[cleanup] accept NONBE_1324_TC_BUFFER_MEMOERY_KEEP" This reverts commit 7987fa593c49dd435fa774b1a719e94217755b0b. --- lib_com/options.h | 2 ++ lib_dec/ivas_jbm_dec.c | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib_com/options.h b/lib_com/options.h index c33b9eba21..fb8eb62a99 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -187,6 +187,8 @@ #define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ #define NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS /* FhG: fix non-BE in DFT stereo encoder between optimization levels */ +#define NONBE_1324_TC_BUFFER_MEMOERY_KEEP /* VA: issue 1324: do not reset TSM memory in JBM bitrate switching */ + /* ##################### End NON-BE switches ########################### */ /* ################## End DEVELOPMENT switches ######################### */ diff --git a/lib_dec/ivas_jbm_dec.c b/lib_dec/ivas_jbm_dec.c index 91522f3f3d..b19f0bdf7e 100644 --- a/lib_dec/ivas_jbm_dec.c +++ b/lib_dec/ivas_jbm_dec.c @@ -2284,13 +2284,16 @@ ivas_error ivas_jbm_dec_tc_buffer_reconfigure( const int16_t n_samples_granularity /* i : new granularity of the renderer/buffer */ ) { +#ifdef NONBE_1324_TC_BUFFER_MEMOERY_KEEP int16_t ch_idx, num_tc_buffer_mem, n_samples_still_available; float tc_buffer_mem[MAX_INTERN_CHANNELS][L_FRAME48k / MAX_PARAM_SPATIAL_SUBFRAMES - 1]; +#endif ivas_error error; DECODER_TC_BUFFER_HANDLE hTcBuffer; hTcBuffer = st_ivas->hTcBuffer; +#ifdef NONBE_1324_TC_BUFFER_MEMOERY_KEEP num_tc_buffer_mem = 0; n_samples_still_available = 0; @@ -2309,6 +2312,7 @@ ivas_error ivas_jbm_dec_tc_buffer_reconfigure( } } +#endif /* if granularity changes, adapt subframe_nb_slots */ if ( n_samples_granularity != hTcBuffer->n_samples_granularity ) { @@ -2348,6 +2352,12 @@ ivas_error ivas_jbm_dec_tc_buffer_reconfigure( hTcBuffer->nchan_buffer_full = nchan_full; hTcBuffer->n_samples_granularity = n_samples_granularity; +#ifndef NONBE_1324_TC_BUFFER_MEMOERY_KEEP +#ifdef DEBUGGING + /* what is remaining from last frames needs always be smaller than n_samples_granularity */ + assert( ( hTcBuffer->n_samples_buffered - hTcBuffer->n_samples_rendered ) < n_samples_granularity ); +#endif +#endif /* reallocate TC audio buffers */ ivas_jbm_dec_tc_audio_deallocate( hTcBuffer ); @@ -2357,12 +2367,14 @@ ivas_error ivas_jbm_dec_tc_buffer_reconfigure( return error; } +#ifdef NONBE_1324_TC_BUFFER_MEMOERY_KEEP /* propagate samples of the TC buffer from the previous frame */ for ( ch_idx = 0; ch_idx < num_tc_buffer_mem; ch_idx++ ) { mvr2r( tc_buffer_mem[ch_idx], hTcBuffer->tc_buffer_old[ch_idx], n_samples_still_available ); } +#endif return IVAS_ERR_OK; } -- GitLab From f5230359877f57fc20d9d259c5c3199be1bd3645 Mon Sep 17 00:00:00 2001 From: Tapani Pihlajakuja Date: Thu, 30 Oct 2025 10:44:52 +0200 Subject: [PATCH 320/340] Disable switch NONBE_1324_TC_BUFFER_MEMOERY_KEEP to align with BASOP for now. --- lib_com/options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_com/options.h b/lib_com/options.h index fb8eb62a99..c8c8be6895 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -187,7 +187,7 @@ #define NONBE_MDCT_ST_DTX_FIX_SUBOPT_SPATIAL_CNG /* FhG: Fix MDCT-Stereo comfort noise for certain noise types */ #define NONBE_FIX_NONBE_BETWEEN_OPTIMIZATION_LEVELS /* FhG: fix non-BE in DFT stereo encoder between optimization levels */ -#define NONBE_1324_TC_BUFFER_MEMOERY_KEEP /* VA: issue 1324: do not reset TSM memory in JBM bitrate switching */ +/*#define NONBE_1324_TC_BUFFER_MEMOERY_KEEP*/ /* VA: issue 1324: do not reset TSM memory in JBM bitrate switching */ /* ##################### End NON-BE switches ########################### */ -- GitLab From ec0b8eebd92ca6c69b6fed7ed387de281550df0a Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 30 Oct 2025 21:22:07 +1100 Subject: [PATCH 321/340] Remove DEBUG_PRINT encapsulation Signed-off-by: Ripinder Singh --- lib_dec/lib_dec.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index 1e34dcd1b0..a52a53ef35 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5543,13 +5543,6 @@ ivas_error IVAS_DEC_is_split_rendering_coded_out( return IVAS_ERR_OK; } -#ifdef IVAS_RTPDUMP -#ifdef DEBUGGING -#define DEBUG_PRINT fprintf -#else -#define DEBUG_PRINT( ... ) -#endif - /*---------------------------------------------------------------------* * IVAS_DEC_feedSinglePIorientation( ) * @@ -5623,6 +5616,7 @@ static void IVAS_DEC_setDiegeticInputPI( } #endif +#ifdef IVAS_RTPDUMP ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) { ivas_error error = IVAS_ERR_OK; @@ -5634,7 +5628,9 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui case IVAS_PI_SCENE_ORIENTATION: { IVAS_QUATERNION *quat = &piData->data.scene.orientation; - DEBUG_PRINT( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); +#ifdef DEBUGGING + fprintf( stdout, "PI_SCENE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); +#endif error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; @@ -5642,7 +5638,9 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui case IVAS_PI_DEVICE_ORIENTATION_COMPENSATED: { IVAS_QUATERNION *quat = &piData->data.deviceCompensated.orientation; - DEBUG_PRINT( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); +#ifdef DEBUGGING + fprintf( stdout, "PI_DEVICE_ORIENTATION : %f, %f, %f, %f\n", quat->w, quat->x, quat->y, quat->z ); +#endif error = IVAS_DEC_feedSinglePIorientation( hIvasDec, true, quat ); } break; @@ -5650,7 +5648,9 @@ ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, ui #ifdef RTP_S4_251135_CR26253_0016_REV1 case IVAS_PI_DIEGETIC_TYPE: { - DEBUG_PRINT( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); +#ifdef DEBUGGING + fprintf( stdout, "PI_DIEGETIC_TYPE : %d, %d, %d, %d, %d\n", piData->data.digeticIndicator.isDiegetic[0], piData->data.digeticIndicator.isDiegetic[1], piData->data.digeticIndicator.isDiegetic[2], piData->data.digeticIndicator.isDiegetic[3], piData->data.digeticIndicator.isDiegetic[4] ); +#endif IVAS_DEC_setDiegeticInputPI( hIvasDec, piData->data.digeticIndicator.isDiegetic ); } break; -- GitLab From 9bbf35f8b7e8637bc245ee91bc61d2e951b65a11 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 30 Oct 2025 21:35:09 +1100 Subject: [PATCH 322/340] Rename ApplyPiData to FeedPiDataToDecoder( Signed-off-by: Ripinder Singh --- apps/decoder.c | 2 +- lib_dec/lib_dec.c | 2 +- lib_dec/lib_dec.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index 411bf0a218..f050802ec5 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -3591,7 +3591,7 @@ static ivas_error decodeVoIP( numPiData++; } - if ( ( error = IVAS_RTP_ApplyPiData( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData ) ) != IVAS_ERR_OK ) + if ( ( error = IVAS_RTP_FeedPiDataToDecoder( hIvasDec, &ivasRtp.piData[ivasRtp.nProcPiData], numPiData ) ) != IVAS_ERR_OK ) { fprintf( stderr, "\nError in IVAS_DEC_VoIP_GetSamples: %s\n", IVAS_DEC_GetErrorMessage( error ) ); goto cleanup; diff --git a/lib_dec/lib_dec.c b/lib_dec/lib_dec.c index a52a53ef35..d8a602beb0 100644 --- a/lib_dec/lib_dec.c +++ b/lib_dec/lib_dec.c @@ -5617,7 +5617,7 @@ static void IVAS_DEC_setDiegeticInputPI( #endif #ifdef IVAS_RTPDUMP -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) +ivas_error IVAS_RTP_FeedPiDataToDecoder( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ) { ivas_error error = IVAS_ERR_OK; while ( numPiData-- ) diff --git a/lib_dec/lib_dec.h b/lib_dec/lib_dec.h index 6331f647f7..d0d2b501d8 100644 --- a/lib_dec/lib_dec.h +++ b/lib_dec/lib_dec.h @@ -559,7 +559,7 @@ void IVAS_DEC_PrintDisclaimer( #ifdef IVAS_RTPDUMP #include "ivas_rtp_pi_data.h" -ivas_error IVAS_RTP_ApplyPiData( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); +ivas_error IVAS_RTP_FeedPiDataToDecoder( IVAS_DEC_HANDLE hIvasDec, PIDATA_TS *piData, uint32_t numPiData ); #endif /* clang-format on */ -- GitLab From 1400b863f82d94e182421d02fc35888fd4b85619 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 30 Oct 2025 13:11:34 +0200 Subject: [PATCH 323/340] Use 2 bits for ET-field, put 3 bits version under flag --- lib_util/ivas_rtp_internal.h | 8 ++++++++ lib_util/ivas_rtp_payload.c | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h index f3569ca031..18625bc156 100644 --- a/lib_util/ivas_rtp_internal.h +++ b/lib_util/ivas_rtp_internal.h @@ -96,11 +96,19 @@ enum IVAS_RTP_HEADER_BITS EBYTE_CMR_T_IVAS = 0xF0, /* Initial E-byte indicating IVAS modes */ EBYTE_CMR_T_NO_REQ = 0xFF, /* If no bitrate and no CA mode requested for IVAS/EVS */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + EBYTE_BANDWIDTH_REQUEST = 0x40, /* Subsequent E-byte for Bandwidth Request */ + EBYTE_FORMAT_REQUEST = 0x50, /* Subsequent E-byte for Format Request */ + EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ + EBYTE_PI_INDICATOR = 0x60, /* Subsequent E-byte for PI Indicator */ + EBYTE_SR_REQUEST = 0x70, /* Subsequent E-byte for Split Rendering Request */ +#else EBYTE_BANDWIDTH_REQUEST = 0x80, /* Subsequent E-byte for Bandwidth Request */ EBYTE_FORMAT_REQUEST = 0x90, /* Subsequent E-byte for Format Request */ EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ EBYTE_PI_INDICATOR = 0xA0, /* Subsequent E-byte for PI Indicator */ EBYTE_SR_REQUEST = 0xB0, /* Subsequent E-byte for Split Rendering Request */ +#endif PI_HEADER_PF_LAST = 0x00, /* Last PI header of the Payload in PI Header */ PI_HEADER_PF_NOT_LAST = 0x80, /* Another PI header follows after this in PI Header */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 7345222f0c..26b25d1182 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1339,7 +1339,11 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t while ( nBytes < payload->length ) { uint8_t byte = payload->buffer[nBytes]; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + uint8_t ET = ( byte & ( ~MASK_3BIT ) ); +#else uint8_t ET = ( byte & ( ~MASK_4BIT ) ); +#endif if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) { -- GitLab From c928727ad423770d84fb32d18e7155d8de9e03c6 Mon Sep 17 00:00:00 2001 From: Lauros Pajunen Date: Thu, 30 Oct 2025 13:19:41 +0200 Subject: [PATCH 324/340] Correct order for flags --- lib_util/ivas_rtp_internal.h | 12 ++++++------ lib_util/ivas_rtp_payload.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib_util/ivas_rtp_internal.h b/lib_util/ivas_rtp_internal.h index 18625bc156..dc3dd9c129 100644 --- a/lib_util/ivas_rtp_internal.h +++ b/lib_util/ivas_rtp_internal.h @@ -97,17 +97,17 @@ enum IVAS_RTP_HEADER_BITS EBYTE_CMR_T_NO_REQ = 0xFF, /* If no bitrate and no CA mode requested for IVAS/EVS */ #ifdef RTP_S4_251135_CR26253_0016_REV1 - EBYTE_BANDWIDTH_REQUEST = 0x40, /* Subsequent E-byte for Bandwidth Request */ - EBYTE_FORMAT_REQUEST = 0x50, /* Subsequent E-byte for Format Request */ - EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ - EBYTE_PI_INDICATOR = 0x60, /* Subsequent E-byte for PI Indicator */ - EBYTE_SR_REQUEST = 0x70, /* Subsequent E-byte for Split Rendering Request */ -#else EBYTE_BANDWIDTH_REQUEST = 0x80, /* Subsequent E-byte for Bandwidth Request */ EBYTE_FORMAT_REQUEST = 0x90, /* Subsequent E-byte for Format Request */ EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ EBYTE_PI_INDICATOR = 0xA0, /* Subsequent E-byte for PI Indicator */ EBYTE_SR_REQUEST = 0xB0, /* Subsequent E-byte for Split Rendering Request */ +#else + EBYTE_BANDWIDTH_REQUEST = 0x40, /* Subsequent E-byte for Bandwidth Request */ + EBYTE_FORMAT_REQUEST = 0x50, /* Subsequent E-byte for Format Request */ + EBYTE_SUBFORMAT_REQUEST = 0x9F, /* Subsequent E-byte for SubFormat Request */ + EBYTE_PI_INDICATOR = 0x60, /* Subsequent E-byte for PI Indicator */ + EBYTE_SR_REQUEST = 0x70, /* Subsequent E-byte for Split Rendering Request */ #endif PI_HEADER_PF_LAST = 0x00, /* Last PI header of the Payload in PI Header */ diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 26b25d1182..a6e54abeeb 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1340,9 +1340,9 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t { uint8_t byte = payload->buffer[nBytes]; #ifdef RTP_S4_251135_CR26253_0016_REV1 - uint8_t ET = ( byte & ( ~MASK_3BIT ) ); -#else uint8_t ET = ( byte & ( ~MASK_4BIT ) ); +#else + uint8_t ET = ( byte & ( ~MASK_3BIT ) ); #endif if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) -- GitLab From 812901ac7d1e43319186d9bf22c4e5f0497f3450 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Thu, 23 Oct 2025 14:29:59 +1100 Subject: [PATCH 325/340] SR RTPdump ingest in IVAS post renderer * Generate a SR params file (simulating SR) for post renderer * Enhance RTP API to pass codec framesize duration in ms Signed-off-by: Ripinder Singh --- apps/decoder.c | 43 +++++++ apps/isar_post_rend.c | 227 ++++++++++++++++++++++++++++++++++++ lib_util/ivas_rtp_api.h | 3 + lib_util/ivas_rtp_file.c | 17 ++- lib_util/ivas_rtp_file.h | 7 +- lib_util/ivas_rtp_payload.c | 12 +- 6 files changed, 305 insertions(+), 4 deletions(-) diff --git a/apps/decoder.c b/apps/decoder.c index f050802ec5..59689807f6 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -1866,8 +1866,29 @@ static ivas_error initOnFirstGoodFrame( } #ifdef IVAS_RTPDUMP + /* Split Rendering RTPDump Output file */ if ( arg.rtpOutSR && srRtp != NULL ) { + FILE *fParamsSR = NULL; + char srParamsFile[FILENAME_MAX], *ext = ".sr.txt"; + strncpy( srParamsFile, arg.outputWavFilename, FILENAME_MAX - sizeof( ext ) ); + strncat( srParamsFile, ext, sizeof( ext ) + 1 ); + + /* Write the Split Rendering Params passed from SDP to srParamsFile */ + fParamsSR = fopen( srParamsFile, "w" ); + if ( NULL != fParamsSR ) + { + fprintf( fParamsSR, "CODEC = %s;\nDOF = %d;\nFRAMESIZE = %d;\nRENDERSIZE = %d;\nLC3PLUS_HIGHRES = %d;\n", + splitRendCodec == ISAR_SPLIT_REND_CODEC_LC3PLUS ? "LC3PLUS" : "LCLD", + poseCorrection, + splitRendCodecFrameSizeMs, + splitRendIsarFrameSizeMs, + lc3plusHighRes ); + fclose( fParamsSR ); + fParamsSR = NULL; + } + + /* Split Rendering RTPDump Output file */ if ( ( error = IVAS_RTP_WRITER_Init( srRtp, arg.outputWavFilename, 1000 / ( IVAS_NUM_FRAMES_PER_SEC * splitRendCodecFrameSizeMs ) ) ) != IVAS_ERR_OK ) { @@ -3057,7 +3078,11 @@ static ivas_error printBitstreamInfoVoip( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, 0, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; @@ -3099,7 +3124,11 @@ static ivas_error printBitstreamInfoVoip( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, NULL, &qBit ); +#else error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); +#endif #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSizeBits ); #endif @@ -3286,7 +3315,11 @@ static ivas_error decodeVoIP( case IVAS_DEC_INPUT_FORMAT_RTPDUMP: case IVAS_DEC_INPUT_FORMAT_RTPDUMP_HF: #ifdef IVAS_RTPDUMP +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, 0, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_RTP_READER_Init( &ivasRtp, arg.inputBitstreamFilename, arg.piOutputFilename, arg.outputConfig == IVAS_AUDIO_CONFIG_EXTERNAL, arg.outputWavFilename ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); goto cleanup; @@ -3349,7 +3382,11 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, NULL, &qBit ); +#else error = IVAS_RTP_ReadNextFrame( &ivasRtp, auPtr, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); +#endif initialTsOffsetSystemAndRTP = rtpTimeStamp - systemTime_ms * 16; /* For time mapping */ #else rtpdumpDepackerError = EVS_RTPDUMP_DEPACKER_readNextFrame( &rtpdumpDepacker, &rtpSequenceNumber, &rtpTimeStamp, &nextPacketRcvTime_ms, &isAMRWB_IOmode, &frameTypeIndex, &qBit, &auPtr, (uint16_t *) &auSize ); @@ -3533,7 +3570,11 @@ static ivas_error decodeVoIP( { auPtr = au; /* might have been set to RTP packet in prev call */ #ifdef IVAS_RTPDUMP +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = IVAS_RTP_ReadNextFrame( &ivasRtp, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, NULL, &qBit ); +#else error = IVAS_RTP_ReadNextFrame( &ivasRtp, au, &auSize, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &qBit ); +#endif /* IVAS RTP payload format has timescale 16000, JBM uses 1000 internally */ rtpTimeStamp = rtpTimeStamp / 16; @@ -3836,6 +3877,8 @@ static ivas_error decodeVoIP( fprintf( stderr, "\nError %s while pushing SR audio bitstream to RTP pack\n", ivas_error_to_string( error ) ); goto cleanup; } + splitRendBits->bits_written = 0; + splitRendBits->bits_read = 0; } #endif diff --git a/apps/isar_post_rend.c b/apps/isar_post_rend.c index 26fa12f6d4..7d8fd0939f 100644 --- a/apps/isar_post_rend.c +++ b/apps/isar_post_rend.c @@ -48,6 +48,9 @@ #include "debug.h" #endif #include "wmc_auto.h" +#ifdef RTP_S4_251135_CR26253_0016_REV1 +#include "ivas_rtp_file.h" +#endif #define WMC_TOOL_SKIP @@ -78,6 +81,9 @@ static typedef struct { +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool srRtp; +#endif IVAS_AUDIO_CONFIG audioConfig; int32_t inputChannelIndex; float gain_dB; @@ -99,6 +105,9 @@ typedef struct char executableName[POST_REND_MAX_CLI_ARG_LENGTH]; char inputFilePath[POST_REND_MAX_CLI_ARG_LENGTH]; char outputFilePath[POST_REND_MAX_CLI_ARG_LENGTH]; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + char srParamsFilePath[POST_REND_MAX_CLI_ARG_LENGTH]; +#endif int32_t sampleRate; InputConfig inConfig; OutputConfig outConfig; @@ -128,6 +137,9 @@ typedef enum CmdLnOptionId_listFormats, CmdLnOptionId_SplitRendBFIFile, CmdLnOptionId_framing, +#ifdef RTP_S4_251135_CR26253_0016_REV1 + CmdLnOptionId_srParamsFile, +#endif } CmdLnOptionId; static const CmdLnParser_Option cliOptions[] = { @@ -203,6 +215,14 @@ static const CmdLnParser_Option cliOptions[] = { .matchShort = "fr", .description = "Set Render audio framing.", }, +#ifdef RTP_S4_251135_CR26253_0016_REV1 + { + .id = CmdLnOptionId_srParamsFile, + .match = "sr_params", + .matchShort = "s", + .description = "Path to the split rendering init params file", + }, +#endif }; @@ -214,7 +234,11 @@ static const int32_t numCliOptions = sizeof( cliOptions ) / sizeof( CmdLnParser_ static void printSupportedAudioConfigs( void ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 +static IVAS_AUDIO_CONFIG parseAudioConfig( const char *configString, bool *srRtp ); +#else static IVAS_AUDIO_CONFIG parseAudioConfig( const char *configString ); +#endif static void convertOutputBuffer( const float *floatBuffer, const int16_t numSamplesPerChannel, const int16_t numChannels, int16_t *intBuffer ); @@ -302,12 +326,20 @@ static bool parseInConfig( } /* Check for single-format inputs. The given string should map to a member of AUDIO_CONFIG enum. */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + bool srRtp = false; + IVAS_AUDIO_CONFIG audioConfig = parseAudioConfig( inFormatStr, &srRtp ); +#else IVAS_AUDIO_CONFIG audioConfig = parseAudioConfig( inFormatStr ); +#endif switch ( audioConfig ) { case IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED: case IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_PCM: inConfig->numBinBuses = 1; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + inConfig->binBuses[0].srRtp = srRtp; +#endif inConfig->binBuses[0].audioConfig = audioConfig; inConfig->binBuses[0].inputChannelIndex = 0; inConfig->binBuses[0].gain_dB = 0.0f; @@ -358,11 +390,19 @@ static bool parseRenderFramesize( static IVAS_AUDIO_CONFIG parseAudioConfig( +#ifdef RTP_S4_251135_CR26253_0016_REV1 + const char *configString, + bool *srRtp ) +#else const char *configString ) +#endif { char charBuf[25]; charBuf[24] = '\0'; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + *srRtp = false; +#endif strncpy( charBuf, configString, sizeof( charBuf ) - 1 ); charBuf[sizeof( charBuf ) - 1] = '\0'; to_upper( charBuf ); @@ -379,6 +419,13 @@ static IVAS_AUDIO_CONFIG parseAudioConfig( { return IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( strcmp( charBuf, "RTPDUMP" ) == 0 ) + { + *srRtp = true; + return IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED; + } +#endif return IVAS_AUDIO_CONFIG_INVALID; } @@ -435,6 +482,9 @@ static CmdlnArgs defaultArgs( strncpy( args.executableName, executableName, POST_REND_MAX_CLI_ARG_LENGTH ); clearString( args.inputFilePath ); clearString( args.outputFilePath ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 + clearString( args.srParamsFilePath ); +#endif args.sampleRate = 0; args.outConfig.audioConfig = IVAS_AUDIO_CONFIG_INVALID; @@ -542,6 +592,12 @@ static void parseOption( } break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case CmdLnOptionId_srParamsFile: + assert( numOptionValues == 1 ); + strncpy( args->srParamsFilePath, optionValues[0], POST_REND_MAX_CLI_ARG_LENGTH - 1 ); + break; +#endif default: assert( 0 && "This should be unreachable - all command line options should be explicitly handled." ); break; @@ -578,6 +634,9 @@ static void printSupportedAudioConfigs( void ) "BINAURAL (output only)", "BINAURAL_SPLIT_PCM", "BINAURAL_SPLIT_CODED", +#ifdef RTP_S4_251135_CR26253_0016_REV1 + "RTPDUMP", +#endif }; fprintf( stdout, "Supported audio formats:\n" ); @@ -670,6 +729,96 @@ static void convertOutputBuffer( return; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 +static void trim( char *str ) +{ + char c; + int r = 0, w = 0; + while ( ( c = str[r] ) != 0 && ( c == ' ' || c == '\t' || c == ';' ) ) + { + r++; + } + + while ( ( c = str[r] ) != 0 && ( c != ' ' && c != '\t' && c != ';' ) ) + { + str[w++] = c; + r++; + } + str[w] = 0; +} + +static ivas_error parseSRParamsFile( + const char *srParamsFilePath, + ISAR_SPLIT_REND_CODEC *codec, + ISAR_SPLIT_REND_POSE_CORRECTION_MODE *poseCorrection, + int16_t *codec_frame_size_ms, + int16_t *isar_frame_size_ms, + int16_t *lc3plusHighRes ) +{ + FILE *fParamSR = fopen( srParamsFilePath, "r" ); + if ( NULL == fParamSR ) + { + fprintf( stderr, "error in opening srParams File %s)\n", srParamsFilePath ); + return IVAS_ERR_FAILED_FILE_OPEN; + } + + *codec = ISAR_SPLIT_REND_CODEC_NONE; + *poseCorrection = ISAR_SPLIT_REND_POSE_CORRECTION_MODE_NONE; + *codec_frame_size_ms = 5; + *lc3plusHighRes = 0; + + while ( !feof( fParamSR ) ) + { + char key[16], value[16]; + if ( 2 == fscanf( fParamSR, "%15s = %15s", key, value ) ) + { + trim( key ); + trim( value ); + + if ( 0 == strncmp( key, "CODEC", 5 ) ) + { + *codec = ( 0 == strncmp( value, "LCLD", 4 ) ) ? ISAR_SPLIT_REND_CODEC_LCLD : *codec; + *codec = ( 0 == strncmp( value, "LC3PLUS", 7 ) ) ? ISAR_SPLIT_REND_CODEC_LC3PLUS : *codec; + } + else if ( 0 == strncmp( key, "DOF", 3 ) ) + { + int val = atoi( value ); + if ( val == 0 || val == 1 ) + { + *poseCorrection = ( val == 0 ) ? ISAR_SPLIT_REND_POSE_CORRECTION_MODE_NONE : ISAR_SPLIT_REND_POSE_CORRECTION_MODE_CLDFB; + } + } + else if ( 0 == strncmp( key, "FRAMESIZE", 9 ) ) + { + int val = atoi( value ); + if ( val == 5 || val == 10 || val == 20 ) + { + *codec_frame_size_ms = (int16_t) val; + } + } + else if ( 0 == strncmp( key, "RENDERSIZE", 9 ) ) + { + int val = atoi( value ); + if ( val == 5 || val == 10 || val == 20 ) + { + *isar_frame_size_ms = (int16_t) val; + } + } + else if ( 0 == strncmp( key, "LC3PLUS_HIGHRES", 15 ) ) + { + int val = atoi( value ); + if ( val == 0 || val == 1 ) + { + *lc3plusHighRes = (int16_t) val; + } + } + } + } + + fclose( fParamSR ); + return IVAS_ERR_OK; +} +#endif /*------------------------------------------------------------------------------------------* * main() @@ -710,6 +859,9 @@ int main( int16_t i, numChannels; ivas_error error = IVAS_ERR_OK; bool splitBinNeedsNewFrame = true; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP srRTP = { 0 }; +#endif #ifdef WMOPS reset_wmops(); @@ -736,6 +888,9 @@ int main( convert_backslash( args.inputFilePath ); convert_backslash( args.outputFilePath ); convert_backslash( args.headRotationFilePath ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 + convert_backslash( args.srParamsFilePath ); +#endif /*------------------------------------------------------------------------------------------* * Open head-rotation file @@ -790,8 +945,34 @@ int main( } } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( ( args.inConfig.numBinBuses > 0 ) && ( args.inConfig.binBuses[0].srRtp ) ) + { + error = parseSRParamsFile( args.srParamsFilePath, + &bitsBuffer.config.codec, + &bitsBuffer.config.poseCorrection, + &bitsBuffer.config.codec_frame_size_ms, + &bitsBuffer.config.isar_frame_size_ms, + &bitsBuffer.config.lc3plusHighRes ); + if ( error != IVAS_ERR_OK ) + { + fprintf( stderr, "\nCould not open split rend params file %s\n", args.srParamsFilePath ); + goto cleanup; + } + + if ( ( error = IVAS_RTP_READER_Init( &srRTP, (uint32_t) bitsBuffer.config.codec_frame_size_ms, args.inputFilePath, NULL, false, NULL ) ) != IVAS_ERR_OK ) + { + fprintf( stderr, "error in IVAS_RTP_READER_Init(): %d\n", error ); + goto cleanup; + } + audioReader = NULL; + } + /*if split renderer is running in post renderer mode*/ + else if ( ( args.inConfig.numBinBuses > 0 ) && ( args.inConfig.binBuses[0].audioConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) ) +#else /*if split renderer is running in post renderer mode*/ if ( ( args.inConfig.numBinBuses > 0 ) && ( args.inConfig.binBuses[0].audioConfig == IVAS_AUDIO_CONFIG_BINAURAL_SPLIT_CODED ) ) +#endif { error = split_rend_reader_open( &hSplitRendFileReadWrite, args.inputFilePath, @@ -991,7 +1172,53 @@ int main( num_in_channels = inBuffer.config.numChannels; numSamplesRead = 0; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( srRTP.hRtpFile && splitBinNeedsNewFrame ) + { + IVAS_RTP_SR_INFO srInfo = { 0 }; + uint32_t rtpTimeStamp = 0, nextPacketRcvTime_ms = 0; + uint16_t rtpSequenceNumber = 0; + int16_t auSizeBits = 0; + bool qBit = false; + uint8_t *bitBuffer = bitsBuffer.bits; + int16_t frameMS = 0; + + numSamplesRead = (int16_t) inBufferSize; + bitsBuffer.config.bitsRead = 0; + bitsBuffer.config.bitsWritten = 0; + + while ( frameMS < bitsBuffer.config.isar_frame_size_ms ) + { + error = IVAS_RTP_ReadNextFrame( &srRTP, bitBuffer, &auSizeBits, &rtpTimeStamp, &rtpSequenceNumber, &nextPacketRcvTime_ms, &srInfo, &qBit ); + if ( error != IVAS_ERR_OK ) + { + if ( error == IVAS_ERR_END_OF_FILE ) + { + numSamplesRead = 0; + } + else + { + fprintf( stderr, "\nUnable to read from bitstream file!\n" ); + goto cleanup; + } + } + /* Ensure a SR RTP stream was received */ + if ( !srInfo.valid ) + { + fprintf( stderr, "\nNon-SR RTP stream detected !\n" ); + goto cleanup; + } + + bitBuffer += ( auSizeBits + 7 ) / 8; + bitsBuffer.config.bitsWritten += auSizeBits; + bitsBuffer.config.codec = srInfo.codec == IVAS_SR_TRANSPORT_LC3PLUS ? ISAR_SPLIT_REND_CODEC_LC3PLUS : ISAR_SPLIT_REND_CODEC_LCLD; + frameMS += bitsBuffer.config.codec_frame_size_ms; + } + } + else if ( ( hSplitRendFileReadWrite != NULL ) && splitBinNeedsNewFrame ) +#else if ( ( hSplitRendFileReadWrite != NULL ) && splitBinNeedsNewFrame ) +#endif { ivas_error error_tmp; diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 06f50ef9e3..405893581b 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -407,6 +407,9 @@ typedef struct IVAS_RTP_UNPACK *IVAS_RTP_UNPACK_HANDLE; /* rtp unpacker handle t typedef struct { uint32_t maxFramesPerPacket; /* maximum no of frame per packet expected during the session */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + uint32_t srCodecFrameSizeMs; /* split rendering transport codec frame size in ms (5/10/20) set by sdp negotiation */ +#endif } IVAS_RTP_UNPACK_CONFIG; /* Open an instance of the RTP unpacker and return a handle to rtp unpacker on success diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index eab863a8cb..698ea875be 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -693,7 +693,10 @@ ivas_error IVAS_RTP_WRITER_Init( } ivas_error IVAS_RTP_READER_Init( - IVAS_RTP *rtp, /* i/o : IVAS RTP File reader handle */ + IVAS_RTP *rtp, /* i/o : IVAS RTP File reader handle */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + uint32_t srCodecFrameSizeMs, /* i : SR Codec Framesize in ms */ +#endif const char *inputBitstreamFilename, /* i : Input rtpdump filename */ const char *piOutputFilename, /* i : Output PI data json filename */ bool isExtOutput, /* i : External output mode */ @@ -705,6 +708,7 @@ ivas_error IVAS_RTP_READER_Init( memset( rtp, 0, sizeof( IVAS_RTP ) ); rtp->unpackCfg.maxFramesPerPacket = IVAS_MAX_FRAMES_PER_RTP_PACKET; + rtp->unpackCfg.srCodecFrameSizeMs = srCodecFrameSizeMs; rtp->rtpPacket.buffer = rtp->packet; rtp->rtpPacket.capacity = sizeof( rtp->packet ); @@ -824,7 +828,10 @@ ivas_error IVAS_RTP_ReadNextFrame( uint32_t *rtpTimeStamp, /* o : RTP Timestamp for this frame */ uint16_t *rtpSequenceNumber, /* o : RTP sequence number for this packet */ uint32_t *nextPacketRcvTime_ms, /* i/o : Clock indicating packet receive times need in JBM */ - bool *qBit /* o : AMRWB Q bite as indicated in the RTP packet */ +#ifdef RTP_S4_251135_CR26253_0016_REV1 + IVAS_RTP_SR_INFO *srInfo, /* o : Split Rendering info if SR RTP frame unpacked */ +#endif + bool *qBit /* o : AMRWB Q bite as indicated in the RTP packet */ ) { ivas_error error = IVAS_ERR_OK; @@ -902,6 +909,12 @@ ivas_error IVAS_RTP_ReadNextFrame( rtp->isAMRWB_IOmode = isAMRWB_IOmode; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( srInfo != NULL ) + { + *srInfo = rtp->srInfo; + } +#endif *qBit = !rtp->speechLostIndicated; rtp->numFramesInPacket--; rtp->numFramesProduced++; diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index bd8d265873..bbe6f8180c 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -71,14 +71,19 @@ typedef struct } IVAS_RTP; ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 +ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, uint32_t srCodecFrameSizeMs, const char *inputBitstreamFilename, const char *piOutputFilename, bool isExtOutput, const char *outputWavFilename ); +#else ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, const char *inputBitstreamFilename, const char *piOutputFilename, bool isExtOutput, const char *outputWavFilename ); +#endif void IVAS_RTP_Term( IVAS_RTP *rtp ); #ifdef RTP_S4_251135_CR26253_0016_REV1 ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, const IVAS_RTP_SR_INFO *srInfo, int16_t auSizeBits, bool isMono, bool forcePacket ); +ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, IVAS_RTP_SR_INFO *srInfo, bool *qBit ); #else ivas_error IVAS_RTP_WriteNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t auSizeBits, bool isMono, bool forcePacket ); -#endif ivas_error IVAS_RTP_ReadNextFrame( IVAS_RTP *rtp, uint8_t *au, int16_t *auSizeBits, uint32_t *rtpTimeStamp, uint16_t *rtpSequenceNumber, uint32_t *nextPacketRcvTime_ms, bool *qBit ); +#endif void IVAS_RTP_LogPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent ); void IVAS_RTP_WriteExtPiData( FILE *f_piDataOut, const PIDATA_TS *piData, uint32_t nPiDataPresent, uint16_t numObj ); const char *IVAS_RTP_GetExtPiFilePath( IVAS_RTP *rtp ); diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index a6e54abeeb..42f33e685d 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -1399,7 +1399,11 @@ static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t return nBytes; } +#ifdef RTP_S4_251135_CR26253_0016_REV1 +static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numFrames, TOC_INFO *toc, uint32_t maxNumberOfToCBytes, uint32_t srCodecFrameSizeMs ) +#else static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBytes, uint32_t *numFrames, TOC_INFO *toc, uint32_t maxNumberOfToCBytes ) +#endif { bool headerFollows = true; uint32_t nBytes = *numBytes; @@ -1466,7 +1470,8 @@ static ivas_error parseToCByte( const IVAS_DATA_BUFFER *payload, uint32_t *numBy toc->srInfo.valid = true; toc->srInfo.diegetic = ( byte >> 6 ) & MASK_1BIT; toc->srInfo.codec = IVAS_SR_TRANSPORT_LCLD + ( ( byte >> 5 ) & MASK_1BIT ); - toc->auNumBits = ( SR_BR + 1 ) * 128000u / IVAS_NUM_FRAMES_PER_SEC; + toc->srInfo.bitrateKbps = ( SR_BR + 1 ) * 128000u; + toc->auNumBits = toc->srInfo.bitrateKbps * srCodecFrameSizeMs / 1000; } else { @@ -1648,7 +1653,12 @@ ivas_error IVAS_RTP_UNPACK_PushPayload( nBytes = parseSubsequentEByte( payload, nBytes, hUnpack->requests, &piDataIndicated ); /* Unpack the ToC Bytes => Extract number of frames in packet */ + +#ifdef RTP_S4_251135_CR26253_0016_REV1 + error = parseToCByte( payload, &nBytes, &numFrames, toc, sizeof( toc ) / sizeof( toc[0] ), hUnpack->initConfig.srCodecFrameSizeMs ); +#else error = parseToCByte( payload, &nBytes, &numFrames, toc, sizeof( toc ) / sizeof( toc[0] ) ); +#endif ERR_CHECK_RETURN( error ); /* Read frame bits */ -- GitLab From 5d9d0ec21f2e7e6b5c5ae924a644dccd4112044d Mon Sep 17 00:00:00 2001 From: rtyag Date: Fri, 31 Oct 2025 20:47:41 +1100 Subject: [PATCH 326/340] update the script to work with latest BE conformance changes --- scripts/ivas_conformance/runConformance.py | 31 +++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 6c29cd0094..7841b4c7b9 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -192,9 +192,13 @@ class MLDConformance: else: refEncOutput = self.getOutputFile(command) refEncOutput = refEncOutput.replace( - "$CUT_PATH/dut/param_file/enc/", + "$CUT_PATH/ref/param_file/enc/", f"{self.testvecDir}/testv/ref/param_file/enc/", ) + refEncOutput = refEncOutput.replace( + "$CUT_PATH/ref/sba_bs/pkt/", + f"{self.testvecDir}/testv/ref/sba_bs/pkt/", + ) # Run reference Encoder encCommandIdx = self.Commands["ENC"].index(command) @@ -233,10 +237,10 @@ class MLDConformance: refInputFile = command.split()[-2].replace( "$REF_PATH/ref", f"{self.testvDir}/ref" ) - refInputFile = refInputFile.replace("_cut.192.fer", ".192") + #refInputFile = refInputFile.replace("_cut.192.fer", ".192") # refInputFile = refInputFile.replace(".fer.192", ".192").replace(".192.fer", ".192").replace("_cut.192.fer", ".192").replace("_cut.192", ".192") refDecOutput = self.getOutputFile(command).replace( - "$CUT_PATH/dut", f"{self.testvDir}/ref" + "$CUT_PATH/ref", f"{self.testvDir}/ref" ) command = self.reformatCommand(command=command, ref=False) # command = command.replace("-VOIP", "") @@ -247,7 +251,10 @@ class MLDConformance: + [refInputFile, dutDecOutputFile] ) self.process(command=" ".join(dutDecCmd)) - self.mld("DEC", dutPytestTag, refFile=refDecOutput, dutFile=dutDecOutputFile) + + ##### skip MLD verification for files with only 1 frame as MLD does not run with such files. Possible solution: append 0s and then compare ##### + if refInputFile.find("_cut.192.fer") == -1: + self.mld("DEC", dutPytestTag, refFile=refDecOutput, dutFile=dutDecOutputFile) def getRendOutputFile(self, command: str): cmds = command.split() @@ -302,10 +309,10 @@ class MLDConformance: if ref: command = command.replace( - "$CUT_PATH/dut/param_file/enc/", f"{self.outputDir}/ref/enc/" + "$CUT_PATH/ref/param_file/enc/", f"{self.outputDir}/ref/enc/" ) command = command.replace( - "$CUT_PATH/dut/param_file/dec/", f"{self.outputDir}/ref/dec/" + "$CUT_PATH/ref/param_file/dec/", f"{self.outputDir}/ref/dec/" ) command = command.replace( "$CUT_PATH/renderer/cut/", f"{self.outputDir}/ref/renderer/" @@ -315,17 +322,17 @@ class MLDConformance: f"{self.outputDir}/ref/split_rendering/", ) command = command.replace( - "$CUT_PATH/dut/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" + "$CUT_PATH/ref/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" ) command = command.replace( - "$CUT_PATH/dut/sba_bs/raw/", f"{self.outputDir}/ref/dec/" + "$CUT_PATH/ref/sba_bs/raw/", f"{self.outputDir}/ref/dec/" ) else: command = command.replace( - "$CUT_PATH/dut/param_file/enc/", f"{self.outputDir}/dut/enc/" + "$CUT_PATH/ref/param_file/enc/", f"{self.outputDir}/dut/enc/" ) command = command.replace( - "$CUT_PATH/dut/param_file/dec/", f"{self.outputDir}/dut/dec/" + "$CUT_PATH/ref/param_file/dec/", f"{self.outputDir}/dut/dec/" ) command = command.replace( "$CUT_PATH/renderer/cut/", f"{self.outputDir}/dut/renderer/" @@ -335,10 +342,10 @@ class MLDConformance: f"{self.outputDir}/dut/split_rendering/", ) command = command.replace( - "$CUT_PATH/dut/sba_bs/pkt/", f"{self.outputDir}/dut/enc/" + "$CUT_PATH/ref/sba_bs/pkt/", f"{self.outputDir}/dut/enc/" ) command = command.replace( - "$CUT_PATH/dut/sba_bs/raw/", f"{self.outputDir}/dut/dec/" + "$CUT_PATH/ref/sba_bs/raw/", f"{self.outputDir}/dut/dec/" ) return command -- GitLab From b4e4cb983a9ebc2d5906095faeda564d5487b079 Mon Sep 17 00:00:00 2001 From: rtyag Date: Fri, 31 Oct 2025 21:00:10 +1100 Subject: [PATCH 327/340] adding a shell script to generate BE conformance package --- ivas_be_conf_test_gen.sh | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 ivas_be_conf_test_gen.sh diff --git a/ivas_be_conf_test_gen.sh b/ivas_be_conf_test_gen.sh new file mode 100644 index 0000000000..f5d4c524f6 --- /dev/null +++ b/ivas_be_conf_test_gen.sh @@ -0,0 +1,43 @@ +make -f Makefile clean +make -f Makefile -j CLANG=0 +cp IVAS_cod IVAS_cod_ref +cp IVAS_dec IVAS_dec_ref +cp IVAS_rend IVAS_rend_ref +cp ISAR_post_rend ISAR_post_rend_ref +python3 scripts/prepare_combined_format_inputs.py +python3 -m pytest -q tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html +python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt +rm -rf testvec +mkdir testvec +mkdir testvec/binauralRenderer_interface +mkdir testvec/testv +mkdir testvec/testv/renderer +mkdir testvec/testv/split_rendering +mkdir testvec/bin +cp scripts/testv/* testvec/testv +cp -r scripts/ls_layouts testvec +cp -r scripts/object_edit testvec +cp -r scripts/switchPaths testvec +cp -r scripts/trajectories testvec +cp -r scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface +cp -r tests/ref testvec/testv/ref +cp -r tests/dut/* testvec/testv/ref +cp -r tests/renderer/cut testvec/testv/renderer/ref +cp -r tests/split_rendering/cut testvec/testv/split_rendering/ref +cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs +cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns + +python3 scripts/cleanup_26252.py + +cp -r tests/conformance-test testvec/ +cp Readme_IVAS_dec.txt testvec +cp Readme_IVAS_enc.txt testvec +cp Readme_IVAS_rend.txt testvec +cp Readme_IVAS_JBM_dec.txt testvec +cp Readme_IVAS_ISAR_dec.txt testvec +cp Readme_IVAS_ISAR_post_rend.txt testvec + +cp IVAS_cod testvec/bin +cp IVAS_dec testvec/bin +cp IVAS_rend testvec/bin +cp ISAR_post_rend testvec/bin -- GitLab From 72f76c5bf491d7fe503a90f9cff1340581d611a8 Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Fri, 31 Oct 2025 13:01:54 +0100 Subject: [PATCH 328/340] [ci] reenable lc3plus-ensure-no-code-changes --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eef6803ede..0c5f72c334 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1060,7 +1060,7 @@ check-first-frame-is-sid: expose_as: "logs-sidstart" expire_in: "5 days" -.lc3plus-ensure-no-code-changes: +lc3plus-ensure-no-code-changes: extends: - .test-job-linux - .rules-merge-request-to-main @@ -2236,7 +2236,7 @@ sanitizer-test-osba-planar-hoa3-ism4: expire_in: 1 week coverage-merge: - extends: + extends: - .rules-main-scheduled - .test-job-linux-needs-testv-dir tags: -- GitLab From 6416a60c0057007b70a78769efae1d0f2a857fc9 Mon Sep 17 00:00:00 2001 From: rtyag Date: Fri, 31 Oct 2025 23:26:53 +1100 Subject: [PATCH 329/340] add CLANG O0 option to makefile, fix renderer tests partially --- Makefile | 3 +++ ivas_be_conf_test_gen.sh | 27 +++++++++++----------- scripts/ivas_conformance/runConformance.py | 8 +++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index c72aaacd8d..48c102c6ab 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,9 @@ LDLIBS += -lm # Clang sanitizer compiler options CCCLANG = clang +ifeq "$(CLANG)" "0" +CC = $(CCCLANG) +endif ifeq "$(CLANG)" "1" CC = $(CCCLANG) CFLAGS += -fsanitize=memory diff --git a/ivas_be_conf_test_gen.sh b/ivas_be_conf_test_gen.sh index f5d4c524f6..2536de1f89 100644 --- a/ivas_be_conf_test_gen.sh +++ b/ivas_be_conf_test_gen.sh @@ -5,27 +5,26 @@ cp IVAS_dec IVAS_dec_ref cp IVAS_rend IVAS_rend_ref cp ISAR_post_rend ISAR_post_rend_ref python3 scripts/prepare_combined_format_inputs.py -python3 -m pytest -q tests/codec_be_on_mr_nonselection tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html +python3 -m pytest -q tests/codec_be_on_mr_nonselection tests/renderer_short/test_renderer.py tests/split_rendering/test_split_rendering.py -v -n auto --update_ref 1 --create_ref --keep_files --html=report_cmd.html --self-contained-html python3 scripts/parse_commands.py report_cmd.html Readme_IVAS.txt rm -rf testvec mkdir testvec mkdir testvec/binauralRenderer_interface mkdir testvec/testv -mkdir testvec/testv/renderer +mkdir testvec/testv/renderer_short mkdir testvec/testv/split_rendering mkdir testvec/bin -cp scripts/testv/* testvec/testv -cp -r scripts/ls_layouts testvec -cp -r scripts/object_edit testvec -cp -r scripts/switchPaths testvec -cp -r scripts/trajectories testvec -cp -r scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface -cp -r tests/ref testvec/testv/ref -cp -r tests/dut/* testvec/testv/ref -cp -r tests/renderer/cut testvec/testv/renderer/ref -cp -r tests/split_rendering/cut testvec/testv/split_rendering/ref -cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs -cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns +cp -r scripts/testv/* testvec/testv +cp -r scripts/ls_layouts testvec +cp -r scripts/object_edit testvec +cp -r scripts/switchPaths testvec +cp -r scripts/trajectories testvec +cp -r scripts/binauralRenderer_interface/binaural_renderers_hrtf_data testvec/binauralRenderer_interface +cp -r tests/ref testvec/testv/ref +cp -r tests/renderer_short/ref testvec/testv/renderer_short/ref +cp -r tests/split_rendering/ref testvec/testv/split_rendering/ref +cp -r tests/split_rendering/renderer_configs testvec/testv/split_rendering/renderer_configs +cp -r tests/split_rendering/error_patterns testvec/testv/split_rendering/error_patterns python3 scripts/cleanup_26252.py diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 7841b4c7b9..00252b52a8 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -98,7 +98,7 @@ class MLDConformance: def createDirs(self): os.makedirs(self.outputDir, exist_ok=True) - subdirs = ["enc", "dec", "renderer", "split_rendering"] + subdirs = ["enc", "dec", "renderer_short", "split_rendering"] for odir in subdirs: os.makedirs(os.path.join(self.outputDir, "ref", odir), exist_ok=True) os.makedirs(os.path.join(self.outputDir, "dut", odir), exist_ok=True) @@ -265,7 +265,7 @@ class MLDConformance: def runOneRendererTest(self, tag: str, command: str): refRendOutputFile = self.getRendOutputFile(command).replace( - "$CUT_PATH/renderer", f"{self.testvDir}/renderer" + "$CUT_PATH/renderer_short", f"{self.testvDir}/renderer_short" ) rendPytestTag = os.path.basename(refRendOutputFile).split(".")[-2] command = self.reformatCommand(command=command, ref=False) @@ -315,7 +315,7 @@ class MLDConformance: "$CUT_PATH/ref/param_file/dec/", f"{self.outputDir}/ref/dec/" ) command = command.replace( - "$CUT_PATH/renderer/cut/", f"{self.outputDir}/ref/renderer/" + "$CUT_PATH/renderer_short/ref/", f"{self.outputDir}/ref/renderer_short/" ) command = command.replace( "$CUT_PATH/split_rendering/cut/", @@ -335,7 +335,7 @@ class MLDConformance: "$CUT_PATH/ref/param_file/dec/", f"{self.outputDir}/dut/dec/" ) command = command.replace( - "$CUT_PATH/renderer/cut/", f"{self.outputDir}/dut/renderer/" + "$CUT_PATH/renderer_short/ref/", f"{self.outputDir}/dut/renderer_short/" ) command = command.replace( "$CUT_PATH/split_rendering/cut/", -- GitLab From 1befb2289431526abff094b0797b8229e03ba1ea Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Fri, 31 Oct 2025 13:26:58 +0100 Subject: [PATCH 330/340] add patchfile for fixes needed in latest LC3plus release for WMC tool --- scripts/lc3plus_lib_setup/get_lc3plus.sh | 3 ++ scripts/lc3plus_lib_setup/lc3plus_float.patch | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 scripts/lc3plus_lib_setup/lc3plus_float.patch diff --git a/scripts/lc3plus_lib_setup/get_lc3plus.sh b/scripts/lc3plus_lib_setup/get_lc3plus.sh index 1aa5dc7b3a..2077484cc9 100755 --- a/scripts/lc3plus_lib_setup/get_lc3plus.sh +++ b/scripts/lc3plus_lib_setup/get_lc3plus.sh @@ -59,3 +59,6 @@ printf ' DisableFormat: true SortIncludes: Never ' >lib_lc3plus/.clang-format + +# temporary until LC3plus release is updated +git apply scripts/lc3plus_lib_setup/lc3plus_float.patch diff --git a/scripts/lc3plus_lib_setup/lc3plus_float.patch b/scripts/lc3plus_lib_setup/lc3plus_float.patch new file mode 100644 index 0000000000..3d3a4beae6 --- /dev/null +++ b/scripts/lc3plus_lib_setup/lc3plus_float.patch @@ -0,0 +1,49 @@ +diff --git b/lib_lc3plus/lc3plus.c a/lib_lc3plus/lc3plus.c +index 7063c4e183..594683cd2c 100644 +--- b/lib_lc3plus/lc3plus.c ++++ a/lib_lc3plus/lc3plus.c +@@ -77,8 +77,10 @@ static int lc3plus_frame_size_supported(LC3PLUS_FrameDuration frame_dms) + case LC3PLUS_FRAME_DURATION_7p5MS: /* fallthru */ + case LC3PLUS_FRAME_DURATION_10MS: + return 1; +- default: return 0; ++ default: break; + } ++ ++ return 0; + } + + static int null_in_list(void **list, int n) +diff --git b/lib_lc3plus/ltpf_decoder.c a/lib_lc3plus/ltpf_decoder.c +index f6560d9710..48aa9a2d70 100644 +--- b/lib_lc3plus/ltpf_decoder.c ++++ a/lib_lc3plus/ltpf_decoder.c +@@ -14,21 +14,25 @@ + #ifdef CR9_C_ADD_1p25MS + static LC3_INT16 get_continuation (LC3_INT32 fading_case, LC3PLUS_FrameDuration frame_dms, LC3_INT32 pos, LC3_INT32 total) + { ++ LC3_INT16 retval; ++ + if ( frame_dms != LC3PLUS_FRAME_DURATION_1p25MS ) + { +- return 0; ++ retval = 0; + } + else + { + if ( pos == total ) + { +- return 0; ++ retval = 0; + } + else + { +- return fading_case; ++ retval = fading_case; + } + } ++ ++ return retval; + } + #endif + -- GitLab From 9d0aa6652003476fbb5cb3749575946fcdc9d0de Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Tue, 28 Oct 2025 10:25:29 +1100 Subject: [PATCH 331/340] Ensure rtp header sequence and SSRC using fixed random seeds * Refactor rtpfile API for SSRC, Sequence No input from user Signed-off-by: Ripinder Singh --- apps/decoder.c | 15 +++++++++++++++ apps/encoder.c | 16 ++++++++++++++++ lib_com/options.h | 1 + lib_util/ivas_rtp_api.h | 13 +++++++++++++ lib_util/ivas_rtp_file.c | 14 +++++++++++++- lib_util/ivas_rtp_file.h | 4 ++++ lib_util/ivas_rtp_payload.c | 13 +++++++++++++ 7 files changed, 75 insertions(+), 1 deletion(-) diff --git a/apps/decoder.c b/apps/decoder.c index 59689807f6..6f7b929cd2 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -63,6 +63,9 @@ #include "flp_debug.h" #endif +#ifdef FIXED_RTP_SEQUENCE_NUM +#define RANDOM_INITSEED_DEC ( 0xFEEDFADE ) +#endif #define WMC_TOOL_SKIP @@ -1852,6 +1855,14 @@ static ivas_error initOnFirstGoodFrame( ISAR_SPLIT_REND_POSE_CORRECTION_MODE poseCorrection; int16_t splitRendIsarFrameSizeMs; int16_t lc3plusHighRes; +#ifdef FIXED_RTP_SEQUENCE_NUM + /* Ideally ssrc is negotiated via SDP and sequence number is radomized but we + use fixed seed for random num generator for regression based tests. Any realtime + application should implement this initialization seperately */ + srand( RANDOM_INITSEED_DEC ); + uint32_t ssrc = ( (uint32_t) rand() & 0x0000FFFF ) | ( (uint32_t) rand() << 16 ); + uint16_t seqNumInitVal = (uint16_t) ( rand() & 0xFFFF ); +#endif if ( ( error = IVAS_DEC_GetDelay( hIvasDec, delayNumSamples_temp, &delayTimeScale_temp ) ) != IVAS_ERR_OK ) { @@ -1890,7 +1901,11 @@ static ivas_error initOnFirstGoodFrame( /* Split Rendering RTPDump Output file */ +#ifdef FIXED_RTP_SEQUENCE_NUM + if ( ( error = IVAS_RTP_WRITER_Init( srRtp, arg.outputWavFilename, 1000 / ( IVAS_NUM_FRAMES_PER_SEC * splitRendCodecFrameSizeMs ), ssrc, seqNumInitVal ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_RTP_WRITER_Init( srRtp, arg.outputWavFilename, 1000 / ( IVAS_NUM_FRAMES_PER_SEC * splitRendCodecFrameSizeMs ) ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "\nError: Can't open SR output bitstream file for RTP output %s \n\n", arg.outputWavFilename ); return error; diff --git a/apps/encoder.c b/apps/encoder.c index 2bbd131a61..162ac9a19d 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -50,6 +50,9 @@ #include "flp_debug.h" #endif +#ifdef FIXED_RTP_SEQUENCE_NUM +#define RANDOM_INITSEED_ENC ( 0xFEEDDEAF ) +#endif #define WMC_TOOL_SKIP @@ -231,6 +234,15 @@ int main( IVAS_RTP ivasRtp = { 0 }; #endif +#ifdef FIXED_RTP_SEQUENCE_NUM + /* Ideally ssrc is negotiated via SDP and sequence number is radomized but we + use fixed seed for random num generator for regression based tests. Any realtime + application should implement this initialization seperately */ + srand( RANDOM_INITSEED_ENC ); + uint32_t ssrc = ( (uint32_t) rand() & 0x0000FFFF ) | ( (uint32_t) rand() << 16 ); + uint16_t seqNumInitVal = (uint16_t) ( rand() & 0xFFFF ); +#endif + /*------------------------------------------------------------------------------------------* * Parse command-line arguments *------------------------------------------------------------------------------------------*/ @@ -628,7 +640,11 @@ int main( if ( arg.rtpdumpOutput ) { +#ifdef FIXED_RTP_SEQUENCE_NUM + if ( ( error = IVAS_RTP_WRITER_Init( &ivasRtp, arg.outputBitstreamFilename, arg.numFramesPerPacket, ssrc, seqNumInitVal ) ) != IVAS_ERR_OK ) +#else if ( ( error = IVAS_RTP_WRITER_Init( &ivasRtp, arg.outputBitstreamFilename, arg.numFramesPerPacket ) ) != IVAS_ERR_OK ) +#endif { fprintf( stderr, "\nError: Can't open output bitstream file for RTP output %s \n\n", arg.outputBitstreamFilename ); goto cleanup; diff --git a/lib_com/options.h b/lib_com/options.h index b72be74b4f..a98f995caa 100755 --- a/lib_com/options.h +++ b/lib_com/options.h @@ -161,6 +161,7 @@ #define RTP_S4_251135_CR26253_0016_REV1 /* RTP Pack/Unpack API corresponding to CR 26253 */ #define IVAS_RTPDUMP /* RTPDUMP writing and reading for IVAS payloads */ +#define FIXED_RTP_SEQUENCE_NUM /* Remove random sequence number initialization */ /* ################### Start BE switches ################################# */ /* only BE switches wrt selection floating point code */ diff --git a/lib_util/ivas_rtp_api.h b/lib_util/ivas_rtp_api.h index 405893581b..8e551c5ffc 100644 --- a/lib_util/ivas_rtp_api.h +++ b/lib_util/ivas_rtp_api.h @@ -309,8 +309,10 @@ void IVAS_RTP_PACK_Close( ); /* Update the RTP Header structure */ +#ifdef FIXED_RTP_SEQUENCE_NUM ivas_error IVAS_RTP_PACK_UpdateHeader( IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint16_t seqNumInitVal, /* i : Initial sequence number to be used for 1st packet */ uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ uint8_t numCC, /* i : numCC indicates no. of contributing sources */ uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ @@ -318,6 +320,17 @@ ivas_error IVAS_RTP_PACK_UpdateHeader( uint16_t numExtensionBytes, /* i : length of the extension data */ uint8_t *extData /* i : extension data pointer */ ); +#else +ivas_error IVAS_RTP_PACK_UpdateHeader( + IVAS_RTP_PACK_HANDLE hIvasPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ + uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ + uint8_t numCC, /* i : numCC indicates no. of contributing sources */ + uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ + uint16_t extHeaderId, /* i : extension header ID */ + uint16_t numExtensionBytes, /* i : length of the extension data */ + uint8_t *extData /* i : extension data pointer */ +); +#endif /* Add requests for remote sender using a key value pair api * each key must be provided with a corresponding value type diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c index 698ea875be..f7d6d5d867 100644 --- a/lib_util/ivas_rtp_file.c +++ b/lib_util/ivas_rtp_file.c @@ -660,10 +660,18 @@ void IVAS_RTP_Term( ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, /* i/o : IVAS RTP File writer handle */ const char *outputBitstreamFilename, /* i : RTP Dump filename */ - uint32_t numFramesPerPacket /* i : No. of frames per packet desired */ +#ifdef FIXED_RTP_SEQUENCE_NUM + uint32_t numFramesPerPacket, /* i : No. of frames per packet desired */ + uint32_t SSRC, /* i : SSRC for RTP Header */ + uint16_t seqNumInitVal /* i : Initial seq number in rtp header */ +#else + uint32_t numFramesPerPacket /* i : No. of frames per packet desired */ +#endif ) { +#ifndef FIXED_RTP_SEQUENCE_NUM uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); +#endif ivas_error error = IVAS_ERR_OK; memset( rtp, 0, sizeof( IVAS_RTP ) ); @@ -682,7 +690,11 @@ ivas_error IVAS_RTP_WRITER_Init( return error; } +#ifdef FIXED_RTP_SEQUENCE_NUM + error = IVAS_RTP_PACK_UpdateHeader( rtp->hPack, seqNumInitVal, SSRC, 0, NULL, 0, 0, NULL ); +#else error = IVAS_RTP_PACK_UpdateHeader( rtp->hPack, SSRC, 0, NULL, 0, 0, NULL ); +#endif if ( error != IVAS_ERR_OK ) { fprintf( stderr, "error in IVAS_RTP_PACK_UpdateHeader(): %d\n", error ); diff --git a/lib_util/ivas_rtp_file.h b/lib_util/ivas_rtp_file.h index bbe6f8180c..70f8c99f0e 100644 --- a/lib_util/ivas_rtp_file.h +++ b/lib_util/ivas_rtp_file.h @@ -70,7 +70,11 @@ typedef struct #endif } IVAS_RTP; +#ifdef FIXED_RTP_SEQUENCE_NUM +ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket, uint32_t SSRC, uint16_t seqNumInitVal ); +#else ivas_error IVAS_RTP_WRITER_Init( IVAS_RTP *rtp, const char *outputBitstreamFilename, uint32_t numFramesPerPacket ); +#endif #ifdef RTP_S4_251135_CR26253_0016_REV1 ivas_error IVAS_RTP_READER_Init( IVAS_RTP *rtp, uint32_t srCodecFrameSizeMs, const char *inputBitstreamFilename, const char *piOutputFilename, bool isExtOutput, const char *outputWavFilename ); #else diff --git a/lib_util/ivas_rtp_payload.c b/lib_util/ivas_rtp_payload.c index 42f33e685d..98846adf84 100644 --- a/lib_util/ivas_rtp_payload.c +++ b/lib_util/ivas_rtp_payload.c @@ -32,7 +32,9 @@ #include #include #include +#ifndef FIXED_RTP_SEQUENCE_NUM #include +#endif #include "common_api_types.h" #ifdef IVAS_RTPDUMP @@ -182,6 +184,9 @@ static const uint32_t amrWBIOFrameSizeInBits[] = { /* Update the RTP Header structure */ ivas_error IVAS_RTP_PACK_UpdateHeader( IVAS_RTP_PACK_HANDLE hPack, /* i/o: pointer to an IVAS rtp packer handle to be opened */ +#ifdef FIXED_RTP_SEQUENCE_NUM + uint16_t seqNumInitVal, /* i : Initial sequence number to be used for 1st packet */ +#endif uint32_t ssrc, /* i : Unique 32-bit Source ID as negotiated during SDP */ uint8_t numCC, /* i : numCC indicates no. of contributing sources */ uint32_t *csrc, /* i : SSRCs of contributing Sources for a mixed stream */ @@ -197,6 +202,9 @@ ivas_error IVAS_RTP_PACK_UpdateHeader( return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "CC must be less than 16" ); } +#ifdef FIXED_RTP_SEQUENCE_NUM + header->seqNumber = seqNumInitVal; +#endif header->ssrc = ssrc; header->CC = numCC; if ( csrc != NULL ) @@ -229,11 +237,16 @@ static void InitRtpHeader( RTP_HEADER *header /* RTP header structure */ ) { +#ifdef FIXED_RTP_SEQUENCE_NUM + memset( header, 0, sizeof( *header ) ); + header->version = 2; +#else time_t t; memset( header, 0, sizeof( *header ) ); srand( (uint32_t) time( &t ) ); header->version = 2; header->seqNumber = rand() & 0xFFFF; +#endif } static ivas_error PackRtpHeader( -- GitLab From 4725761d9ea49d73d58704b51fe9fedc87721fe9 Mon Sep 17 00:00:00 2001 From: Ripinder Singh Date: Mon, 3 Nov 2025 13:02:36 +1100 Subject: [PATCH 332/340] Add reference generation for encoder Signed-off-by: Ripinder Singh --- scripts/ivas_conformance/runConformance.py | 216 ++++++++++++--------- 1 file changed, 124 insertions(+), 92 deletions(-) diff --git a/scripts/ivas_conformance/runConformance.py b/scripts/ivas_conformance/runConformance.py index 00252b52a8..15f9fbcd53 100644 --- a/scripts/ivas_conformance/runConformance.py +++ b/scripts/ivas_conformance/runConformance.py @@ -55,64 +55,67 @@ class MLDConformance: "ISAR": "ISAR_post_rend", } - def __init__(self, args) -> None: - self.RefBins = dict() - self.CutBins = dict() + def setupCommon(self): self.Commands = dict() - self.multiprocessing = not args.no_multi_processing - self.regenEncRefs = args.regenerate_enc_refs - self.dryrun = args.dryrun - self.verbose = args.verbose - self.executedTests = Value("i", 0) - self.failedTests = Value("i", 0) - self.testvecDir = args.testvecDir - self.ref_build_path = args.ref_build_path - self.cut_build_path = args.cut_build_path - self.filter = args.filter self.EncoderToDecoderCmdMap = dict() + for tag in MLDConformance.IVAS_Bins.keys(): + self.Commands[tag] = list() - self.scriptsDir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + # CREATE OUTPUT DIRECTORY STRUCTURE : CLEAN PREV OUTPUT self.outputDir = os.path.join(self.scriptsDir, "CUT_OUTPUTS") if os.path.exists(self.outputDir): shutil.rmtree(self.outputDir, ignore_errors=False) - os.mkdir(self.outputDir) - self.toolsdir = os.path.join(self.scriptsDir, "tools") - self.testvDir = os.path.join(self.testvecDir, "testv") - self.refDir = self.testvDir - self.mldbin = os.path.join(self.toolsdir, platform.system(), "mld") + os.makedirs(self.outputDir, exist_ok=True) + subdirs = ["enc", "dec", "renderer_short", "split_rendering"] + for odir in subdirs: + os.makedirs(os.path.join(self.outputDir, "ref", odir), exist_ok=True) + os.makedirs(os.path.join(self.outputDir, "dut", odir), exist_ok=True) self.logFile = os.path.join(self.outputDir, "runlog.txt") self.failedCmdsFile = os.path.join(self.outputDir, "failedCmds.txt") open(self.logFile, "w").close() open(self.failedCmdsFile, "w").close() + def setupDUT(self): + self.cut_build_path = args.cut_build_path + self.filter = args.filter + self.mldbin = os.path.join(self.toolsdir, platform.system(), "mld") + self.CutBins = dict() self.mldcsv = dict() self.sampleStats = dict() + for tag in MLDConformance.IVAS_Bins.keys(): + self.CutBins[tag] = os.path.join( + self.cut_build_path, MLDConformance.IVAS_Bins[tag] + ) self.mldcsv[tag] = os.path.join(self.outputDir, f"mld_{tag}.csv") self.sampleStats[tag] = os.path.join( self.outputDir, f"sampleStats_{tag}.csv" ) - self.setup() - - def createDirs(self): - os.makedirs(self.outputDir, exist_ok=True) - subdirs = ["enc", "dec", "renderer_short", "split_rendering"] - for odir in subdirs: - os.makedirs(os.path.join(self.outputDir, "ref", odir), exist_ok=True) - os.makedirs(os.path.join(self.outputDir, "dut", odir), exist_ok=True) - - def setup(self): - self.createDirs() + def setupRef(self): + self.RefBins = dict() + self.ref_build_path = self.args.ref_build_path for tag in MLDConformance.IVAS_Bins.keys(): self.RefBins[tag] = os.path.join( self.ref_build_path, MLDConformance.IVAS_Bins[tag] ) - self.CutBins[tag] = os.path.join( - self.cut_build_path, MLDConformance.IVAS_Bins[tag] - ) - self.Commands[tag] = list() + + def setup(self): + self.setupCommon() + self.setupRef() + if not self.args.regenerate_enc_refs: + self.setupDUT() + + def __init__(self, args) -> None: + self.args = args + self.scriptsDir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + self.testvecDir = args.testvecDir + self.toolsdir = os.path.join(self.scriptsDir, "tools") + self.testvDir = os.path.join(self.testvecDir, "testv") + self.executedTests = Value("i", 0) + self.failedTests = Value("i", 0) + self.setup() def accumulateCommands(self): for root, _, files in os.walk(self.testvecDir): @@ -164,7 +167,7 @@ class MLDConformance: self.EncoderToDecoderCmdMap[encoderPyTestTags[encTag]] = ( decoderPyTestTags[encTag] ) - if self.verbose: + if self.args.verbose: print( f"{encTag} {encoderPyTestTags[encTag]} -> {decoderPyTestTags[encTag]}" ) @@ -179,28 +182,63 @@ class MLDConformance: self.Commands["ENC"] ), "Failed to Map Encoder Commands to Decoder Commands" - def runOneEncoderTest(self, command: str): - - encPytestTag = self.getEncPytestTag(command) - - if self.regenEncRefs: - refCommand = self.reformatCommand(command=command, ref=True) - refEncOutput = self.getOutputFile(refCommand) + def genEncoderReferences(self, command: str, encCommandIdx: int): + # RUN ENCODER COMMAND LINE WITH REFERENCE ENCODER + refCommand = self.reformatCommand(command=command, ref=True) + refEncOutput = self.getOutputFile(refCommand) + if not os.path.exists(refEncOutput): self.process( command=self.setCommandExec(tag="ENC", command=refCommand, ref=True) ) + + # FIND CORRESPONDING DECODER COMMAND + decCommandIdx = self.EncoderToDecoderCmdMap[encCommandIdx] + refDecOutputFile = refEncOutput.replace(".192", "_REFDECODED.wav") + + command = self.reformatCommand( + command=self.Commands["DEC"][decCommandIdx], ref=True + ) + command = command.replace("-VOIP", "") + refDecCmd = ( + [self.RefBins["DEC"]] + + command.split()[1:-2] + + [refEncOutput, refDecOutputFile] + ) + self.process(command=" ".join(refDecCmd)) + self.executedTests.value += 1 + self.stats() + + def runReferenceGeneration(self): + processes = list() # Multiprocess list + commands = conformance.Commands["ENC"] + self.totalTests = len(commands) + if not self.args.no_multi_processing: + for commandIdx, command in enumerate(commands): + p = Process( + target=self.genEncoderReferences, args=(command, commandIdx) + ) + processes.append(p) + p.start() + for p in processes: + p.join() else: - refEncOutput = self.getOutputFile(command) - refEncOutput = refEncOutput.replace( - "$CUT_PATH/ref/param_file/enc/", - f"{self.testvecDir}/testv/ref/param_file/enc/", - ) - refEncOutput = refEncOutput.replace( - "$CUT_PATH/ref/sba_bs/pkt/", - f"{self.testvecDir}/testv/ref/sba_bs/pkt/", - ) + for commandIdx, command in enumerate(commands): + conformance.genEncoderReferences(command, commandIdx) + + def runOneEncoderTest(self, command: str): + encPytestTag = self.getEncPytestTag(command) + refEncOutput = self.getOutputFile(command) + refEncOutput = refEncOutput.replace( + "$CUT_PATH/ref/param_file/enc/", + f"{self.testvecDir}/testv/ref/param_file/enc/", + ) + refEncOutput = refEncOutput.replace( + "$CUT_PATH/ref/sba_bs/pkt/", + f"{self.testvecDir}/testv/ref/sba_bs/pkt/", + ) + refDecOutputFile = refEncOutput.replace(".192", "_REFDECODED.wav") - # Run reference Encoder + # Run CUT Encoder encCommandIdx = self.Commands["ENC"].index(command) command = self.reformatCommand(command=command, ref=False) command = self.setCommandExec(tag="ENC", command=command, ref=False) @@ -210,7 +248,6 @@ class MLDConformance: # Decode the encoded output with Reference decoder dutDecOutputFile = dutEncOutput.replace(".192", "_CUT_REFDECODED.wav") - refDecOutputFile = dutEncOutput.replace(".192", "_REF_REFDECODED.wav") decCommandIdx = self.EncoderToDecoderCmdMap[encCommandIdx] command = self.reformatCommand( command=self.Commands["DEC"][decCommandIdx], ref=False @@ -221,13 +258,7 @@ class MLDConformance: + command.split()[1:-2] + [dutEncOutput, dutDecOutputFile] ) - refDecCmd = ( - [self.RefBins["DEC"]] - + command.split()[1:-2] - + [refEncOutput, refDecOutputFile] - ) self.process(command=" ".join(dutDecCmd)) - self.process(command=" ".join(refDecCmd)) self.mld( "ENC", encPytestTag, refFile=refDecOutputFile, dutFile=dutDecOutputFile ) @@ -237,7 +268,7 @@ class MLDConformance: refInputFile = command.split()[-2].replace( "$REF_PATH/ref", f"{self.testvDir}/ref" ) - #refInputFile = refInputFile.replace("_cut.192.fer", ".192") + # refInputFile = refInputFile.replace("_cut.192.fer", ".192") # refInputFile = refInputFile.replace(".fer.192", ".192").replace(".192.fer", ".192").replace("_cut.192.fer", ".192").replace("_cut.192", ".192") refDecOutput = self.getOutputFile(command).replace( "$CUT_PATH/ref", f"{self.testvDir}/ref" @@ -251,10 +282,12 @@ class MLDConformance: + [refInputFile, dutDecOutputFile] ) self.process(command=" ".join(dutDecCmd)) - + ##### skip MLD verification for files with only 1 frame as MLD does not run with such files. Possible solution: append 0s and then compare ##### if refInputFile.find("_cut.192.fer") == -1: - self.mld("DEC", dutPytestTag, refFile=refDecOutput, dutFile=dutDecOutputFile) + self.mld( + "DEC", dutPytestTag, refFile=refDecOutput, dutFile=dutDecOutputFile + ) def getRendOutputFile(self, command: str): cmds = command.split() @@ -285,7 +318,7 @@ class MLDConformance: return " ".join([exec, *commands[1:]]) def reformatCommand(self, command: str, ref: bool) -> str: - command = command.replace("$TESTV_PATH", self.testvecDir) + command = command.replace("$TESTV_PATH", self.scriptsDir) command = command.replace( "$REF_PATH/split_rendering", f"{self.testvecDir}/testv/split_rendering" ) @@ -296,38 +329,36 @@ class MLDConformance: command = command.replace(".fer.192", ".192") command = command.replace(".192.fer", ".192") ################################################## - command = command.replace( - "$REF_PATH/ref/param_file/enc/", f"{self.outputDir}/ref/enc/" + "$REF_PATH/ref/param_file/", f"{self.testvDir}/ref/param_file/" ) command = command.replace( - "$REF_PATH/ref/param_file/dec/", f"{self.outputDir}/ref/dec/" + "$REF_PATH/ref/sba_bs/pkt/", f"{self.testvDir}/ref/sba_bs/pkt/" ) - command = command.replace( - "$REF_PATH/ref/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" - ) - if ref: command = command.replace( - "$CUT_PATH/ref/param_file/enc/", f"{self.outputDir}/ref/enc/" + "$CUT_PATH/dut/sba_bs/pkt/", f"{self.testvDir}/ref/sba_bs/pkt/" ) command = command.replace( - "$CUT_PATH/ref/param_file/dec/", f"{self.outputDir}/ref/dec/" + "$CUT_PATH/dut/param_file/", f"{self.testvDir}/ref/param_file/" ) command = command.replace( - "$CUT_PATH/renderer_short/ref/", f"{self.outputDir}/ref/renderer_short/" + "$CUT_PATH/ref/param_file/", f"{self.testvDir}/ref/param_file/" ) command = command.replace( - "$CUT_PATH/split_rendering/cut/", - f"{self.outputDir}/ref/split_rendering/", + "$CUT_PATH/renderer_short/ref/", f"{self.testvDir}/ref/renderer_short/" ) command = command.replace( - "$CUT_PATH/ref/sba_bs/pkt/", f"{self.outputDir}/ref/enc/" + "$CUT_PATH/split_rendering/cut/", + f"{self.testvDir}/ref/split_rendering/", ) command = command.replace( - "$CUT_PATH/ref/sba_bs/raw/", f"{self.outputDir}/ref/dec/" + "$CUT_PATH/ref/sba_bs/", f"{self.testvDir}/ref/sba_bs/" ) else: + #command = command.replace( + # "$CUT_PATH/dut/sba_bs/pkt/", f"{self.outputDir}/dut/enc/" + #) command = command.replace( "$CUT_PATH/ref/param_file/enc/", f"{self.outputDir}/dut/enc/" ) @@ -350,7 +381,7 @@ class MLDConformance: return command - def runOneCommand(self, tag: str, command: str, ref: bool): + def runOneCommand(self, tag: str, command: str): if tag == "ENC": self.runOneEncoderTest(command) elif tag == "DEC": @@ -362,7 +393,7 @@ class MLDConformance: self.executedTests.value += 1 self.stats() - def runTag(self, tag: str, ref: bool = False): + def runTag(self, tag: str): self.executedTests.value = 0 self.failedTests.value = 0 # reset MLD, Sample Stats @@ -383,11 +414,11 @@ class MLDConformance: print( f"Executing tests for {tag} {'Filter='+self.filter if self.filter else ''} ({self.totalTests} tests)" ) - if self.multiprocessing: + if not self.args.no_multi_processing: for command in commands: p = Process( target=self.runOneCommand, - args=(tag, command, ref), + args=(tag, command), ) processes.append(p) p.start() @@ -395,16 +426,16 @@ class MLDConformance: p.join() else: for command in commands: - self.runOneCommand(tag, command, ref) + self.runOneCommand(tag, command) def process(self, command) -> int: - if self.verbose: + if self.args.verbose: print(command) with open(self.logFile, "a") as fd: fd.write(command + "\n") with open(self.logFile, "a") as fd: - if not self.dryrun: + if not self.args.dryrun: c = subprocess.run( command, stdout=fd, stderr=subprocess.STDOUT, text=True, shell=True ) @@ -526,20 +557,18 @@ if __name__ == "__main__": parser.add_argument( "--ref_build_path", type=str, - required=True, help="Path to the reference build folder containing IVAS Encoder, Decoder, Renderer and Post Render binaries", ) parser.add_argument( "--cut_build_path", type=str, - required=True, help="Path to the CUT build folder containing IVAS Encoder, Decoder, Renderer and Post Render binaries", ) parser.add_argument( - "--regenerate_enc_refs", + "--regenerate-enc-refs", default=False, action="store_true", - help="Enable verbose printing", + help="Regenerate the encoder reference bitstreams and decoded outputs", ) parser.add_argument( "--verbose", @@ -567,7 +596,7 @@ if __name__ == "__main__": help='Choose tests to run ["ENC", "DEC", "REND", "ISAR", "ALL"]', ) parser.add_argument( - "--no_multi_processing", + "--no-multi-processing", default=False, action="store_true", help="Disable multi-processing for sequential test run (debugging)", @@ -584,8 +613,11 @@ if __name__ == "__main__": conformance = MLDConformance(args) conformance.accumulateCommands() - # import sys - # sys.exit(0) + + if args.regenerate_enc_refs: + conformance.runReferenceGeneration() + sys.exit(0) + testTags = ( MLDConformance.IVAS_Bins.keys() if args.test_mode == "ALL" else [args.test_mode] ) @@ -594,5 +626,5 @@ if __name__ == "__main__": # Not implemented yet continue if not args.analyse_only: - conformance.runTag(tag, ref=True) + conformance.runTag(tag) conformance.doAnalysis(selectTag=tag) -- GitLab From 81139896a192ae92ddcd878a4d1d65dd64ee95a3 Mon Sep 17 00:00:00 2001 From: rtyag Date: Tue, 4 Nov 2025 16:20:39 +1100 Subject: [PATCH 333/340] update the usage steps in readme.md --- scripts/ivas_conformance/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/ivas_conformance/README.md b/scripts/ivas_conformance/README.md index 43952b6598..e5fb0824ff 100644 --- a/scripts/ivas_conformance/README.md +++ b/scripts/ivas_conformance/README.md @@ -1,3 +1,22 @@ # IVAS conformance scripts This folder contains scripts for running IVAS conformance tests. This is a placeholder file for instructions. + +no-BE conformance USAGE + +Following CMDs needs to be executed from ivas-codec root folder: + +################generate testvec package and encoder refs (temporary step to obtain testvec package, this step will be removed in final delivery) ############ +sh ivas_be_conf_test_gen.sh +PYTHONPATH=scripts python scripts/ivas_conformance/runConformance.py --testvecDir $PWD/testvec --ref_build_path=testvec/ref_bin --cut_build_path=testvec/cut_bin --test-mode=ENC --regenerate-enc-refs + + + +Encoder conformance: + - PYTHONPATH=scripts python scripts/ivas_conformance/runConformance.py --testvecDir $PWD/testvec --ref_build_path=testvec/ref_bin --cut_build_path=testvec/cut_bin --test-mode=ENC +Decoder conformance: + - PYTHONPATH=scripts python scripts/ivas_conformance/runConformance.py --testvecDir $PWD/testvec --ref_build_path=testvec/ref_bin --cut_build_path=testvec/cut_bin --test-mode=DEC +Renderer conformance: + - PYTHONPATH=scripts python scripts/ivas_conformance/runConformance.py --testvecDir $PWD/testvec --ref_build_path=testvec/ref_bin --cut_build_path=testvec/cut_bin --test-mode=REND +Split renderer confomance: + - -- GitLab From 37f3ed2400f03c6b257071d10a4cc515ebebaa83 Mon Sep 17 00:00:00 2001 From: norvell Date: Tue, 4 Nov 2025 09:30:52 +0100 Subject: [PATCH 334/340] Revert "Merge branch '1418-ci-ivas-conformance-pipeline-failing-with-the-added-rtpdump-tests' into 'main'" This reverts merge request !2310 --- scripts/parse_commands.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/parse_commands.py b/scripts/parse_commands.py index ea32898c91..38b0d92cdf 100644 --- a/scripts/parse_commands.py +++ b/scripts/parse_commands.py @@ -91,9 +91,6 @@ if __name__ == '__main__': # Filter out networkSimulator_g192 commands from cmds_rend cmds_rend = [cmd for cmd in cmds_rend if not "networkSimulator_g192" in cmd] - # Filter out rtpdump tests from cmds_enc. The rtpdump bitstreams differ between each run. - cmds_enc = [cmd for cmd in cmds_enc if not "rtpdump" in cmd] - with open(txt_file.replace('.','_enc.'),'w', newline='\n') as outfile: with open('scripts/enc_header.txt','r') as header: outfile.write(header.read()) -- GitLab From 4f786e8b33831069cfd14960bc6e441779e9cb2b Mon Sep 17 00:00:00 2001 From: Erik Norvell Date: Tue, 4 Nov 2025 14:37:08 +0100 Subject: [PATCH 335/340] Unify networkSimulator_g192 output suffix to netsimout --- tests/split_rendering/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/split_rendering/utils.py b/tests/split_rendering/utils.py index f2125bcc90..ca7a5143c5 100644 --- a/tests/split_rendering/utils.py +++ b/tests/split_rendering/utils.py @@ -260,7 +260,7 @@ def run_full_chain_split_rendering( run_ivas_isar_enc_cmd(enc_cmd) if delay_profile: - rtp_bitstream = ivas_bitstream.with_suffix(".rtpg192") + rtp_bitstream = ivas_bitstream.with_suffix(".netsimout") netsim_cmd = [ str(TOOLS_DIR / f"networkSimulator_g192{EXE_SUFFIX}"), str(delay_profile), -- GitLab From 477be898d86eefee0ae2d2713404864735d65d96 Mon Sep 17 00:00:00 2001 From: Vladimir Malenovsky Date: Tue, 4 Nov 2025 15:56:18 +0100 Subject: [PATCH 336/340] use new noisy SBA input files in DTX cases --- scripts/config/self_test.prm | 68 ++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/scripts/config/self_test.prm b/scripts/config/self_test.prm index 496a23e70f..2f6f2efe4a 100644 --- a/scripts/config/self_test.prm +++ b/scripts/config/self_test.prm @@ -626,17 +626,17 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec -t testv/headrot.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv -otr avg BINAURAL 32 bit testv/stv3OA32c.pcm_SBA_24400_32-32_Binaural_Headrot_EXOF_OtrAvg.tst // SBA at 24.4 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, random FER at 5% -../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32c_cut_.004.wav bit +../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32n.wav bit eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g192 bit_error -../IVAS_dec BINAURAL 32 bit_error testv/stv3OA32c_cut.004.wav_SBA_24400_32-32_DTX_Binaural_FER5.tst +../IVAS_dec BINAURAL 32 bit_error testv/stv3OA32n.wav_SBA_24400_32-32_DTX_Binaural_FER5.tst // SBA at 24.4 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, DTX on, HR -../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32c_cut_.004.wav bit -../IVAS_dec -t testv/headrot.csv BINAURAL 32 bit testv/stv3OA32c_cut_.004.wav_SBA_24400_32-32_DTX_Binaural_Headrot.tst +../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32n.wav bit +../IVAS_dec -t testv/headrot.csv BINAURAL 32 bit testv/stv3OA32n.wav_SBA_24400_32-32_DTX_Binaural_Headrot.tst // SBA at 24.4 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, DTX on, HR, exo -../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32c_cut_.004.wav bit -../IVAS_dec -t testv/headrot.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv BINAURAL 32 bit testv/stv3OA32c_cut_.004.wav_SBA_24400_32-32_DTX_Binaural_Headrot_EXOF.tst +../IVAS_cod -sba 3 -dtx 24400 32 testv/stv3OA32n.wav bit +../IVAS_dec -t testv/headrot.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv BINAURAL 32 bit testv/stv3OA32n.wav_SBA_24400_32-32_DTX_Binaural_Headrot_EXOF.tst // SBA at 32 kbps, 32kHz in, 32kHz out, FOA out ../IVAS_cod -sba 1 32000 32 testv/stvFOA32c.wav bit @@ -652,8 +652,8 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec BINAURAL_ROOM_IR 32 bit testv/stvFOA32c.wav_SBA_32000_32-32_BINAURAL_ROOM.tst // SBA at 32 kbps, 48kHz in, 48kHz out, MONO out, DTX, bandwidth switching -../IVAS_cod -max_band testv/ivas_bws_20fr_start_FB.txt -dtx -sba 1 32000 48 testv/stvFOA48c_cut_.004.wav bit -../IVAS_dec MONO 48 bit testv/stvFOA48c_cut_.004.wav_SBA_32000_48-48_DTX_MONO.tst +../IVAS_cod -max_band testv/ivas_bws_20fr_start_FB.txt -dtx -sba 1 32000 48 testv/stvFOA48n.wav bit +../IVAS_dec MONO 48 bit testv/stvFOA48n.wav_SBA_32000_48-48_DTX_MONO.tst // SBA at 48 kbps, 32kHz in, 32kHz out, MONO out, random FER at 5% ../IVAS_cod -sba 3 48000 32 testv/stv3OA32c.wav bit @@ -701,26 +701,26 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec -t ../scripts/trajectories/full-circle-with-up-and-down-4s.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv -rvf ../scripts/trajectories/full-circle-with-up-and-down-4s-Vector3.csv -otr ref_vec_lev BINAURAL_ROOM_IR 32 bit testv/stv3OA32c.pcm_SBA_48000_32-32_BinauralRoom_Headrot_EXOF_OtrRefPosLev.tst // SBA at 48 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, random FER at 5% -../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32c_cut_.004.wav bit +../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32n.wav bit eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g192 bit_error -../IVAS_dec BINAURAL 32 bit_error testv/stv3OA32c_cut_.004.wav_SBA_48000_32-32_DTX_Binaural_FER5.tst +../IVAS_dec BINAURAL 32 bit_error testv/stv3OA32n.wav_SBA_48000_32-32_DTX_Binaural_FER5.tst // SBA at 48 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, DTX on, HR -../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32c_cut_.004.wav bit -../IVAS_dec -t testv/headrot.csv BINAURAL 32 bit testv/stv3OA32c_cut_.004.wav_SBA_48000_32-32_DTX_Binaural_Headrot.tst +../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32n.wav bit +../IVAS_dec -t testv/headrot.csv BINAURAL 32 bit testv/stv3OA32n.wav_SBA_48000_32-32_DTX_Binaural_Headrot.tst // SBA at 48 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out, DTX on, HR, exo -../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32c_cut_.004.wav bit -../IVAS_dec -t testv/headrot.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv BINAURAL 32 bit testv/stv3OA32c_cut_.004.wav_SBA_48000_32-32_DTX_Binaural_Headrot_EXOF.tst +../IVAS_cod -sba 3 -dtx 48000 32 testv/stv3OA32n.wav bit +../IVAS_dec -t testv/headrot.csv -exof testv/headrot_case00_3000_q_combinedRotationTest.csv BINAURAL 32 bit testv/stv3OA32n.wav_SBA_48000_32-32_DTX_Binaural_Headrot_EXOF.tst // SBA at 48 kbps, 48kHz in, 48kHz out, 5_1_2 out ../IVAS_cod -sba 3 48000 48 testv/stv3OA48c.wav bit ../IVAS_dec 5_1_2 48 bit testv/stv3OA48c.wav_SBA_48000_48-48_5_1_2.tst // SBA at 64 kbps, 32kHz in, 32kHz out, FOA out, DTX, random FER at 5% -../IVAS_cod -dtx -sba 1 64000 32 testv/stvFOA32c_cut_.004.wav bit +../IVAS_cod -dtx -sba 1 64000 32 testv/stvFOA32n.wav bit eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g192 bit_error -../IVAS_dec FOA 32 bit_error testv/stvFOA32c.wav_SBA_64000_32-32_DTX_FOA_FER5.tst +../IVAS_dec FOA 32 bit_error testv/stvFOA32n.wav_SBA_64000_32-32_DTX_FOA_FER5.tst // SBA at 64 kbps, 48kHz in, 48kHz out, 5_1_4 out ../IVAS_cod -sba 1 64000 48 testv/stvFOA48c.wav bit @@ -731,12 +731,12 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec 7_1_4 48 bit testv/stvFOA48c.wav_SBA_64000_48-48_7_1_4.tst // SBA at 64 kpbs, 48kHz in, 48kHz out, BINAURAL out, DTX -../IVAS_cod -dtx -sba 1 64000 48 testv/stvFOA48c_cut_.004.wav bit -../IVAS_dec BINAURAL 48 bit testv/stvFOA48c_cut_.004.wav_SBA_64000_48-48_DTX_BINAURAL.tst +../IVAS_cod -dtx -sba 1 64000 48 testv/stvFOA48n.wav bit +../IVAS_dec BINAURAL 48 bit testv/stvFOA48n.wav_SBA_64000_48-48_DTX_BINAURAL.tst // SBA at 64 kpbs, 48kHz in, 48kHz out, BINAURAL_ROOM out, DTX -../IVAS_cod -dtx -sba 1 64000 48 testv/stvFOA48c_cut_.004.wav bit -../IVAS_dec BINAURAL_ROOM_IR 48 bit testv/stvFOA48c_cut_.004.wav_SBA_64000_48-48_DTX_BINAURAL_ROOM.tst +../IVAS_cod -dtx -sba 1 64000 48 testv/stvFOA48n.wav bit +../IVAS_dec BINAURAL_ROOM_IR 48 bit testv/stvFOA48n.wav_SBA_64000_48-48_DTX_BINAURAL_ROOM.tst // SBA at 80 kbps, 32kHz in, 32kHz out, HOA3 out ../IVAS_cod -sba 3 80000 32 testv/stv3OA32c.wav bit @@ -789,8 +789,8 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec HOA2 48 bit_error testv/stv3OA48c.wav_SBA_192000_48-48_HOA2_FER5.tst // SBA at 48 kbps, 48kHz in, 48kHz out, DTX on, 5_1 out -../IVAS_cod -sba 3 -dtx 48000 48 testv/stv3OA48c_cut_.004.wav bit -../IVAS_dec 5_1 48 bit testv/stv3OA48c_cut_.004.wav_SBA_48000_48-48_DTX_5_1.tst +../IVAS_cod -sba 3 -dtx 48000 48 testv/stv3OA48n.wav bit +../IVAS_dec 5_1 48 bit testv/stv3OA48n.wav_SBA_48000_48-48_DTX_5_1.tst // SBA at 160 kbps, 32kHz in, 32kHz out, FOA out ../IVAS_cod -sba 1 160000 32 testv/stvFOA32c.wav bit @@ -871,12 +871,12 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec 7_1_4 48 bit testv/stv3OA48c.wav_sw_48-48_7_1_4.tst // SBA FOA bitrate switching from 13.2 kbps to 192 kbps, 32kHz in, 32kHz out, DTX on, BINAURAL out -../IVAS_cod -dtx -sba 1 ../scripts/switchPaths/sw_13k2_192k_50fr.bin 32 testv/stvFOA32c_cut_.004.wav bit +../IVAS_cod -dtx -sba 1 ../scripts/switchPaths/sw_13k2_192k_50fr.bin 32 testv/stvFOA32n.wav bit ../IVAS_dec BINAURAL 32 bit testv/stvFOA32c.wav_sw_32-32_DTX_BINAURAL.tst // SBA 3OA bitrate switching from 13.2 kbps to 128 kbps, 32kHz in, 32kHz out, DTX on, HOA3 out -../IVAS_cod -dtx -sba 3 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 32 testv/stv3OA32c_cut_.004.wav bit -../IVAS_dec HOA3 32 bit testv/stv3OA32c.wav_sw_32-32_DTX_HOA3.tst +../IVAS_cod -dtx -sba 3 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 32 testv/stv3OA32n.wav bit +../IVAS_dec HOA3 32 bit testv/stv3OA32n.wav_sw_32-32_DTX_HOA3.tst // SBA FOA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, FOA out ../IVAS_cod -sba 1 -max_band fb ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stvFOA48c.wav bit @@ -923,30 +923,30 @@ eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g1 ../IVAS_dec BINAURAL 48 bit testv/stvFOA48c.wav_SBA_PCA_256000_48-48_BINAURAL.tst // SBA FOA bitrate switching from 13.2 kbps to 192 kbps, 32kHz in, 32kHz out, DTX on, EXT out -../IVAS_cod -dtx -sba 1 ../scripts/switchPaths/sw_13k2_192k_50fr.bin 32 testv/stvFOA32c_cut_.004.wav bit -../IVAS_dec EXT 32 bit testv/stvFOA32c_cut_.004.wav_sw_32-32_DTX_EXT.tst +../IVAS_cod -dtx -sba 1 ../scripts/switchPaths/sw_13k2_192k_50fr.bin 32 testv/stvFOA32n.wav bit +../IVAS_dec EXT 32 bit testv/stvFOA32n.wav_sw_32-32_DTX_EXT.tst // SBA planar 2OA bitrate switching from 13.2 kbps to 128 kbps, 32kHz in, 32kHz out, DTX on, EXT out -../IVAS_cod -dtx -sba -2 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 32 testv/stv2OA32c_cut_.004.wav bit -../IVAS_dec EXT 32 bit testv/stv2OA32c_cut_.004.wav_sw_32-32_DTX_EXT.tst +../IVAS_cod -dtx -sba -2 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 32 testv/stv2OA32n.wav bit +../IVAS_dec EXT 32 bit testv/stv2OA32n.wav_sw_32-32_DTX_EXT.tst // SBA 3OA bitrate switching from 13.2 kbps to 128 kbps, 48kHz in, 48kHz out, DTX on, random FER at 5%, EXT out -../IVAS_cod -dtx -sba 3 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stv3OA48c_cut_.004.wav bit +../IVAS_cod -dtx -sba 3 ../scripts/switchPaths/sw_13k2_to_128k_10fr.bin 48 testv/stv3OA48n.wav bit eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g192 bit_error -../IVAS_dec EXT 48 bit_error testv/stv3OA48c_cut_.004.wav_sw_48-48_DTX_EXT_FER5.tst +../IVAS_dec EXT 48 bit_error testv/stv3OA48n.wav_sw_48-48_DTX_EXT_FER5.tst // SBA planar FOA bitrate switching from 13.2 kbps to 512 kbps, 32kHz in, 32kHz out, EXT out ../IVAS_cod -sba -1 ../scripts/switchPaths/sw_13k2_512k.bin 32 testv/stvFOA32c.wav bit -../IVAS_dec EXT 32 bit testv/stvFOA32c.wav_sw_32-32_DTX_EXT.tst +../IVAS_dec EXT 32 bit testv/stvFOA32c.wav_sw_32-32_EXT.tst // SBA 2OA bitrate switching from 13.2 kbps to 512 kbps, 32kHz in, 32kHz out, EXT out ../IVAS_cod -sba 2 ../scripts/switchPaths/sw_13k2_512k.bin 32 testv/stv2OA32c.wav bit -../IVAS_dec EXT 32 bit testv/stv2OA32c.wav_sw_32-32_DTX_EXT.tst +../IVAS_dec EXT 32 bit testv/stv2OA32c.wav_sw_32-32_EXT.tst // SBA planar 3OA bitrate switching from 13.2 kbps to 512 kbps, 48kHz in, 48kHz out, random FER at 5%, EXT out ../IVAS_cod -sba -3 ../scripts/switchPaths/sw_13k2_512k.bin 48 testv/stv3OA48c.wav bit eid-xor -fer -vbr -bs g192 -ep g192 bit ../scripts/dly_error_profiles/ep_5pct.g192 bit_error -../IVAS_dec EXT 48 bit_error testv/stv3OA48c.wav_sw_48-48_DTX_EXT_FER5.tst +../IVAS_dec EXT 48 bit_error testv/stv3OA48c.wav_sw_48-48_EXT_FER5.tst // SBA 3OA at 96 kbps, 48kHz in, 48kHz out, BINAURAL_ROOM_REVERB out default configuration ../IVAS_cod -sba 3 512000 48 testv/stv3OA48c.wav bit -- GitLab From b79a64be3e327129b8042c8255a047e97724a0fb Mon Sep 17 00:00:00 2001 From: Vladimir Malenovsky Date: Tue, 4 Nov 2025 15:57:18 +0100 Subject: [PATCH 337/340] new noisy SBA input files --- scripts/testv/stv2OA32n.wav | 3 +++ scripts/testv/stv2OA48n.wav | 3 +++ scripts/testv/stv3OA32n.wav | 3 +++ scripts/testv/stv3OA48n.wav | 3 +++ scripts/testv/stvFOA32n.wav | 3 +++ scripts/testv/stvFOA48n.wav | 3 +++ 6 files changed, 18 insertions(+) create mode 100644 scripts/testv/stv2OA32n.wav create mode 100644 scripts/testv/stv2OA48n.wav create mode 100644 scripts/testv/stv3OA32n.wav create mode 100644 scripts/testv/stv3OA48n.wav create mode 100644 scripts/testv/stvFOA32n.wav create mode 100644 scripts/testv/stvFOA48n.wav diff --git a/scripts/testv/stv2OA32n.wav b/scripts/testv/stv2OA32n.wav new file mode 100644 index 0000000000..393307f6f9 --- /dev/null +++ b/scripts/testv/stv2OA32n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00f992140544aaec42a474dfee59e302d237087601402209d6206ac28230a3ac +size 3456080 diff --git a/scripts/testv/stv2OA48n.wav b/scripts/testv/stv2OA48n.wav new file mode 100644 index 0000000000..40c9723d69 --- /dev/null +++ b/scripts/testv/stv2OA48n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:752df20b150420cea0a3add9a2ff78cd1bb5a02ff8975f1f0f1f909bd8542e86 +size 5184080 diff --git a/scripts/testv/stv3OA32n.wav b/scripts/testv/stv3OA32n.wav new file mode 100644 index 0000000000..9cfd62d787 --- /dev/null +++ b/scripts/testv/stv3OA32n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:780182483a647741b37be8056e2cf05088f0518ab8150ec7e61a6a454b142452 +size 6144080 diff --git a/scripts/testv/stv3OA48n.wav b/scripts/testv/stv3OA48n.wav new file mode 100644 index 0000000000..31ad785ad2 --- /dev/null +++ b/scripts/testv/stv3OA48n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbf252c2508f0ff4916019f5864b9d50809b1dc5d280eb06c12c1b9c5b8ad7f7 +size 9216044 diff --git a/scripts/testv/stvFOA32n.wav b/scripts/testv/stvFOA32n.wav new file mode 100644 index 0000000000..fb09a47350 --- /dev/null +++ b/scripts/testv/stvFOA32n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ceff91346cd6fd74413ab471226dd13c0b957905ab8cb59c07217d89dd7b25aa +size 1536080 diff --git a/scripts/testv/stvFOA48n.wav b/scripts/testv/stvFOA48n.wav new file mode 100644 index 0000000000..8ad69c1e8d --- /dev/null +++ b/scripts/testv/stvFOA48n.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ba415ae230e162a937822d1150845bf13521b5cf5036e12581ef067651d3b7 +size 2304080 -- GitLab From 2d4100d0c7bcba5e7c77402513fa5f6c650d6527 Mon Sep 17 00:00:00 2001 From: Vladimir Malenovsky Date: Tue, 4 Nov 2025 16:02:24 +0100 Subject: [PATCH 338/340] set +x rights correctly with git --- scripts/testv/stv2OA32n.wav | 0 scripts/testv/stv2OA48n.wav | 0 scripts/testv/stv3OA32n.wav | 0 scripts/testv/stv3OA48n.wav | 0 scripts/testv/stvFOA32n.wav | 0 scripts/testv/stvFOA48n.wav | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/testv/stv2OA32n.wav mode change 100644 => 100755 scripts/testv/stv2OA48n.wav mode change 100644 => 100755 scripts/testv/stv3OA32n.wav mode change 100644 => 100755 scripts/testv/stv3OA48n.wav mode change 100644 => 100755 scripts/testv/stvFOA32n.wav mode change 100644 => 100755 scripts/testv/stvFOA48n.wav diff --git a/scripts/testv/stv2OA32n.wav b/scripts/testv/stv2OA32n.wav old mode 100644 new mode 100755 diff --git a/scripts/testv/stv2OA48n.wav b/scripts/testv/stv2OA48n.wav old mode 100644 new mode 100755 diff --git a/scripts/testv/stv3OA32n.wav b/scripts/testv/stv3OA32n.wav old mode 100644 new mode 100755 diff --git a/scripts/testv/stv3OA48n.wav b/scripts/testv/stv3OA48n.wav old mode 100644 new mode 100755 diff --git a/scripts/testv/stvFOA32n.wav b/scripts/testv/stvFOA32n.wav old mode 100644 new mode 100755 diff --git a/scripts/testv/stvFOA48n.wav b/scripts/testv/stvFOA48n.wav old mode 100644 new mode 100755 -- GitLab From f3720acfb349b18c3f5633bdcc1d81b80b8d1be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20M=C3=BCller?= Date: Wed, 5 Nov 2025 12:57:08 +0100 Subject: [PATCH 339/340] Fix make concurrency make's behavior is different than, e.g., ninja in that the lack of a parameter to -j means that it may spawn indefinite amounts of processes. This can easily exhaust a build system's resources and thus cause problems not only in CI. --- ivas_be_conf_test_gen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivas_be_conf_test_gen.sh b/ivas_be_conf_test_gen.sh index 2536de1f89..fa6aa7bd89 100644 --- a/ivas_be_conf_test_gen.sh +++ b/ivas_be_conf_test_gen.sh @@ -1,5 +1,5 @@ make -f Makefile clean -make -f Makefile -j CLANG=0 +make -f Makefile -j"$(nproc)" CLANG=0 cp IVAS_cod IVAS_cod_ref cp IVAS_dec IVAS_dec_ref cp IVAS_rend IVAS_rend_ref -- GitLab From c0e0f180a64965b9e3b35421ebe788fc93356519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20M=C3=BCller?= Date: Wed, 5 Nov 2025 13:01:01 +0100 Subject: [PATCH 340/340] Make sure script exits upon errors --- ivas_be_conf_test_gen.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ivas_be_conf_test_gen.sh b/ivas_be_conf_test_gen.sh index fa6aa7bd89..db23621c4a 100644 --- a/ivas_be_conf_test_gen.sh +++ b/ivas_be_conf_test_gen.sh @@ -1,3 +1,7 @@ +#! /bin/sh + +set -e + make -f Makefile clean make -f Makefile -j"$(nproc)" CLANG=0 cp IVAS_cod IVAS_cod_ref -- GitLab