diff --git a/Workspace_msvc/lib_util.vcxproj b/Workspace_msvc/lib_util.vcxproj index 94561b175a79fa076065a403ec06d70f7b87c2a2..94f22c71ca67340650072a678a92f9d5aba745ac 100644 --- a/Workspace_msvc/lib_util.vcxproj +++ b/Workspace_msvc/lib_util.vcxproj @@ -111,6 +111,11 @@ + + + + + diff --git a/Workspace_msvc/lib_util.vcxproj.filters b/Workspace_msvc/lib_util.vcxproj.filters index afaedfb5dab4395988686f77340487df02b6c4d8..f81b34b6da7828d992fdcdeffa814a9e1ae23f3f 100644 --- a/Workspace_msvc/lib_util.vcxproj.filters +++ b/Workspace_msvc/lib_util.vcxproj.filters @@ -85,6 +85,21 @@ util_c + + util_c + + + util_c + + + util_c + + + util_c + + + util_c + diff --git a/apps/decoder.c b/apps/decoder.c index 213b77985aefed88d05e025f67e19655ad37ec2c..8cd444ec9a73bafeb5dafe1bc32533d728a3c0df 100644 --- a/apps/decoder.c +++ b/apps/decoder.c @@ -39,7 +39,9 @@ #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" +#include "ivas_rtp_file.h" #endif #include "jbm_file_writer.h" #include "hrtf_file_reader.h" @@ -129,6 +131,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 @@ -166,6 +172,383 @@ 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", + "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 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_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 ( 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; + + 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 @@ -174,7 +557,6 @@ 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 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 ); @@ -183,6 +565,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, 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 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() * @@ -755,7 +1248,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 { @@ -982,6 +1479,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; @@ -1068,6 +1569,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 ) { @@ -1657,6 +2177,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" ); @@ -2881,36 +3403,37 @@ 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; + 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; -#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: 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 ) @@ -2919,16 +3442,13 @@ 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 ); goto cleanup; } +#endif break; case IVAS_DEC_INPUT_FORMAT_G192: auPtr = au; @@ -2944,17 +3464,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, 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 @@ -2962,16 +3478,16 @@ 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; } -#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 +3502,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 @@ -3023,7 +3539,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 */ @@ -3055,25 +3575,21 @@ static ivas_error decodeVoIP( int16_t delayNumSamples = -1; 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; + IVAS_RTP ivasRtp = { 0 }; + IVAS_DEC_HANDLE hIvasDec = *phIvasDec; + int32_t initialTsOffsetSystemAndRTP = 0; #else + FILE *f_rtpstream = NULL; 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,31 +3626,27 @@ static ivas_error decodeVoIP( delayNumSamples_orig[0] = -1; +#ifndef IVAS_RTPDUMP rtpdumpDepacker.rtpdump = NULL; +#endif switch ( arg.inputFormat ) { 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 - 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 ) { @@ -3178,17 +3690,14 @@ 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 = 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 @@ -3197,7 +3706,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 @@ -3229,6 +3738,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 ) { @@ -3352,11 +3882,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 +3895,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 = 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; - - /* 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 +3915,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 +3932,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 */ @@ -3441,6 +3946,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 = 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; + } + } +#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 @@ -3770,7 +4287,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 fe444c4d11d28f6885ce166cde25117140313662..9fb3b600137bcce6231680c013bf09c59ffb7b64 100644 --- a/apps/encoder.c +++ b/apps/encoder.c @@ -39,16 +39,16 @@ #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" +#include "ivas_rtp_file.h" #endif #ifdef DEBUGGING #include "debug.h" #endif #include "wmc_auto.h" - #define WMC_TOOL_SKIP /*------------------------------------------------------------------------------------------* @@ -153,6 +153,7 @@ typedef struct bool ism_extended_metadata; #ifdef IVAS_RTPDUMP bool rtpdumpOutput; + uint32_t numFramesPerPacket; char *sceneOrientationTrajFileName; char *deviceOrientationTrajFileName; #endif @@ -221,9 +222,19 @@ int main( #endif #ifdef IVAS_RTPDUMP - FILE *f_rtpstream = NULL; - IVAS_RTPDUMP_PACKER *rtpdumpPacker = NULL; - IVAS_RTPDUMP_PACKER_ERROR rtpdumpPackerError = IVAS_RTPDUMP_PACKER_NO_ERROR; + 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; #endif /*------------------------------------------------------------------------------------------* @@ -623,19 +634,38 @@ int main( if ( arg.rtpdumpOutput ) { - /* Open the output file for RTPDump writing */ - f_rtpstream = fopen( arg.outputBitstreamFilename, "wb" ); + IVAS_RTP_PACK_CONFIG packCfg; + uint32_t SSRC = ( rand() & 0xFFFF ) | ( (uint32_t) rand() << 16 ); + + packCfg.maxFramesPerPacket = arg.numFramesPerPacket; - if ( f_rtpstream == NULL ) + /* 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; } - 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_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_RTPDUMP_PACKER_open(): %d\n", rtpdumpPackerError ); + fprintf( stderr, "error in IVAS_RTP_PACK_UpdateHeader(): %d\n", error ); goto cleanup; } } @@ -669,6 +699,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 @@ -831,54 +862,99 @@ 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 ( rtpdumpPacker && rtpdumpPacker->rtpdump ) + if ( 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; + + /* 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 ); + 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 ) { @@ -966,9 +1042,25 @@ cleanup: } #ifdef IVAS_RTPDUMP - if ( rtpdumpPacker ) + if ( hPack ) { - IVAS_RTPDUMP_PACKER_close( &rtpdumpPacker ); + /* 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 ) @@ -980,6 +1072,8 @@ cleanup: { RotationFileReader_close( &deviceOrientationFileReader ); } + + IvasRtpFile_Close( &hWriter ); #endif IVAS_ENC_Close( &hIvasEnc ); @@ -1858,9 +1952,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 > IVAS_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; + } + } + } + fprintf( stdout, "Output format: RTPDump using %d frames/packet \n", arg->numFramesPerPacket ); } /*-----------------------------------------------------------------* @@ -2133,10 +2243,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 a2f893ed78edd4d5055503e9eb720de1a8d323f4..24b576d696e40e054f919b16bed8d06792e032b3 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_com/options.h b/lib_com/options.h index 941685105cbd14ae1e94537a9005118ca0d70f56..1f6e49388905026d7d7aa8577315b7392a4a0830 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_dec/lib_dec.c b/lib_dec/lib_dec.c index a476430478b0bcde23b41ae70a89490accbb3fe4..11e208218aff7322415e8e953bfd9073864e579e 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; } @@ -3742,7 +3741,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 +3749,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 +3796,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 40a5403373ad3a8c56bb8c9424804a5fc430c8a6..2e98d9e0f1e5547ba8f73c6b5db3d96ea43f30a6 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 0000000000000000000000000000000000000000..2bf707cea62b7a41a3efaf1eb1b74722d2738ee3 --- /dev/null +++ b/lib_util/ivas_bpool.c @@ -0,0 +1,154 @@ +/****************************************************************************************************** + + (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_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 ) && ( *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 numFreeBuffers; + mtx_lock( &handle->lock ); + numFreeBuffers = handle->numFreeBuffers; + mtx_unlock( &handle->lock ); + return numFreeBuffers; +} diff --git a/lib_util/ivas_bpool.h b/lib_util/ivas_bpool.h new file mode 100644 index 0000000000000000000000000000000000000000..ae8f114127407292b41d7f6805b50b3b1300ef94 --- /dev/null +++ b/lib_util/ivas_bpool.h @@ -0,0 +1,58 @@ +/****************************************************************************************************** + + (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_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 0000000000000000000000000000000000000000..c5806e0307234def2bf5aed26e2f10d3279864d3 --- /dev/null +++ b/lib_util/ivas_queue.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 "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 free-up all allocated memory */ +void QUEUE_Destroy( QUEUE_HANDLE *pHandle ) +{ + if ( ( pHandle != NULL ) && ( *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 ) +{ + 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 ) +{ + 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 ) +{ + uint32_t numNodes; + mtx_lock( &handle->lock ); + numNodes = handle->size; + mtx_unlock( &handle->lock ); + return numNodes; +} diff --git a/lib_util/ivas_queue.h b/lib_util/ivas_queue.h new file mode 100644 index 0000000000000000000000000000000000000000..9f2a9a856cdc1848ed3eb729f1be645149ced921 --- /dev/null +++ b/lib_util/ivas_queue.h @@ -0,0 +1,68 @@ +/****************************************************************************************************** + + (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_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 new file mode 100644 index 0000000000000000000000000000000000000000..29ed397bebe5713ff6d0a3df65ca1ee5e6001fc0 --- /dev/null +++ b/lib_util/ivas_rtp_api.h @@ -0,0 +1,603 @@ +/****************************************************************************************************** + + (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_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 + * + */ + +#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 + * 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 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 */ + + /* 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; +#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 */ +#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 */ +#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; + +#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 */ + + /* 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) */ +#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 + +#endif /* IVAS_RTP_API_H */ diff --git a/lib_util/ivas_rtp_file.c b/lib_util/ivas_rtp_file.c new file mode 100644 index 0000000000000000000000000000000000000000..0112fdfb547b87afd930ff0786d295a17eb07598 --- /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 0000000000000000000000000000000000000000..47d545208ad57152bd4d0268e060dcad4aa89157 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..f3569ca031ac7c8c60d0d31efb7f60b06644f9d7 --- /dev/null +++ b/lib_util/ivas_rtp_internal.h @@ -0,0 +1,140 @@ +/****************************************************************************************************** + + (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_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 ) + +#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]; +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_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 */ + + 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 d952450b52ee708439dc5b7815d157ea8da88e0a..f716f31eee9bafff5ea1d0e428c110ce8eb34d7c 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 @@ -29,1068 +29,1828 @@ 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" -/*-----------------------------------------------------------------------* - * writeByteToRtp() - * - * Write a single byte (value) to RTP packet. - *-----------------------------------------------------------------------*/ +#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 */ -static void writeByteToRtp( - IVAS_RTPDUMP_PACKER *self, /* i/o: IVAS rtpdump packer handle */ - char value /* i : Value to be written to the RTP packet */ -) +/* Generic RTP Header + Header Extension data structure */ +typedef struct { - self->rtpPacket.data[self->writingIndex] = value; - ++self->writingIndex; - self->rtpPacket.payloadSize += 1; -} - - -/*-----------------------------------------------------------------------* - * determineBitrate() - * - * Determine bitrate based on the number of encoded bits. - *-----------------------------------------------------------------------*/ + 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; -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 initRequests( IVAS_RTP_REQUEST_VALUE *requests ) { - *frameBitrate = numBits * IVAS_NUM_FRAMES_PER_SEC; + 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 */ } +static void initPiDataFrame( PIDATA_FRAME *piDataFrm ) +{ + /* 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 */ +} -/*-----------------------------------------------------------------------* - * 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 FRAME_NODE { - bool isIvasToc = ( toc & 0x30 ) == 0x10; // xx01 xxxx - if ( !isIvasToc ) - { - 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]; - } + 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; + +static void initFrameNode( FRAME_NODE *node ) +{ + node->next = NULL; + initPiDataFrame( &node->piDataFrame ); + node->auNumBits = 0; + node->tocNumBytes = 0; } +struct IVAS_RTP_PACK +{ + 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 +{ + 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 */ +} 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_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 */ +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 apilock; /* Lock to handle concurrent API invocation */ + size_t maxNumberOfFrames; + size_t maxNumberOfPiData; + RTP_HEADER header; + BPOOL_HANDLE unpackNodePool; + 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, 35 +}; + + +/* 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 */ ) { - 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 ) + RTP_HEADER *header = &hPack->header; + + if ( numCC > 15 ) { - PIdataCurrentFrame->deviceOrientationUncompensated = PIframeDataBeginning + parsedPIframeSizeBytes; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "CC must be less than 16" ); } - else if ( PIframeType == PI_ACOUSTIC_ENVIRONMENT ) + + header->ssrc = ssrc; + header->CC = numCC; + memcpy( header->CSRC, csrc, numCC & 0xF ); + + if ( ( numExtensionBytes > 0 ) && ( extData != NULL ) ) { - if ( PIframeSizeBytes == PI_ACOUSTIC_ENVIRONMENT_ID_SIZE_BYTES ) + header->extHeaderId = extHeaderId; + header->extData = realloc( header->extData, numExtensionBytes ); + if ( header->extData == NULL ) { - 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; + 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. - *-----------------------------------------------------------------------*/ + return IVAS_ERR_OK; +} -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 */ +static void InitRtpHeader( + RTP_HEADER *header /* RTP header structure */ ) { - if ( PIdataCurrentFrame->sceneOrientation != NULL ) + time_t t; + memset( header, 0, sizeof( *header ) ); + srand( (uint32_t) time( &t ) ); + header->version = 2; + header->seqNumber = rand() & 0xFFFF; +} + +static ivas_error PackRtpHeader( + RTP_HEADER *header, + IVAS_DATA_BUFFER *buf ) +{ + 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 ) { - PIdataCurrentFrame->sceneOrientation += PIheaderSizeBytes; + return IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient output buffer for RTP header packing" ); } - if ( PIdataCurrentFrame->deviceOrientationCompensated != NULL ) + + /* + +---+---+---+---+---+---+---+---+ + | 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 ) { - PIdataCurrentFrame->deviceOrientationCompensated += PIheaderSizeBytes; + /* 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--; } - if ( PIdataCurrentFrame->deviceOrientationUncompensated != NULL ) + + if ( extension ) { - PIdataCurrentFrame->deviceOrientationUncompensated += PIheaderSizeBytes; + 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 ); + + memcpy( &buf->buffer[nBytes], header->extData, header->numExtUnits * 4 ); + nBytes += header->numExtUnits; } - if ( PIdataCurrentFrame->acousticEnvironmentId != NULL ) + + buf->length = nBytes; + return IVAS_ERR_OK; +} + +static void UpdateRtpHeader( + RTP_HEADER *header, /* RTP header structure */ + uint32_t timestampOffset /* Timestamp offset @ 16KHz clock for next frame */ +) +{ + header->seqNumber++; + header->timestamp += timestampOffset; +} + +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 */ +) +{ + uint32_t n = 0, nByte = 0, expectedSize = 12; + uint8_t byte; + + if ( packet->length < expectedSize ) { - PIdataCurrentFrame->acousticEnvironmentId += PIheaderSizeBytes; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Insufficient input buffer for RTP header unpacking" ); } - if ( PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb != NULL ) + + /* + +---+---+---+---+---+---+---+---+ + | 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 ) { - PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb += PIheaderSizeBytes; + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "CC indicated but insufficient input buffer" ); } - if ( PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections != NULL ) + + /* CSRC Identifiers */ + for ( n = 0; n < header->CC; n++ ) { - PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections += PIheaderSizeBytes; + /* 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_convertFromQ15() - * - * Convert a Q15 encoded value to a float value. - *-----------------------------------------------------------------------*/ + if ( header->extension ) + { + 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++] ); -static float ivasPayload_convertFromQ15( uint16_t Q15Value ) -{ - float temp; - temp = (float) ( Q15Value & 0x7FFF ) / 0x7FFF; - return ( Q15Value & 0x8000 ) ? -temp : temp; -} + expectedSize += header->numExtUnits * 4; + if ( packet->length < expectedSize ) + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Extension Header indicated but insufficient input buffer" ); + } + header->extData = realloc( header->extData, header->numExtUnits * 4 ); + if ( header->extData == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "failed to allocate extdata" ); + } + memcpy( header->extData, &packet->buffer[nByte], header->numExtUnits * 4 ); + nByte += header->numExtUnits * 4; + } -/*-----------------------------------------------------------------------* - * ivasPayload_convertToQ15() - * - * Convert a float value into a Q15 encoded value. - *-----------------------------------------------------------------------*/ + *numHeaderBytes = nByte; + return IVAS_ERR_OK; +} -static uint16_t ivasPayload_convertToQ15( float value ) +static ivas_error getBitrateFromCodecAndFrameSize( + IVAS_RTP_CODEC codecId, + uint32_t frameLengthBytes, + uint32_t *bitrateKbps, + uint32_t *frameLenInBits, + uint8_t *tocByte, + bool *isAmrwbIOMode ) { - uint16_t temp; - temp = (uint16_t) ( fabsf( value ) * 0x7FFF ); - if ( value >= 0.0f ) + size_t n; + *bitrateKbps = 0; + *frameLenInBits = 0; + if ( codecId == IVAS_RTP_IVAS ) { - temp = temp & 0x7FFF; + /* Validate the framelength is supported for ivas frame */ + for ( n = 0; n < sizeof( ivasFrameSizeInBits ) / sizeof( ivasFrameSizeInBits[0] ); n++ ) + { + 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; + } + } + return IVAS_ERR_INVALID_BITRATE; } else { - temp = temp | 0x8000; + /* Try if frameLength is a supported EVS frame length */ + for ( n = 0; n < sizeof( evsFrameSizeInBits ) / sizeof( evsFrameSizeInBits[0] ); n++ ) + { + if ( evsFrameSizeInBits[n] == frameLengthBytes * 8 ) + { + *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; + } + } + /* 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 ) + { + *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 */ + return IVAS_ERR_OK; + } + } + return IVAS_ERR_INVALID_BITRATE; } - 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 */ +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 */ ) { - orientationQ15->w = ivasPayload_convertToQ15( orientation.w ); - orientationQ15->x = ivasPayload_convertToQ15( orientation.x ); - orientationQ15->y = ivasPayload_convertToQ15( orientation.y ); - orientationQ15->z = ivasPayload_convertToQ15( orientation.z ); -} + ivas_error error; + IVAS_RTP_PACK_HANDLE hPack; + uint32_t numFramesPerPacket = ( config->maxFramesPerPacket == 0 ) ? IVAS_MAX_FRAMES_PER_RTP_PACKET : config->maxFramesPerPacket; + if ( phPack == NULL ) + { + return IVAS_ERR_UNEXPECTED_NULL_POINTER; + } -/*-----------------------------------------------------------------------* - * ivasPayload_parseQuaternionValue() - * - * Parse a single Q15 encoded quaternion component from the RTP packet and convert it to float. - *-----------------------------------------------------------------------*/ + 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 ); + } -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 ); -} + *phPack = NULL; + if ( ( hPack = (IVAS_RTP_PACK_HANDLE) calloc( 1, sizeof( struct IVAS_RTP_PACK ) ) ) == NULL ) + { + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtppack handle" ); + } + error = BPOOL_Create( &hPack->packNodePool, sizeof( FRAME_NODE ), numFramesPerPacket * 4 ); + ERR_CHECK_RETURN( error ); -/*-----------------------------------------------------------------------* - * ivasPayload_parseOrientationPIdata() - * - * Parse a single orientation in quaternions from the PI data. - *-----------------------------------------------------------------------*/ + error = QUEUE_Create( &hPack->frameQ ); + ERR_CHECK_RETURN( error ); -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 */ + mtx_init( &hPack->apilock, 0 ); + hPack->initConfig = *config; + initRequests( hPack->requests ); + InitRtpHeader( &hPack->header ); + initPiDataFrame( &hPack->piDataCache ); + *phPack = hPack; + return IVAS_ERR_OK; +} + +/* 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 */ ) { - q->w = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->x = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->y = ivasPayload_parseQuaternionComponent( &PIorientationData ); - q->z = ivasPayload_parseQuaternionComponent( &PIorientationData ); + IVAS_RTP_PACK_HANDLE hPack; - 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 ) + /* Free all memory */ + if ( phPack == NULL || *phPack == NULL ) { - fprintf( stderr, "Error: invalid orientation PI data\n" ); - return false; + return; } - return true; -} + hPack = *phPack; + QUEUE_Destroy( &hPack->frameQ ); + mtx_destroy( &hPack->apilock ); + BPOOL_Destroy( &hPack->packNodePool ); + free( hPack->header.extData ); + free( hPack ); + *phPack = NULL; +} -/*---------------------------------------------------------------------* - * 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 */ +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 */ ) { - if ( orientation ) + if ( reqType < 0 || reqType >= IVAS_REQUEST_MAX ) { - /* 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_WRONG_PARAMS, "Invalid request key provided" ); } - return true; -} - - -/*-----------------------------------------------------------------------* - * 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 ) + /* Sanity on API */ + switch ( reqType ) { - if ( (int16_t) PIdataPayloadSizeBytes <= 0 ) + case IVAS_REQUEST_CODEC: { - fprintf( stderr, "Error: PI payload too small\n" ); - return false; + uint32_t codec = reqValue.codec; + if ( codec != IVAS_RTP_IVAS && codec != IVAS_RTP_EVS ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported codec id provided" ); + } } - - /* 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 ) + break; + case IVAS_REQUEST_BITRATE: { - ++PIdataPayload; - ++totalPIheaderSizeBytes; - --PIdataPayloadSizeBytes; - tempPIframeSizeBytes = ( *PIdataPayload & 0xFF ); - PIframeSizeBytes += tempPIframeSizeBytes; + uint32_t bitrate = reqValue.bitrate; + if ( bitrate < 5900 || bitrate > 512000 ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bitrate provided" ); + } } - ++PIdataPayload; - ++totalPIheaderSizeBytes; - --PIdataPayloadSizeBytes; - PIdataPayloadSizeBytes -= PIframeSizeBytes; - - /* general PI data for all frames */ - if ( PIframeMarker == PM_GENERAL ) + break; + case IVAS_REQUEST_BANDWIDTH: { - ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + uint32_t bandwidth = reqValue.bandwidth; + if ( bandwidth > IVAS_BANDWIDTH_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bandwidth provided" ); + } } - else + break; + case IVAS_REQUEST_FORMAT: { - /* current frame */ - if ( ( PIframeMarker > 0 ) & ( count_audio == frameIndex ) ) + uint32_t format = reqValue.formatType; + if ( format > IVAS_FMT_NO_REQ ) { - ivasPayload_parsePItype( PIframeDataBeginning, PIframeSizeBytes, totalPIframeSizeBytes, PIframeType, PIdataCurrentFrame ); + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported format provided" ); } - - /* increase audio frame counter */ - if ( PIframeMarker == PM_LAST ) + } + break; + case IVAS_REQUEST_CA_MODE: + { + uint32_t caMode = reqValue.caMode; + if ( caMode > IVAS_RTP_CA_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported channel aware mode provided" ); + } + } + break; +#ifdef RTP_S4_251135_CR26253_0016_REV1 + case IVAS_REQUEST_SUBFORMAT: + { + uint32_t subFormat = reqValue.subFormatType; + if ( subFormat > IVAS_SUBFMT_NO_REQ ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported subformat provided" ); + } + } + break; + case IVAS_REQUEST_SR_CONFIG: + { + IVAS_RTP_SPLITRENDER srConfig = reqValue.srConfig; + if ( srConfig.reserved != 0 ) { - ++count_audio; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported reserved bits in SR config provided" ); } } - totalPIframeSizeBytes += PIframeSizeBytes; + break; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ + default: + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported request type" ); } - ivasPayload_PIaddHeaderBytes( totalPIheaderSizeBytes, PIdataCurrentFrame ); - /* scene orientation */ - if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->sceneOrientation, &PIdataDepackerState->sceneOrientationSaved, &PIdataDepackerState->sceneOrientationQuat ) ) - { - fprintf( stderr, "Error: parsing scene orientation PI data failed.\n" ); - return false; - } + hPack->requests[reqType] = reqValue; + + return IVAS_ERR_OK; +} - /* device orientation */ - if ( !ivasPayload_parseSinglePIorientation( PIdataCurrentFrame->deviceOrientationUncompensated, &PIdataDepackerState->deviceOrientationSaved, &PIdataDepackerState->deviceOrientationQuat ) ) +static bool isAmrWBIOMode( uint32_t bitrate, uint32_t *idx ) +{ + size_t n; + *idx = 0; + for ( n = 0; n < sizeof( amrWBIOFrameSizeInBits ) / sizeof( amrWBIOFrameSizeInBits[0] ); n++ ) { - fprintf( stderr, "Error: parsing device orientation PI data failed.\n" ); - return false; + if ( bitrate == ( amrWBIOFrameSizeInBits[n] * IVAS_NUM_FRAMES_PER_SEC ) ) + { + *idx = n; + return true; + } } - - return true; + return false; } - -/*-----------------------------------------------------------------------* - * resetPIdataCurrentFrame() - * - * Reset the PI data handle for the current audio frame. - *-----------------------------------------------------------------------*/ - -static void resetPIdataCurrentFrame( PI_DATA_CURRENT_FRAME *PIdataCurrentFrame ) +static uint32_t getBitrateIdx( const uint32_t *table, uint32_t tableLen, uint32_t bitrate ) { - PIdataCurrentFrame->PIdataPresent = false; - PIdataCurrentFrame->sceneOrientation = NULL; - PIdataCurrentFrame->deviceOrientationCompensated = NULL; - PIdataCurrentFrame->deviceOrientationUncompensated = NULL; - PIdataCurrentFrame->acousticEnvironmentId = NULL; - PIdataCurrentFrame->acousticEnvironmentOnlyLateReverb = NULL; - PIdataCurrentFrame->acousticEnvironmentLateReverbAndEarlyReflections = NULL; -} + size_t n, idx = 0; + for ( n = 0; n < tableLen; n++ ) + { + if ( bitrate > ( table[n] * IVAS_NUM_FRAMES_PER_SEC ) ) + { + idx = n; + } + else + { + return idx; + } + } + return idx; +} -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 */ +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 */ ) { - 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 ) - { - 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 ) + /* 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 ) + { + /* If requesting EVS/AMRWB IO from farend, only Initial E-byte present */ + IVAS_RTP_CA_MODE caMode = hPack->requests[IVAS_REQUEST_CA_MODE].caMode; + + if ( caMode != IVAS_RTP_CA_NO_REQ ) { - if ( ( *payload & 0xF0 ) == IVAS_E_CMR ) // 1111 xxxx + 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 ) { - /* 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; + eByte[nByte++] = ( T | BR ); } - else + } + 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 ) { - /* EVS CMR */ - ++payload; - --payloadSizeBytes; - if ( *payload & E_BYTE ) // 1xxx xxxx - { - continue; - } - else - { - break; - } + eByte[nByte++] = ( T | BR ); } } - } - - /* parse all ToC entries */ - *frame = (unsigned char *) payload; /* no need to copy frame bytes */ - for ( iFrame = 0; someFrameFollowing; ++iFrame ) - { - if ( (int16_t) payloadSizeBytes <= 0 ) + else if ( bitrate > 0 ) { - fprintf( stderr, "Error: payload too small\n" ); - return false; + /* 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 ); + } } - if ( *payload & E_BYTE ) // 1xxx xxxx + else if ( piDataPresent ) { - fprintf( stderr, "Error: expected ToC, found CMR/E-byte\n" ); - return false; + /* Send an initial E-byte to allow for subsequent PI indication E-byte */ + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = EBYTE_CMR_T_NO_REQ; + } } - ivasPayload_parseToc( *payload, &someEvsIvasModeBit, &someFrameFollowing, &someBitrateIndex, &someIvasIndicatorBit, &someGoodFrameIndication, &bitrate ); - if ( bitrate < 0 ) + } + else + { + /* 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 + + /* Initial E-Byte only sent if either bitrate requested or subsequent E-byte is requested */ + if ( !( ( bitrate > 0 ) || isBandwidthProvided || isFormatProvided || + isSubFormatProvided || isSRConfigProvided || piDataPresent ) ) { - fprintf( stderr, "Error: unexpected frameTypeIndex in ToC\n" ); - return false; + return; } - ++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 ) + + if ( bitrate > 0 ) { - *frame += ( someFrameSizeInBits + 7 ) / 8; - if ( !someFrameFollowing ) + /* 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 ) { - fprintf( stderr, "Error: expected ToC with F bit set\n" ); - return false; + eByte[nByte++] = ( EBYTE_CMR_T_IVAS | BR ); } } - else if ( iFrame == frameIndex ) + else { - *evsIvasModeBit = someEvsIvasModeBit; - *frameFollowing = someFrameFollowing; - *bitrateIndex = someBitrateIndex; - *ivasIndicatorBit = someIvasIndicatorBit; - *isGoodFrame = someGoodFrameIndication; - *frameSizeInBits = someFrameSizeInBits; + if ( nByte < maxNumEBytes ) + { + eByte[nByte++] = EBYTE_CMR_T_NO_REQ; + } } - if ( (int16_t) payloadSizeBytes < 0 ) + + /* Subsequent E-bytes - Bandwidth Request */ + if ( isBandwidthProvided && nByte < maxNumEBytes ) { - fprintf( stderr, "Error: payload too small for frame %u data\n", frameIndex ); - return false; + uint8_t bw = ( bandwidth - IVAS_BANDWIDTH_WB ) & MASK_2BIT; + eByte[nByte++] = ( EBYTE_BANDWIDTH_REQUEST | bw ); } - } - /* parse PI data */ - if ( PIdataCurrentFrame.PIdataPresent ) - { - PIdataPayloadSizeBytes = payloadSizeBytes; - PIdataPayload += ( fullPayloadSizeBytes - PIdataPayloadSizeBytes ); - if ( !ivasPayload_parsePIdata( PIdataPayload, PIdataPayloadSizeBytes, frameIndex, &PIdataCurrentFrame, PIdataDepackerState ) ) + /* Subsequent E-bytes - Coded Format Request */ + if ( ( isFormatProvided || isSubFormatProvided ) && nByte < maxNumEBytes ) { - fprintf( stderr, "Error while parsing PI data\n" ); - return false; + uint8_t fmtEByte = ( EBYTE_FORMAT_REQUEST | ( (uint8_t) format & MASK_3BIT ) ); + eByte[nByte++] = isSubFormatProvided ? EBYTE_SUBFORMAT_REQUEST : fmtEByte; } - } - - 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; - } - 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) */ - 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 */ -) -{ - /* 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 ) +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( isSubFormatProvided && nByte < maxNumEBytes ) { - return IVAS_RTPDUMP_DEPACKER_EOF; + eByte[nByte++] = ( (uint8_t) subFormat & MASK_6BIT ); /* Requested Coded subFormat */ } - else if ( rtpdumpError != RTPDUMP_NO_ERROR ) + /* Subsequent E-bytes - Split Renderer Configuration Request */ + if ( isSRConfigProvided && nByte < maxNumEBytes ) { - return IVAS_RTPDUMP_DEPACKER_RTPDUMP_ERROR; + 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 ); } - self->frameIndex = 0; +#endif /* RTP_S4_251135_CR26253_0016_REV1 */ } - /* 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 ) ) + + /* Final E-byte is the PI Indicator E-Byte */ + if ( piDataPresent && ( nByte < maxNumEBytes ) ) { - return IVAS_RTPDUMP_DEPACKER_PAYLOAD_ERROR; + eByte[nByte++] = EBYTE_PI_INDICATOR; /* PI Indication */ } - /* 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; + *nBytesWritten = nByte; } -void IVAS_RTPDUMP_DEPACKER_close( - IVAS_RTPDUMP_DEPACKER *self /* i : IVAS rtpdump depacker handle */ + +#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 */ ) { - if ( !self ) + uint8_t bitIdx, codecId, digetic; + if ( bitrateKbps < 256000 || bitrateKbps > 512000 ) { - return; + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Unsupported bitrate for SR" ); } - RTPDUMP_Close( &self->rtpdump, 0 ); + + 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 */ -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 addPackedPiDataToFrame( PIDATA_FRAME *piDataFrm, const PIDATA_PACKED *packedPiData, uint32_t piDataType ) { - 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 ) - { - 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; + piDataFrm->piData[piDataType].size = packedPiData->size; + memcpy( piDataFrm->piData[piDataType].data, packedPiData->data, packedPiData->size ); + + /* 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--; + } } -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_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 */ ) { - UWord8 initialEbyte, subsequentEbyte; - bool initialEbyteWritten; + ivas_error error = IVAS_ERR_OK; + uint32_t bitrate = 0; + uint32_t frameLengthInBits = 0; + uint8_t tocByte = 0; +#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; - /* determine E-bytes */ - initialEbyte = 0; - subsequentEbyte = 0; - initialEbyteWritten = false; - if ( requestImmersiveFormatBitrate ) + if ( frameBuffer == NULL || frameBuffer->length == 0 ) { - 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; + /* Indicates a NO_DATA_FRAME in case of DTX mode */ + 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 { - // TODO(pajunen): add EVS CMRs here - /* EVS CMR */ - initialEbyteWritten = false; + error = getBitrateFromCodecAndFrameSize( codecId, frameBuffer->length, &bitrate, &frameLengthInBits, &tocByte, &hPack->amrwbIOMode ); + ERR_CHECK_RETURN( error ); } - if ( ivasBandwidthRequest != NO_BANDWIDTH_REQUEST ) + /* 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 ) + { + 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++ ) + { + 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 ); + } + } + initPiDataFrame( piDataFrame ); /* Reset Cache */ + } + mtx_unlock( &hPack->apilock ); + + /* Add to frames FiFo */ + QUEUE_Push( hPack->frameQ, (NODE *) node ); + + return IVAS_ERR_OK; +} + +uint32_t IVAS_RTP_PACK_GetNumFrames( + IVAS_RTP_PACK_HANDLE hPack /* i/o : IVAS rtp packer handle */ +) +{ + uint32_t nFrames = 0; + if ( hPack ) + { + nFrames = QUEUE_Size( hPack->frameQ ); + } + return nFrames; +} + +/* 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_error error = IVAS_ERR_OK; + 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 */ + data->size > sizeof( PIDATA ) ) + { + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Wrong PI Data provided" ); + } + + error = PI_PackData( data, &packedPiData, 0 ); + ERR_CHECK_RETURN( error ); + + mtx_lock( &hPack->apilock ); /* Lock to prevent access to shared cache */ + { + 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; +} + +#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 *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 *availableFrameNodes[IVAS_MAX_FRAMES_PER_RTP_PACKET]; + size_t numEBytes = 0; + + if ( payload == NULL ) { - if ( !initialEbyteWritten ) + return IVAS_ERROR( IVAS_ERR_WRONG_PARAMS, "Output data buffer is NULL" ); + } + + /* 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++ ) + { + FRAME_NODE *node = (FRAME_NODE *) QUEUE_Pop( hPack->frameQ ); + if ( node == NULL ) { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; + 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; + } + + /* IVAS Payload starts with E-Bytes */ + packEBytes( hPack, ( numPiDataPresent > 0 ), payload->capacity, &payload->buffer[payload->length], &numEBytes ); + payload->length += numEBytes; + + /* ToC bytes (atleast 1 byte per frame, 2 if SR )*/ + for ( n = 0; n < numFrame; n++ ) + { + FRAME_NODE *node = availableFrameNodes[n]; + uint8_t fBit = ( n != ( numFrame - 1 ) ) ? TOC_HEADER_FOLLOWS : 0; /* Next ToC present */ - switch ( ivasBandwidthRequest ) + WRITE_BYTE_PAYLOAD_OR_EXIT( payload, ( node->toc[0] | fBit ) ); +#ifdef RTP_S4_251135_CR26253_0016_REV1 + if ( node->tocNumBytes == 2 ) { - 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; + WRITE_BYTE_PAYLOAD_OR_EXIT( payload, node->toc[1] ); } - writeByteToRtp( self, (char) subsequentEbyte ); +#endif } - if ( ivasFormatRequest != NO_FORMAT_REQUEST ) + /* Frame Data */ + for ( n = 0; n < numFrame; n++ ) { - if ( !initialEbyteWritten ) + FRAME_NODE *node = availableFrameNodes[n]; + size_t frameLength = ( node->auNumBits + 7 ) >> 3; /* zero padded length in bytes */ + if ( payload->length + frameLength <= payload->capacity ) { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; + memcpy( &payload->buffer[payload->length], node->au, frameLength ); } + else + { + error = IVAS_ERROR( IVAS_ERR_INSUFFICIENT_OUTPUT_SIZE, "Insufficient memory to write RTP Payload" ); + goto err_exit; + } + payload->length += frameLength; + } - switch ( ivasFormatRequest ) + /* PI Data */ + if ( numPiDataPresent > 0 ) + { + bool skipPiData = false; + size_t nBytes = payload->length; + uint32_t numPiDataWritten = 0; + + for ( n = 0; n < numFrame; n++ ) { - 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; + 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 ); + 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 < payload->capacity ) + { + memcpy( &payload->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 ) + { + payload->length = nBytes; /* update payload length after PI packing */ } - writeByteToRtp( self, (char) subsequentEbyte ); } - if ( self->piDataPresent ) +err_exit: + /* Pop frames from Queue */ + for ( n = 0; n < numFrame; n++ ) { - if ( !initialEbyteWritten ) + if ( BPOOL_FreeBuffer( hPack->packNodePool, availableFrameNodes[n] ) != IVAS_ERR_OK ) { - initialEbyte = IVAS_CMR_NO_REQ; - writeByteToRtp( self, (char) initialEbyte ); - initialEbyteWritten = true; + assert( 0 ); /* catastrophic error if this fails */ + return IVAS_ERROR( IVAS_ERR_INTERNAL_FATAL, "frame node could not be freed" ); } - subsequentEbyte = IVAS_PI_INDICATION; - writeByteToRtp( self, (char) subsequentEbyte ); + availableFrameNodes[n] = NULL; } + + return error; } -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_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 */ ) { - UWord8 H_bit, F_bit, EVS_IVAS_mode_bit, Q_bit, toc; - int16_t is_amr_wb; + ivas_error error = PackRtpHeader( &hPack->header, packet ); + ERR_CHECK_RETURN( error ); + + error = IVAS_RTP_PACK_GetPayload( hPack, packet, numFramesInPacket ); + ERR_CHECK_RETURN( error ); + + UpdateRtpHeader( &hPack->header, ( *numFramesInPacket ) * 320 ); + + return IVAS_ERR_OK; +} - 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 ) +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 ) { - toc = (UWord8) ( rate2IVASmode( frameBitrate ) ); - EVS_IVAS_mode_bit = 0; - Q_bit = 1; + return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - else + + *phUnpack = NULL; + if ( ( hUnpack = (IVAS_RTP_UNPACK_HANDLE) calloc( 1, sizeof( struct IVAS_RTP_UNPACK ) ) ) == NULL ) { - toc = (UWord8) ( rate2EVSmode( frameBitrate, &is_amr_wb ) ); - EVS_IVAS_mode_bit = (UWord8) is_amr_wb; - Q_bit = 0; + return IVAS_ERROR( IVAS_ERR_FAILED_ALLOC, "Cannot allocate memory for IVAS rtp unpack handle" ); } - toc = ( H_bit << 7 ) | ( F_bit << 6 ) | ( EVS_IVAS_mode_bit << 5 ) | ( Q_bit << 4 ) | toc; - writeByteToRtp( self, (char) toc ); + + 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 ); + ERR_CHECK_RETURN( error ); + + error = BPOOL_Create( &hUnpack->piDataPool, sizeof( PIDATA_NODE ), hUnpack->maxNumberOfPiData ); + ERR_CHECK_RETURN( error ); + + error = QUEUE_Create( &hUnpack->frameQ ); + ERR_CHECK_RETURN( error ); + + error = QUEUE_Create( &hUnpack->piDataQ ); + ERR_CHECK_RETURN( error ); + + hUnpack->initConfig = *config; + mtx_init( &hUnpack->apilock, 0 ); + + initRequests( hUnpack->requests ); + + *phUnpack = hUnpack; + return IVAS_ERR_OK; } -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 */ +/* 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 */ ) { + IVAS_RTP_UNPACK_HANDLE hUnpack; + + /* Free all memory */ + if ( phUnpack == NULL || *phUnpack == NULL ) + { + return; + } + + hUnpack = *phUnpack; + mtx_destroy( &hUnpack->apilock ); + QUEUE_Destroy( &hUnpack->frameQ ); + QUEUE_Destroy( &hUnpack->piDataQ ); + BPOOL_Destroy( &hUnpack->piDataPool ); + BPOOL_Destroy( &hUnpack->unpackNodePool ); + 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 ); - RTPDUMP_ERROR rtpdumpError; - int32_t i, k, frameBitrate; - char tempData; + if ( ( byte & EBYTE_TOC_HEADER_BIT ) == 0 ) + { + return nBytes; + } - self->writingIndex = self->rtpPacket.headerSize; - self->rtpPacket.payloadSize = 0; - memset( self->rtpPacket.data, 0, sizeof( self->rtpPacket.data ) ); + nBytes++; /* Consume this e-byte */ - determineBitrate( numBits, &frameBitrate ); + 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; + } + } - /* payload header */ - IVAS_RTPDUMP_PACKER_writeEbytes( self, requestedBitrate, requestImmersiveFormatBitrate, ivasBandwidthRequest, ivasFormatRequest ); - IVAS_RTPDUMP_PACKER_writeToc( self, frameBitrate, isImmersiveFormat, false ); + return nBytes; +} - /* frame data */ - for ( i = 0; i < numBits; i += 8 ) +static uint32_t parseSubsequentEByte( const IVAS_DATA_BUFFER *payload, uint32_t nBytes, IVAS_RTP_REQUEST_VALUE *requests, bool *piDataIndicated ) +{ + while ( nBytes < payload->length ) { - tempData = 0; - for ( k = 0; k < 8; ++k ) + 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 ) { - tempData = (char) ( ( tempData << 1 ) | (char) bitStream[i + k] ); + 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 ); } - writeByteToRtp( self, tempData ); } - /* PI data */ - IVAS_RTPDUMP_PACKER_writePIdata( self ); + return nBytes; +} + +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; - /* write packet */ - rtpdumpError = RTPDUMP_WritePacket( self->rtpdump, &self->rtpPacket, self->timeoffset_ms ); - if ( rtpdumpError != RTPDUMP_NO_ERROR ) + *numFrames = 0; + while ( nBytes < payload->length && headerFollows ) { - return IVAS_RTPDUMP_PACKER_RTPDUMP_ERROR; + 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 */ + + 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; + toc->isAMRWB_IOmode = true; + 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++; + } + } } - /* adjust packetizer values */ - self->rtpPacket.sequenceNumber += 1; - self->rtpPacket.timeStamp += 320; - self->timeoffset_ms += 20; + *numBytes = nBytes; + return IVAS_ERR_OK; +} + +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; + + while ( PF ) + { + 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 ( 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 ); + 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 ); + } + else + { + return IVAS_ERROR( IVAS_ERR_UNDERFLOW, "Underflow during reading pi data" ); + } - return IVAS_RTPDUMP_PACKER_NO_ERROR; + if ( PM == PI_HEADER_PM_LAST ) + { + rtpTimestamp += 16000 / IVAS_NUM_FRAMES_PER_SEC; + } + } + + *numBytes = nBytes; + return IVAS_ERR_OK; } -void IVAS_RTPDUMP_PACKER_writePIdata( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +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 */ ) { - if ( !self->piDataPresent ) + ivas_error error = IVAS_ERR_OK; + uint32_t nBytes = 0, numFrames = 0, numPiData = 0, n; + bool piDataIndicated = false; + TOC_INFO toc[IVAS_MAX_FRAMES_PER_RTP_PACKET]; + + IVAS_RTP_REQUEST_VALUE oldRequests[IVAS_REQUEST_MAX]; + + if ( hUnpack == NULL || payload == NULL ) { - return; + return IVAS_ERR_UNEXPECTED_NULL_POINTER; } - UWord8 PIheaderByte; - IVAS_QUATERNION_Q15 sceneOrientationQ15, deviceOrientationQ15; - bool somePIfollowing = false; + if ( remoteRequestBitmap != NULL ) + { + *remoteRequestBitmap = 0; + } - /* PI header section */ + if ( numFramesInPacket != NULL ) + { + *numFramesInPacket = 0; + } - /* scene orientation */ - if ( self->piDataPacker.sceneOrientationPresent ) + if ( numPiDataInPacket != NULL ) { - if ( self->piDataPacker.deviceOrientationPresent ) - { - somePIfollowing = true; - } - if ( somePIfollowing ) + *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 */ + 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, sizeof( toc ) / sizeof( toc[0] ) ); + 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->unpackNodePool, (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 ) { - PIheaderByte = ( PF1 << 7 ) | ( PM_NOT_LAST << 5 ) | PI_SCENE_ORIENTATION; + 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; +} + +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 */ +) +{ + 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 ); } -void IVAS_RTPDUMP_PACKER_determinePIpresence( - IVAS_RTPDUMP_PACKER *self /* i/o: IVAS rtpdump packer handle */ +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 */ ) { - self->piDataPresent = false; - if ( self->piDataPacker.sceneOrientationPresent || - self->piDataPacker.deviceOrientationPresent ) + 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 */ + 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 */ ) { - 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 ( frameSizeInBits != NULL ) + { + *frameSizeInBits = (int16_t) node->toc.auNumBits; + } + + 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; + } + + if ( isAMRWB_IOmode != NULL ) + { + *isAMRWB_IOmode = node->toc.isAMRWB_IOmode; + } + + return BPOOL_FreeBuffer( hUnpack->unpackNodePool, 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 ) ) + { + memcpy( data, pi, pi->size ); + } + + if ( timestamp != NULL ) + { + *timestamp = node->timestamp; + } + + return BPOOL_FreeBuffer( hUnpack->piDataPool, node ); } -#endif + +#endif /* IVAS_RTPDUMP */ diff --git a/lib_util/ivas_rtp_payload.h b/lib_util/ivas_rtp_payload.h index 8529de51b739f70e70b97577e5c078c62c189cea..92cb0e8e715b49663b0ad568208e148573e91c41 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 new file mode 100644 index 0000000000000000000000000000000000000000..d3e8774281b590d405a400ee2454ada0f46aaf8d --- /dev/null +++ b/lib_util/ivas_rtp_pi_data.c @@ -0,0 +1,725 @@ +/****************************************************************************************************** + + (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_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 __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 __inline int16_t readInt16( const uint8_t *buffer ) +{ + return (int16_t) ( (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.0f ); + value = value > +32767.0f ? +32767.0f : value; + value = value < -32768.0f ? -32768.0f : 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 = 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; +} + +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; + uint8_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 < (uint32_t) 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 ) * 8; + + 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; + + 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 ) + { + 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 > ( IVAS_PI_MAX_OBJECTS + 1 ) ) + { + 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; +} + +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 / 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; +} + +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 = 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; +} + +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" ); + } + + /* 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] ); + } + 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]; + /* 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; + } + + 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 */ +#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 */ + packListenerPosition, /* LISTENER_POSITION */ + 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 */ +#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 */ +#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 */ + unpackListenerPosition, /* LISTENER_POSITION */ + unpackDynamicSuppression, /* DYNAMIC_AUDIO_SUPPRESSION */ + unpackOrientation, /* AUDIO_FOCUS_DIRECTION */ +#else + unpackUnsupportedData, /* PLAYBACK_DEVICE_ORIENTATION */ + unpackUnsupportedData, /* HEAD_ORIENTATION */ + unpackUnsupportedData, /* LISTENER_POSITION */ + 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 0000000000000000000000000000000000000000..3be265bb87a46b2f9dad7a5d6020323b59f111cc --- /dev/null +++ b/lib_util/ivas_rtp_pi_data.h @@ -0,0 +1,441 @@ +/****************************************************************************************************** + + (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_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; /* 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 0000000000000000000000000000000000000000..016e6ce34ea393817471c4b1ae818fe953a4c7f1 --- /dev/null +++ b/lib_util/ivas_rtp_rom.c @@ -0,0 +1,64 @@ +/****************************************************************************************************** + + (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 */ diff --git a/lib_util/mutex.h b/lib_util/mutex.h new file mode 100644 index 0000000000000000000000000000000000000000..caf0145e2e6ebef39a474de45738108958d94137 --- /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 */ diff --git a/tests/requirements.txt b/tests/requirements.txt index 2eb090f4fb56da7587e0a3eb19b7e287870361c1..a8d8f7b06447230f83eb69871de39e19f0f78a40 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 new file mode 100644 index 0000000000000000000000000000000000000000..f6923c007753f1cc3eee9ee84064fd4d3e7aa1b6 --- /dev/null +++ b/tests/rtp/ivasrtp.py @@ -0,0 +1,1157 @@ +#!/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 + +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" + 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 + 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 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}') + + +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 + unpackUnsupported, # RESERVED14 + unpackUnsupported, # RESERVED15 + 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 + 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 + 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: + 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 0000000000000000000000000000000000000000..342e29c8b64f3afde799ba22eab8230afeb670fe --- /dev/null +++ b/tests/rtp/test_rtp.py @@ -0,0 +1,478 @@ +#!/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 +import random + +from tempfile import TemporaryDirectory +from pathlib import Path +from ivasrtp import * +import soundfile as sf +import numpy as np + +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)) ]) + + 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" + 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}" + +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 = 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: + # 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)