diff --git a/code/include/IAMF_decoder.h b/code/include/IAMF_decoder.h index 4071ad69..87cd0c43 100755 --- a/code/include/IAMF_decoder.h +++ b/code/include/IAMF_decoder.h @@ -253,13 +253,29 @@ int IAMF_layout_binaural_channels_count(void); char *IAMF_decoder_get_codec_capability(void); /** - * @brief Set target normalization loudness value, then loudness will be - * adjusted to the setting target. - * @param [in] handle : iamf decoder handle. - * @param [in] loundness : target normalization loudness in LKFS. - * 0 dose not do normalization, - * others(<0) target value of normalization. - * @return @ref IAErrCode. + * @brief Set loudness normalization target for IAMF decoder + * + * This function configures the target loudness for loudness normalization + * according to EBU R128 standard. Normalization ensures consistent loudness + * across different audio sources. + * + * @param [in] handle : IAMF decoder handle + * @param [in] loudness : Target loudness in dB (LKFS): + * - loudness = 0.0f: DISABLE loudness normalization (use original loudness) + * - loudness < 0: Enable normalization to target loudness + * * Common values: -23.0 (EBU R128), -24.0 (ATSC A/85) + * * Example: -16.0 (YouTube), -14.0 (Spotify) + * - loudness > 0: INVALID (returns IAMF_ERR_BAD_ARG) + * + * @return @ref IAErrCode. IAMF_OK on success, error code on failure. + * + * @note IMPORTANT: loudness = 0.0f is a SPECIAL FLAG to disable normalization. + * It does NOT mean normalize to 0 dB (which would be digital full scale). + * To disable normalization, explicitly set loudness = 0.0f. + * + * @note Normalization is only applied if the presentation is active. + * + * @see EBU R128 Technical Recommendation EBU - TECH 3344 */ int IAMF_decoder_set_normalization_loudness(IAMF_DecoderHandle handle, float loudness); diff --git a/code/src/iamf_dec/iamf_audio_block.c b/code/src/iamf_dec/iamf_audio_block.c index 2388f506..6081c573 100755 --- a/code/src/iamf_dec/iamf_audio_block.c +++ b/code/src/iamf_dec/iamf_audio_block.c @@ -358,15 +358,6 @@ int iamf_audio_block_trim(iamf_audio_block_t* block) { return 0; } -int iamf_audio_block_gain(iamf_audio_block_t* block, float gain) { - if (!block) return -22; - if (gain == def_default_loudness_gain) return 0; - int num_samples = block->num_channels * block->num_samples_per_channel; - - for (int i = 0; i < num_samples; ++i) block->data[i] *= gain; - return 0; -} - uint32_t iamf_audio_block_available_samples(iamf_audio_block_t* block) { if (!block) return 0; return block->num_samples_per_channel - block->skip - block->padding - diff --git a/code/src/iamf_dec/iamf_audio_block.h b/code/src/iamf_dec/iamf_audio_block.h index 658baccc..194258e8 100755 --- a/code/src/iamf_dec/iamf_audio_block.h +++ b/code/src/iamf_dec/iamf_audio_block.h @@ -71,7 +71,6 @@ int iamf_audio_block_partial_copy_data(iamf_audio_block_t* dst, iamf_audio_block_t* iamf_audio_block_samples_concat( iamf_audio_block_t* blocks[], uint32_t n); int iamf_audio_block_trim(iamf_audio_block_t* block); -int iamf_audio_block_gain(iamf_audio_block_t* block, float gain); uint32_t iamf_audio_block_available_samples(iamf_audio_block_t* block); #endif //__IAMF_AUDIO_BLOCK_H__ diff --git a/code/src/iamf_dec/iamf_decoder.c b/code/src/iamf_dec/iamf_decoder.c index dd55f081..28cf5b19 100755 --- a/code/src/iamf_dec/iamf_decoder.c +++ b/code/src/iamf_dec/iamf_decoder.c @@ -148,65 +148,6 @@ static sample_format_t _get_sample_format(uint32_t bit_depth, int interleaved) { return ck_sample_format_i16_interleaved; } -static float iamf_mix_presentation_get_best_loudness( - iamf_mix_presentation_obu_t *obj, iamf_layout_t *layout) { - obu_sub_mix_t *sub; - float loudness_lkfs = def_default_loudness_lkfs; - iamf_layout_t highest_layout = - def_sound_system_layout_instance(SOUND_SYSTEM_NONE); - - int num_sub_mixes = array_size(obj->sub_mixes); - - for (int sub_idx = 0; sub_idx < num_sub_mixes; ++sub_idx) { - sub = def_value_wrap_optional_ptr(array_at(obj->sub_mixes, sub_idx)); - if (!sub) continue; - - int n = array_size(sub->loudness_layouts); - if (n <= 0) continue; - - for (int i = 0; i < n; ++i) { - iamf_layout_t *loudness_layout = - def_value_wrap_optional_ptr(array_at(sub->loudness_layouts, i)); - if (!loudness_layout) continue; - - if (iamf_layout_is_equal(*layout, *loudness_layout)) { - obu_loudness_info_t *li = - def_value_wrap_optional_ptr(array_at(sub->loudness, i)); - if (li) { - loudness_lkfs = - iamf_gain_q78_to_db(def_lsb_16bits(li->integrated_loudness)); - info( - "selected loudness %f db from exact match layout in sub_mix[%d] " - "<- 0x%x", - loudness_lkfs, sub_idx, def_lsb_16bits(li->integrated_loudness)); - return loudness_lkfs; - } - } - - if (iamf_layout_is_equal(highest_layout, def_sound_system_layout_instance( - SOUND_SYSTEM_NONE)) || - iamf_layout_higher_check(*loudness_layout, highest_layout, 1)) { - obu_loudness_info_t *li = - def_value_wrap_optional_ptr(array_at(sub->loudness, i)); - if (li) { - highest_layout = *loudness_layout; - loudness_lkfs = - iamf_gain_q78_to_db(def_lsb_16bits(li->integrated_loudness)); - debug( - "selected loudness %f db from highest layout in sub_mix[%d] <- " - "0x%x", - loudness_lkfs, sub_idx, def_lsb_16bits(li->integrated_loudness)); - } - } - } - } - - debug("selected loudness %f db from highest layout %s", loudness_lkfs, - iamf_layout_string(highest_layout)); - - return loudness_lkfs; -} - static int iamf_decoder_priv_decode(iamf_decoder_t *self, const uint8_t *data, int32_t size, uint32_t *rsize, void *pcm) { iamf_decoder_context_t *ctx = &self->ctx; @@ -538,7 +479,6 @@ int iamf_decoder_priv_configure(iamf_decoder_t *self, const uint8_t *data, ctx->head_tracking_enabled); } - // Set element gain offset to presentation. if (ctx->element_gains && hash_map_size(ctx->element_gains) > 0) { hash_map_iterator_t *iter = hash_map_iterator_new(ctx->element_gains); if (iter) { @@ -563,15 +503,12 @@ int iamf_decoder_priv_configure(iamf_decoder_t *self, const uint8_t *data, } while (!hash_map_iterator_next(iter)); hash_map_iterator_delete(iter); } - // Clear element gains after applying to presentation hash_map_delete(ctx->element_gains, 0); ctx->element_gains = 0; info("Cleared element gains after applying to presentation %u", mpo->mix_presentation_id); } - // Check if the sampling rate of the stream in presentation matches the - // configured sampling rate int in_sampling_rate = iamf_presentation_get_sampling_rate(self->presentation); if (in_sampling_rate == ctx->sampling_rate) { @@ -589,14 +526,8 @@ int iamf_decoder_priv_configure(iamf_decoder_t *self, const uint8_t *data, ret = iamf_decoder_priv_update_frame_info(self); if (ret == IAMF_OK) { self->ctx.status = ck_iamf_decoder_status_parse_2; - self->ctx.loudness_lkfs = - iamf_mix_presentation_get_best_loudness(mpo, &self->ctx.layout); - if (self->ctx.normalized_loudness_lkfs != def_default_loudness_lkfs) { - iamf_presentation_set_loudness_gain( - self->presentation, - f32_db_to_linear(self->ctx.normalized_loudness_lkfs - - self->ctx.loudness_lkfs)); - } + iamf_presentation_set_loudness(self->presentation, + self->ctx.normalized_loudness_lkfs); } } } else { @@ -818,7 +749,6 @@ IAMF_DecoderHandle IAMF_decoder_open(void) { ctx->sampling_rate = def_default_sampling_rate; ctx->mix_presentation_id = def_i64_id_none; - ctx->loudness_lkfs = def_default_loudness_lkfs; ctx->normalized_loudness_lkfs = def_default_loudness_lkfs; ctx->limiter_threshold_db = def_limiter_max_true_peak; ctx->enable_limiter = 1; @@ -1046,13 +976,19 @@ int IAMF_decoder_set_normalization_loudness(IAMF_DecoderHandle handle, float loudness) { iamf_decoder_t *self = (iamf_decoder_t *)handle; if (!self) return IAMF_ERR_BAD_ARG; + + if (loudness > 0) { + error("Invalid loudness value: %.2f dB. Loudness must be 0 or negative.", + loudness); + return IAMF_ERR_BAD_ARG; + } + if (self->ctx.normalized_loudness_lkfs != loudness) { self->ctx.normalized_loudness_lkfs = loudness; - if (self->ctx.status > ck_iamf_decoder_status_configure) { - iamf_presentation_set_loudness_gain( - self->presentation, - f32_db_to_linear(self->ctx.normalized_loudness_lkfs - - self->ctx.loudness_lkfs)); + + if (self->ctx.status > ck_iamf_decoder_status_configure && + self->presentation) { + iamf_presentation_set_loudness(self->presentation, loudness); } } return IAMF_OK; diff --git a/code/src/iamf_dec/iamf_decoder_private.h b/code/src/iamf_dec/iamf_decoder_private.h index 27075bbc..d61673ec 100755 --- a/code/src/iamf_dec/iamf_decoder_private.h +++ b/code/src/iamf_dec/iamf_decoder_private.h @@ -51,7 +51,6 @@ struct IAMF_DecoderContext { hash_map_t *element_gains; - float loudness_lkfs; float normalized_loudness_lkfs; float limiter_threshold_db; int enable_limiter; diff --git a/code/src/iamf_dec/iamf_presentation.c b/code/src/iamf_dec/iamf_presentation.c index 12eb8ca0..9476e151 100755 --- a/code/src/iamf_dec/iamf_presentation.c +++ b/code/src/iamf_dec/iamf_presentation.c @@ -91,8 +91,10 @@ typedef struct IamfPresentation { iamf_synchronizer_t* synchronizer; iamf_renderer_t renderer; - float loudness_gain; int out_sampling_rate; + + float loudnesses[def_max_sub_mixes]; + int num_loudnesses; } iamf_presentation_t; /** @@ -108,8 +110,9 @@ typedef struct IamfPresentation { * @param pid The parameter ID to test for conflicts * @return 1 if there is a conflict, 0 otherwise */ -static int iamf_presentation_pid_conflicts(iamf_presentation_t* self, - uint32_t element_id, uint32_t pid) { +static int iamf_presentation_priv_pid_conflicts(iamf_presentation_t* self, + uint32_t element_id, + uint32_t pid) { // Check conflict with mix_gain_id for the same element value_wrap_t* val = hash_map_get(self->elements, element_id); if (val) { @@ -157,11 +160,11 @@ static int iamf_presentation_pid_conflicts(iamf_presentation_t* self, * @param element_id The element ID * @return A unique parameter ID for gain offset */ -static uint32_t iamf_presentation_generate_unique_gain_offset_id( +static uint32_t iamf_presentation_priv_generate_unique_gain_offset_id( iamf_presentation_t* self, uint32_t element_id) { // First, try using pid directly uint32_t candidate_id = self->id; - if (!iamf_presentation_pid_conflicts(self, element_id, candidate_id)) { + if (!iamf_presentation_priv_pid_conflicts(self, element_id, candidate_id)) { debug("Using direct id %u as gain offset parameter ID", candidate_id); return candidate_id; } @@ -169,7 +172,7 @@ static uint32_t iamf_presentation_generate_unique_gain_offset_id( // If conflict, try different offsets for (uint32_t offset = 1; offset < 0xFFFF; offset++) { candidate_id += offset; - if (!iamf_presentation_pid_conflicts(self, element_id, candidate_id)) { + if (!iamf_presentation_priv_pid_conflicts(self, element_id, candidate_id)) { debug( "Generated unique gain offset ID: original_id=%u, unique_id=%u, " "offset=%u", @@ -350,8 +353,9 @@ static iamf_presentation_element_t* iamf_presentation_priv_make_element( def_rendering_config_flag_element_gain_offset) { element->enable_gain_offset = 1; // Initialize gain offset PID when gain offset is enabled - element->gain_offset_pid = iamf_presentation_generate_unique_gain_offset_id( - self, element->element_id); + element->gain_offset_pid = + iamf_presentation_priv_generate_unique_gain_offset_id( + self, element->element_id); debug( "Initialized gain offset PID during element creation: element_id=%u, " "gain_offset_pid=%u", @@ -1137,10 +1141,123 @@ static int iamf_presentation_priv_update_output_mix_gain( return IAMF_OK; } -static int iamf_presentation_priv_loudness_process(iamf_presentation_t* self, - iamf_audio_block_t* block) { - if (self->loudness_gain != def_default_loudness_gain) - iamf_audio_block_gain(block, self->loudness_gain); +/** + * @brief Get best loudness for a specific sub_mix + * + * This function searches for the best loudness value in a specific sub_mix + * that matches the given audio layout. + * + * @param obj Pointer to mix presentation OBU + * @param layout Target audio layout + * @param sub_mix_index Index of sub_mix to search + * @return Loudness value in dB (LKFS), or default loudness if not found + */ +static float _sub_mix_get_best_loudness(iamf_mix_presentation_obu_t* obj, + iamf_layout_t* layout, + int sub_mix_index) { + obu_sub_mix_t* sub; + float loudness_lkfs = def_default_loudness_lkfs; + iamf_layout_t highest_layout = + def_sound_system_layout_instance(SOUND_SYSTEM_NONE); + + sub = def_value_wrap_optional_ptr(array_at(obj->sub_mixes, sub_mix_index)); + if (!sub) { + warning("Sub_mix at index %d is NULL", sub_mix_index); + return loudness_lkfs; + } + + int n = array_size(sub->loudness_layouts); + if (n <= 0) { + debug("No loudness layouts in sub_mix %d", sub_mix_index); + return loudness_lkfs; + } + + for (int i = 0; i < n; ++i) { + iamf_layout_t* loudness_layout = + def_value_wrap_optional_ptr(array_at(sub->loudness_layouts, i)); + if (!loudness_layout) continue; + + if (iamf_layout_is_equal(*layout, *loudness_layout)) { + obu_loudness_info_t* li = + def_value_wrap_optional_ptr(array_at(sub->loudness, i)); + if (li) { + loudness_lkfs = + iamf_gain_q78_to_db(def_lsb_16bits(li->integrated_loudness)); + info( + "selected loudness %f db from exact match layout in sub_mix[%d] " + "<- 0x%x", + loudness_lkfs, sub_mix_index, + def_lsb_16bits(li->integrated_loudness)); + return loudness_lkfs; + } + } + + if (iamf_layout_is_equal(highest_layout, def_sound_system_layout_instance( + SOUND_SYSTEM_NONE)) || + iamf_layout_higher_check(*loudness_layout, highest_layout, 1)) { + obu_loudness_info_t* li = + def_value_wrap_optional_ptr(array_at(sub->loudness, i)); + if (li) { + highest_layout = *loudness_layout; + loudness_lkfs = + iamf_gain_q78_to_db(def_lsb_16bits(li->integrated_loudness)); + debug( + "selected loudness %f db from highest layout in sub_mix[%d] <- " + "0x%x", + loudness_lkfs, sub_mix_index, + def_lsb_16bits(li->integrated_loudness)); + } + } + } + + debug("selected loudness %f db from highest layout %s in sub_mix[%d]", + loudness_lkfs, iamf_layout_string(highest_layout), sub_mix_index); + + return loudness_lkfs; +} + +/** + * @brief Initialize loudness for all sub-mixes (internal function) (MOD 5) + * + * This function is called during iamf_presentation_create() to + * pre-fetch loudness values for all sub-mixes and store them in the + * presentation. This optimizes performance by fetching loudness only once + * during creation. + * + * @param self Pointer to presentation instance + * @param layout Audio layout for loudness lookup + * @return IAMF_OK on success, error code on failure + * + * @note This function: + * • Iterates through all sub-mixes in the mix presentation + * • Retrieves loudness for each sub-mix matching the given layout + * • Stores loudness values in self->loudnesses[] array + * • No need to re-fetch during iamf_presentation_set_loudness() + */ +static int iamf_presentation_priv_init_loudness(iamf_presentation_t* self, + iamf_layout_t* layout) { + if (!self || !layout) return IAMF_ERR_BAD_ARG; + + iamf_mix_presentation_obu_t* mpo = + self->database + ? iamf_database_get_mix_presentation_obu(self->database, self->id) + : NULL; + if (!mpo) { + debug("No mix presentation, skip loudness initialization"); + return IAMF_OK; + } + + int num_sub_mixes = array_size(mpo->sub_mixes); + + for (int sub_idx = 0; sub_idx < num_sub_mixes; ++sub_idx) { + self->loudnesses[sub_idx] = + _sub_mix_get_best_loudness(mpo, layout, sub_idx); + debug("Sub_mix[%d] loudness: %f dB", sub_idx, self->loudnesses[sub_idx]); + } + + self->num_loudnesses = num_sub_mixes; + info("Initialized loudness for %u sub-mixes", num_sub_mixes); + return IAMF_OK; } @@ -1164,7 +1281,6 @@ iamf_presentation_t* iamf_presentation_create( self->database = database; self->reconstructor = reconstructor; self->layout = layout; - self->loudness_gain = def_default_loudness_gain; self->elements = hash_map_new(def_hash_map_capacity_elements); if (!self->elements) { @@ -1228,6 +1344,8 @@ iamf_presentation_t* iamf_presentation_create( return 0; } + iamf_presentation_priv_init_loudness(self, &self->layout); + info("Successfully initialized IAMF OAR"); return self; @@ -1332,9 +1450,54 @@ int iamf_presentation_get_element_gain_offset(iamf_presentation_t* self, return IAMF_OK; } -int iamf_presentation_set_loudness_gain(iamf_presentation_t* self, float gain) { - if (!self || !gain) return IAMF_ERR_BAD_ARG; - if (self->loudness_gain != gain) self->loudness_gain = gain; +int iamf_presentation_set_loudness(iamf_presentation_t* self, + float target_loudness_lkfs) { + if (!self) return IAMF_ERR_BAD_ARG; + + debug("Set loudness normalization: target=%.2f dB", target_loudness_lkfs); + + if (target_loudness_lkfs == def_default_loudness_lkfs) { + int ret = iamf_renderer_enable_loudness_processor(&self->renderer, 0); + if (ret != IAMF_OK) { + error("Failed to disable loudness processor"); + return ret; + } + debug("Loudness normalization disabled (target=%.2f dB = default)", + target_loudness_lkfs); + return IAMF_OK; + } + + for (int sub_idx = 0; sub_idx < self->num_loudnesses; ++sub_idx) { + if (self->loudnesses[sub_idx] != def_default_loudness_lkfs) { + float current_loudness = self->loudnesses[sub_idx]; + int ret = iamf_renderer_set_loudness( + &self->renderer, sub_idx, current_loudness, target_loudness_lkfs); + + if (ret != IAMF_OK) { + error( + "Failed to set loudness for sub_mix[%u]: current=%.2f dB, " + "target=%.2f dB", + sub_idx, current_loudness, target_loudness_lkfs); + return ret; + } + + info("Set loudness for sub_mix[%u]: current=%.2f dB, target=%.2f dB", + sub_idx, current_loudness, target_loudness_lkfs); + } else { + debug("Sub_mix[%u] has default loudness, skipping loudness setting", + sub_idx); + } + } + + int ret = iamf_renderer_enable_loudness_processor(&self->renderer, 1); + if (ret != IAMF_OK) { + error("Failed to enable loudness processor"); + return ret; + } + + info("Loudness normalization enabled with target=%.2f dB", + target_loudness_lkfs); + return IAMF_OK; } @@ -1475,8 +1638,6 @@ iamf_audio_block_t* iamf_presentation_process(iamf_presentation_t* self) { return 0; } - iamf_presentation_priv_loudness_process(self, output_block); - debug("Successfully processed presentation %u.", self->id); return output_block; } diff --git a/code/src/iamf_dec/iamf_presentation.h b/code/src/iamf_dec/iamf_presentation.h index 3c5148f8..38253bb9 100755 --- a/code/src/iamf_dec/iamf_presentation.h +++ b/code/src/iamf_dec/iamf_presentation.h @@ -42,7 +42,22 @@ int iamf_presentation_set_element_gain_offset(iamf_presentation_t* self, uint32_t eid, float offset); int iamf_presentation_get_element_gain_offset(iamf_presentation_t* self, uint32_t eid, float* offset); -int iamf_presentation_set_loudness_gain(iamf_presentation_t* self, float gain); + +/** + * @brief Set loudness normalization parameters. + * + * This function sets the target loudness for normalization and automatically + * enables/disables the loudness processor based on the target loudness value. + * When target_loudness differs from the default, OAR will automatically + * normalize the output audio to the target loudness level during rendering. + * + * @param [in] self : Pointer to the presentation instance. + * @param [in] target_loudness_lkfs : Target loudness in dB (LKFS). + * + * @return 0 on success, non-zero error code on failure. + */ +int iamf_presentation_set_loudness(iamf_presentation_t* self, + float target_loudness_lkfs); uint32_t iamf_presentation_get_id(iamf_presentation_t* self); int iamf_presentation_get_sampling_rate(iamf_presentation_t* self); diff --git a/code/src/iamf_dec/iamf_private_definitions.h b/code/src/iamf_dec/iamf_private_definitions.h index 46e071ef..93a652b3 100755 --- a/code/src/iamf_dec/iamf_private_definitions.h +++ b/code/src/iamf_dec/iamf_private_definitions.h @@ -41,7 +41,6 @@ #define def_limiter_max_true_peak -1.0f #define def_default_loudness_lkfs 0.0f -#define def_default_loudness_gain 1.0f #define def_i32_id_none (-1) #define def_i64_id_none (-1LL) diff --git a/code/src/iamf_dec/iamf_renderer.c b/code/src/iamf_dec/iamf_renderer.c index 2f1d2a91..d6266749 100755 --- a/code/src/iamf_dec/iamf_renderer.c +++ b/code/src/iamf_dec/iamf_renderer.c @@ -23,6 +23,7 @@ #include #include "iamf_private_definitions.h" +#include "iamf_utils.h" static const struct { iamf_sound_system_t sound_system; @@ -439,3 +440,31 @@ int iamf_renderer_enable_head_tracking(iamf_renderer_t* self, uint32_t enable) { ? IAMF_OK : IAMF_ERR_INTERNAL; } + +int iamf_renderer_enable_loudness_processor(iamf_renderer_t* self, + uint32_t enable) { + if (!self) return IAMF_ERR_BAD_ARG; + + debug("Loudness processor %s in renderer", enable ? "enabled" : "disabled"); + return oar_enable_loudness_processor(self->oar, enable) == ck_oar_ok + ? IAMF_OK + : IAMF_ERR_INTERNAL; +} + +int iamf_renderer_set_loudness(iamf_renderer_t* self, uint32_t group_index, + float loudness, float target_loudness) { + if (!self || !self->oar) return IAMF_ERR_BAD_ARG; + if (group_index >= def_max_sub_mixes || + self->gids[group_index] == def_i32_id_none) { + error("Invalid group index %u for loudness setting", group_index); + return IAMF_ERR_BAD_ARG; + } + + debug("Set loudness for group %u (id=%u): current=%.2f dB, target=%.2f dB", + group_index, self->gids[group_index], loudness, target_loudness); + + return oar_set_loudness(self->oar, self->gids[group_index], loudness, + target_loudness) == ck_oar_ok + ? IAMF_OK + : IAMF_ERR_INTERNAL; +} diff --git a/code/src/iamf_dec/iamf_renderer.h b/code/src/iamf_dec/iamf_renderer.h index 3da7ae37..c152ecf8 100755 --- a/code/src/iamf_dec/iamf_renderer.h +++ b/code/src/iamf_dec/iamf_renderer.h @@ -121,7 +121,7 @@ int iamf_renderer_set_head_rotation(iamf_renderer_t* self, * experiences. * * @param [in] self : Pointer to the renderer instance. - * @param [in] enable : 1 to enable head tracking, 0 to disable. + * @param [in] enable : 1 to enable, 0 to disable. * * @return 0 on success, non-zero error code on failure. * @@ -135,4 +135,36 @@ int iamf_renderer_set_head_rotation(iamf_renderer_t* self, */ int iamf_renderer_enable_head_tracking(iamf_renderer_t* self, uint32_t enable); +/** + * @brief Enable or disable loudness processor. + * + * This function enables or disables the loudness normalization processor in + * OAR. When enabled, OAR will apply group-specific loudness gain during + * rendering to normalize the audio to a target loudness level. + * + * @param [in] self : Pointer to the renderer instance. + * @param [in] enable : 1 to enable, 0 to disable. + * + * @return 0 on success, non-zero error code on failure. + */ +int iamf_renderer_enable_loudness_processor(iamf_renderer_t* self, + uint32_t enable); + +/** + * @brief Set loudness parameters for a specific audio group. + * + * This function sets the current loudness and target loudness for a specific + * audio group in OAR. The loudness processor will use these parameters to + * calculate and apply the appropriate gain during rendering. + * + * @param [in] self : Pointer to the renderer instance. + * @param [in] group_index : Group index (0 or 1) to set loudness for. + * @param [in] loudness : Current loudness in dB (LKFS). + * @param [in] target_loudness : Target loudness in dB (LKFS). + * + * @return 0 on success, non-zero error code on failure. + */ +int iamf_renderer_set_loudness(iamf_renderer_t* self, uint32_t group_index, + float loudness, float target_loudness); + #endif // __IAMF_RENDERER_H__