From 7ef7709831bd8462338d93caff0dd3a3a732751c Mon Sep 17 00:00:00 2001 From: Archit Tamarapu Date: Wed, 11 Jun 2025 11:54:09 +0200 Subject: [PATCH] [renderer] add OSBA/OMASA CLI support and update printouts. see float MRs 1908 and 2146. --- apps/renderer.c | 149 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/apps/renderer.c b/apps/renderer.c index cf7f05b98..322f382ca 100644 --- a/apps/renderer.c +++ b/apps/renderer.c @@ -147,7 +147,7 @@ typedef struct int32_t sampleRate; InputConfig inConfig; OutputConfig outConfig; - char inMetadataFilePaths[RENDERER_MAX_ISM_INPUTS][RENDERER_MAX_CLI_ARG_LENGTH]; + char inMetadataFilePaths[RENDERER_MAX_ISM_INPUTS + RENDERER_MAX_MASA_INPUTS][RENDERER_MAX_CLI_ARG_LENGTH]; int16_t numInMetadataFiles; char headRotationFilePath[RENDERER_MAX_CLI_ARG_LENGTH]; char referenceVectorFilePath[RENDERER_MAX_CLI_ARG_LENGTH]; @@ -215,13 +215,13 @@ static const CmdLnParser_Option cliOptions[] = { .id = CmdLnOptionId_inputFormat, .match = "input_format", .matchShort = "if", - .description = "Audio format of input file (e.g. 5_1 or HOA3 or META, use -l for a list)", + .description = "Audio format of input file (e.g. 5_1 or HOA3 or META,\nuse -l for a list)", }, { .id = CmdLnOptionId_inputMetadata, .match = "input_metadata", .matchShort = "im", - .description = "Space-separated list of path to metadata files for ISM or MASA inputs", + .description = "Space-separated list of path to metadata files for ISM/MASA/OMASA/\nOSBA inputs. \nFor OMASA, ISM files must be specified first.", }, { .id = CmdLnOptionId_outputFile, @@ -239,57 +239,59 @@ static const CmdLnParser_Option cliOptions[] = { .id = CmdLnOptionId_sampleRate, .match = "sample_rate", .matchShort = "fs", - .description = "Input sampling rate in kHz (16, 32, 48) - required only with raw PCM inputs", + .description = "Input sampling rate in kHz (16, 32, 48) - required only with raw\nPCM inputs", }, { .id = CmdLnOptionId_trajFile, .match = "trajectory_file", .matchShort = "T", - .description = "Head rotation trajectory file for simulation of head tracking (only for binaural outputs)", + .description = "Head rotation trajectory file for simulation of head tracking\n(only for binaural outputs)", }, { .id = CmdLnOptionId_refRotFile, .match = "reference_rotation_file", .matchShort = "rf", - .description = "Reference rotation trajectory file for simulation of head tracking (only for binaural outputs)", + .description = "Reference rotation trajectory file for simulation of head tracking\n(only for binaural outputs)", }, { .id = CmdLnOptionId_customHrtfFile, .match = "custom_hrtf", .matchShort = "hrtf", - .description = "Custom HRTF file for binaural rendering (only for binaural outputs)", + .description = "Custom HRTF file for binaural rendering\n(only for binaural outputs)", }, { .id = CmdLnOptionId_renderConfigFile, .match = "render_config_parameters", .matchShort = "render_config", - .description = "Binaural renderer configuration parameters in file (only for binaural outputs)", + .description = "Binaural renderer configuration parameters in file\n(only for binaural outputs)", }, { .id = CmdLnOptionId_nonDiegeticPan, .match = "non_diegetic_panning", .matchShort = "non_diegetic_pan", - .description = "Panning mono non diegetic sound to stereo -90<= pan <= 90\nleft or l or 90->left, right or r or -90->right, center or c or 0 ->middle\n", + .description = "Panning mono non diegetic sound to stereo -90<= pan <= 90\nleft or l or 90->left, right or r or -90->right,\ncenter or c or 0 ->middle", }, { .id = CmdLnOptionId_orientationTracking, .match = "tracking_type", .matchShort = "otr", - .description = "Head orientation tracking type: 'none', 'ref', 'avg' or `ref_vec` or `ref_vec_lev` (only for binaural outputs)", + .description = "Head orientation tracking type: 'none', 'ref', 'avg' or `ref_vec`\nor `ref_vec_lev` (only for binaural outputs)", }, { .id = CmdlnOptionId_lfePosition, .match = "lfe_position", .matchShort = "lp", - .description = "Output LFE position. Comma-delimited triplet of [gain, azimuth, elevation] where gain is linear (like --gain, -g) and azimuth, elevation are in degrees.\nIf specified, overrides the default behavior which attempts to map input to output LFE channel(s)", + .description = "Output LFE position. Comma-delimited triplet of [gain, azimuth,\nelevation] where gain is linear (like --gain, -g) and azimuth,\nelevation are in degrees. If specified, overrides the default\nbehavior which attempts to map input to output LFE channel(s)", + }, + { + .id = CmdlnOptionId_lfeMatrix, + .match = "lfe_matrix", + .matchShort = "lm", + .description = "LFE panning matrix. File (CSV table) containing a matrix of\ndimensions [ num_input_lfe x num_output_channels ] with elements\nspecifying linear routing gain (like --gain, -g). If specified,\noverrides the output LFE position option and the default\nbehavior which attempts to map input to output LFE channel(s)", }, - { .id = CmdlnOptionId_lfeMatrix, - .match = "lfe_matrix", - .matchShort = "lm", - .description = "LFE panning matrix. File (CSV table) containing a matrix of dimensions [ num_input_lfe x num_output_channels ] with elements specifying linear routing gain (like --gain, -g). \nIf specified, overrides the output LFE position option and the default behavior which attempts to map input to output LFE channel(s)" }, { .id = CmdLnOptionId_noDelayCmp, - .match = "no_delay_comparison", + .match = "no_delay_compensation", .matchShort = "no_delay_cmp", .description = "[flag] Turn off delay compensation", }, @@ -297,7 +299,7 @@ static const CmdLnParser_Option cliOptions[] = { .id = CmdLnOptionId_complexityLevel, .match = "complexity_level", .matchShort = "level", - .description = "Complexity level, level = (1, 2, 3), will be defined after characterisation.", + .description = "Complexity level, level = (1, 2, 3), will be defined after\ncharacterisation.", }, { .id = CmdLnOptionId_quietModeEnabled, @@ -321,13 +323,13 @@ static const CmdLnParser_Option cliOptions[] = { .id = CmdLnOptionId_referenceVectorFile, .match = "reference_vector_file", .matchShort = "rvf", - .description = "Reference vector trajectory file for simulation of head tracking (only for binaural outputs)", + .description = "Reference vector trajectory file for simulation of head tracking\n(only for binaural outputs)", }, { .id = CmdLnOptionId_exteriorOrientationFile, .match = "exterior_orientation_file", .matchShort = "exof", - .description = "External orientation trajectory file for simulation of external orientations", + .description = "External orientation trajectory file for simulation of external\norientations", }, { .id = CmdLnOptionId_framing, @@ -339,19 +341,19 @@ static const CmdLnParser_Option cliOptions[] = { .id = CmdLnOptionId_syncMdDelay, .match = "sync_md_delay", .matchShort = "smd", - .description = "Metadata Synchronization Delay in ms, Default is 0. Quantized by 5ms subframes for TDRenderer (13ms -> 10ms -> 2subframes)", + .description = "Metadata Synchronization Delay in ms, Default is 0. Quantized by\n5ms subframes for TDRenderer (13ms -> 10ms -> 2subframes)", }, { .id = CmdLnOptionId_directivityPatternId, .match = "ism_directivity_pattern_id", .matchShort = "dpid", - .description = "Directivity pattern ID(s) = [ID1, ID2, ID3, ID4]. Space-separated list of up to 4 numbers (unsigned integers) can be specified for BINAURAL and BINAURAL_ROOM_REVERB output configuration.\nID1, ID2, ID3, ID4 specify the directivity pattern IDs used for ISMs 1,2,3 and 4 respectively.\nThis options needs to be accompanied by a render_config file, otherwise a default directivity pattern is used.", + .description = "Directivity pattern ID(s) = [ID1, ID2, ID3, ID4]. Space-separated\nlist of up to 4 numbers (unsigned integers) can be specified for\nBINAURAL and BINAURAL_ROOM_REVERB output.\nID1, ID2, ID3, ID4 specify the directivity pattern IDs used for\nISMs 1,2,3 and 4 respectively. \nThis option needs to be accompanied by a render_config file,\notherwise a default directivity pattern is used.", }, { .id = CmdLnOptionId_acousticEnvironmentId, .match = "acoustic_environment_id", .matchShort = "aeid", - .description = "Acoustic environment ID (number >= 0) for BINAURAL_ROOM_REVERB output configuration", + .description = "Acoustic environment ID (number >= 0) for BINAURAL_ROOM_REVERB output.", }, }; @@ -366,6 +368,8 @@ static IVAS_AUDIO_CONFIG ambisonicsOrderToEnum( const int16_t order ); static void parseSceneDescriptionFile( char *path, char *audioFilePath, InputConfig *inConfig, IsmPositionProvider *positionProvider, MasaFileReader **masaReaders, LfeRoutingConfig **lfeRoutingConfigs ); +static void parseCombinedFormatInput( InputConfig *inConfig, char **configString ); + static ivas_error parseCustomLayoutFile( const char *filePath, IVAS_CUSTOM_LS_DATA *pLsSetupCustom ); static CmdlnArgs parseCmdlnArgs( const int argc, char **argv ); @@ -525,25 +529,7 @@ static void setupWithSingleFormatInput( strncpy( audioFilePath, args.inputFilePath, FILENAME_MAX - 1 ); /* Depending on input format, prepare metadata reading for ISM or MASA */ - if ( args.inConfig.numMasaBuses != 0 ) - { - if ( args.inConfig.numMasaBuses != args.numInMetadataFiles ) - { - fprintf( stderr, "Error: all MASA inputs must have a corresponding metadata file" ); - exit( -1 ); - } - - for ( int16_t i = 0; i < args.numInMetadataFiles; ++i ) - { - masaReaders[i] = MasaFileReader_open( args.inMetadataFilePaths[i] ); - if ( masaReaders[i] == NULL ) - { - fprintf( stderr, "Could not open MASA metadata file %s\n", args.inMetadataFilePaths[i] ); - exit( -1 ); - } - } - } - else if ( args.inConfig.numAudioObjects != 0 ) + if ( args.inConfig.numAudioObjects != 0 ) { positionProvider->numObjects = args.inConfig.numAudioObjects; for ( int16_t i = 0; i < positionProvider->numObjects; ++i ) @@ -566,6 +552,26 @@ static void setupWithSingleFormatInput( } } } + if ( args.inConfig.numMasaBuses != 0 ) + { + if ( args.inConfig.numMasaBuses != ( args.numInMetadataFiles - args.inConfig.numAudioObjects ) ) + { + fprintf( stderr, "Error: all MASA inputs must have a corresponding metadata file" ); + exit( -1 ); + } + + int16_t reader_idx = 0; + for ( int16_t i = args.inConfig.numAudioObjects; i < args.numInMetadataFiles; ++i ) + { + reader_idx = i - args.inConfig.numAudioObjects; + masaReaders[reader_idx] = MasaFileReader_open( args.inMetadataFilePaths[i] ); + if ( masaReaders[reader_idx] == NULL ) + { + fprintf( stderr, "Could not open MASA metadata file %s\n", args.inMetadataFilePaths[i] ); + exit( -1 ); + } + } + } return; } @@ -1728,16 +1734,11 @@ static bool parseInConfig( inConfig->masaBuses[0].gain_dB = 0.0f; break; case IVAS_AUDIO_CONFIG_OBA: - /* If input format is objects, parse the characters after "ISM" to get number of objects */ + /* If input format is objects, parse the characters after "ISM" to get + * number of objects and check for combined formats. */ { char *ptr = NULL; inConfig->numAudioObjects = (uint16_t) strtol( inFormatStr + 3, &ptr, 10 ); - if ( ptr == NULL || *ptr != '\0' ) - { - /* Failed to parse string as a number */ - fprintf( stderr, "Cannot parse string \"%s\" as a valid input format", inFormatStr ); - return false; - } if ( inConfig->numAudioObjects > RENDERER_MAX_ISM_INPUTS ) { fprintf( stderr, "Too many objects at input. Max %d supported.", RENDERER_MAX_ISM_INPUTS ); @@ -1749,6 +1750,17 @@ static bool parseInConfig( inConfig->audioObjects[i].inputChannelIndex = i; inConfig->audioObjects[i].gain_dB = 0.0f; } + if ( *ptr != '\0' ) + { + /* Try to parse combined format */ + parseCombinedFormatInput( inConfig, &ptr ); + } + if ( ptr == NULL || *ptr != '\0' ) + { + /* Failed to parse string as a number */ + fprintf( stderr, "Cannot parse string \"%s\" as a valid input format", inFormatStr ); + return false; + } } break; case IVAS_AUDIO_CONFIG_INVALID: @@ -2179,7 +2191,7 @@ static void parseOption( } break; case CmdLnOptionId_inputMetadata: - assert( numOptionValues <= RENDERER_MAX_ISM_INPUTS ); + assert( numOptionValues <= RENDERER_MAX_ISM_INPUTS + RENDERER_MAX_MASA_INPUTS ); for ( int16_t i = 0; i < numOptionValues; ++i ) { strncpy( args->inMetadataFilePaths[i], optionValues[i], RENDERER_MAX_CLI_ARG_LENGTH - 1 ); @@ -2994,6 +3006,41 @@ static void parseMasa( return; } +static void parseCombinedFormatInput( + InputConfig *inConfig, + char **configString ) +{ + IVAS_AUDIO_CONFIG audioConfig; + audioConfig = parseAudioConfig( *configString ); + + if ( audioConfig == IVAS_AUDIO_CONFIG_FOA || audioConfig == IVAS_AUDIO_CONFIG_HOA2 || audioConfig == IVAS_AUDIO_CONFIG_HOA3 ) + { + /* OSBA */ + inConfig->numAmbisonicsBuses = 1; + inConfig->ambisonicsBuses[0].audioConfig = audioConfig; + inConfig->ambisonicsBuses[0].inputChannelIndex = inConfig->numAudioObjects; + inConfig->ambisonicsBuses[0].gain_dB = -6.f; + *configString += 4; + + /* Modify input gain for objects too */ + for ( int16_t i = 0; i < inConfig->numAudioObjects; ++i ) + { + inConfig->audioObjects[i].gain_dB = -6.f; + } + } + else if ( audioConfig == IVAS_AUDIO_CONFIG_MASA1 || audioConfig == IVAS_AUDIO_CONFIG_MASA2 ) + { + /* OMASA */ + inConfig->numMasaBuses = 1; + inConfig->masaBuses[0].audioConfig = audioConfig; + inConfig->masaBuses[0].inputChannelIndex = inConfig->numAudioObjects; + inConfig->masaBuses[0].gain_dB = 0.0f; + *configString += 5; + } + + return; +} + static ivas_error parseCustomLayoutFile( const char *filePath, IVAS_CUSTOM_LS_DATA *pLsSetupCustom ) @@ -3210,10 +3257,13 @@ static void printSupportedAudioConfigs( void ) "HOA2", "HOA3", "ISMx (input only)", + "ISMxSBAy (OSBA, input only)", + "ISMxMASAy (OMASA, input only)", "MASAx", "BINAURAL (output only)", "BINAURAL_ROOM_IR (output only)", "BINAURAL_ROOM_REVERB (output only)", + "META (Scene description, input only)", }; fprintf( stdout, "Supported audio formats:\n" ); @@ -3221,6 +3271,7 @@ static void printSupportedAudioConfigs( void ) { fprintf( stdout, "%s\n", supportedFormats[i] ); } + fprintf( stdout, "\n" ); return; } -- GitLab