This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit f1b4b5b5f68d86b5828c0da856edc435b16b87ce Author: Lynne <[email protected]> AuthorDate: Fri May 29 04:04:36 2026 +0900 Commit: Lynne <[email protected]> CommitDate: Wed Jun 10 18:04:22 2026 +0900 aacdec_usac: apply volume normalization settings --- libavcodec/aac/aacdec.c | 3 ++ libavcodec/aac/aacdec.h | 14 +++++++++ libavcodec/aac/aacdec_usac.c | 71 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/libavcodec/aac/aacdec.c b/libavcodec/aac/aacdec.c index 7c7151362e..52e3e73ed0 100644 --- a/libavcodec/aac/aacdec.c +++ b/libavcodec/aac/aacdec.c @@ -2648,6 +2648,9 @@ static const AVOption options[] = { { "coded", "order in which the channels are coded in the bitstream", 0, AV_OPT_TYPE_CONST, { .i64 = CHANNEL_ORDER_CODED }, .flags = AACDEC_FLAGS, .unit = "channel_order" }, + { "target_level", "Target output loudness in dBFS for xHE-AAC normalization (0 = disabled)", + OFF(target_level), AV_OPT_TYPE_INT, { .i64 = 0 }, -70, 0, AACDEC_FLAGS }, + {NULL}, }; diff --git a/libavcodec/aac/aacdec.h b/libavcodec/aac/aacdec.h index cf55bbe6c0..80a77289e6 100644 --- a/libavcodec/aac/aacdec.h +++ b/libavcodec/aac/aacdec.h @@ -405,6 +405,13 @@ typedef struct AACUSACConfig { AACUSACLoudnessInfo album_info[64]; uint8_t nb_info; AACUSACLoudnessInfo info[64]; + + /** + * Raw bsMethodValue (μ) of the program/anchor-loudness measurement + * selected for normalization at config time. -1 == none found. + * L_LKFS = -57.75 + 0.25 * input_method_val. + */ + int input_method_val; } loudness; } AACUSACConfig; @@ -568,6 +575,13 @@ struct AACDecContext { enum AACOutputChannelOrder output_channel_order; + /** + * Target output loudness in dBFS, used for xHE-AAC loudness normalization + * based on the parsed loudnessInfoSet() metadata. 0 disables normalization. + */ + int target_level; + int warned_loudness_missing; + OutputConfiguration oc[2]; int warned_num_aac_frames; unsigned warned_71_wide; diff --git a/libavcodec/aac/aacdec_usac.c b/libavcodec/aac/aacdec_usac.c index 3de82e2266..6d48a5746a 100644 --- a/libavcodec/aac/aacdec_usac.c +++ b/libavcodec/aac/aacdec_usac.c @@ -85,6 +85,16 @@ static const enum AVChannel usac_ch_pos_to_av[64] = { [31] = AV_CHAN_TOP_SURROUND_RIGHT, ///< -110 degrees, Rvs, TpRS }; +/* ISO/IEC 23003-4, Table A.48: bit width of bsMethodValue depends on methodDef. */ +static int methodvalue_width(int method_def) +{ + switch (method_def) { + case 7: return 5; /* mixing level */ + case 8: return 2; /* room type */ + default: return 8; /* loudness (0..6, 9) + reserved */ + } +} + static int decode_loudness_info(AACDecContext *ac, AACUSACLoudnessInfo *info, GetBitContext *gb) { @@ -103,7 +113,8 @@ static int decode_loudness_info(AACDecContext *ac, AACUSACLoudnessInfo *info, info->nb_measurements = get_bits(gb, 4); for (int i = 0; i < info->nb_measurements; i++) { info->measurements[i].method_def = get_bits(gb, 4); - info->measurements[i].method_val = get_unary(gb, 0, 8); + info->measurements[i].method_val = + get_bits(gb, methodvalue_width(info->measurements[i].method_def)); info->measurements[i].measurement = get_bits(gb, 4); info->measurements[i].reliability = get_bits(gb, 2); } @@ -111,6 +122,26 @@ static int decode_loudness_info(AACDecContext *ac, AACUSACLoudnessInfo *info, return 0; } +/* Pick the bsMethodValue of a program- or anchor-loudness measurement. + * Per ISO/IEC 23003-4 6.1.2.5, downmixId and drcSetId identify the signal a + * loudnessInfo() applies to; only downmixId == 0 (base layout) together with + * drcSetId == 0 (no DRC) describes the unprocessed signal we output, so + * measurements for any other downmix/DRC set must not be used. */ +static int select_loudness_measurement(const AACUSACConfig *usac) +{ + for (int i = 0; i < usac->loudness.nb_info; i++) { + const AACUSACLoudnessInfo *info = &usac->loudness.info[i]; + if (info->downmix_id != 0 || info->drc_set_id != 0) + continue; + for (int j = 0; j < info->nb_measurements; j++) { + int method = info->measurements[j].method_def; + if (method == 1 || method == 2) + return info->measurements[j].method_val; + } + } + return -1; +} + static int decode_loudness_set(AACDecContext *ac, AACUSACConfig *usac, GetBitContext *gb) { @@ -134,15 +165,15 @@ static int decode_loudness_set(AACDecContext *ac, AACUSACConfig *usac, if (get_bits1(gb)) { /* loudnessInfoSetExtPresent */ enum AACUSACLoudnessExt type; while ((type = get_bits(gb, 4)) != UNIDRCLOUDEXT_TERM) { - uint8_t size_bits = get_bits(gb, 4) + 4; - uint8_t bit_size = get_bits(gb, size_bits) + 1; + uint8_t size_bits = get_bits(gb, 4) + 4; /* bitSizeLen */ + uint32_t bit_size = get_bits_long(gb, size_bits) + 1; /* bitSize */ switch (type) { case UNIDRCLOUDEXT_EQ: avpriv_report_missing_feature(ac->avctx, "loudnessInfoV1"); return AVERROR_PATCHWELCOME; default: - for (int i = 0; i < bit_size; i++) - skip_bits1(gb); + skip_bits_long(gb, bit_size); + break; } } } @@ -497,6 +528,7 @@ int ff_aac_usac_config_decode(AACDecContext *ac, AVCodecContext *avctx, return AVERROR_PATCHWELCOME; memset(usac, 0, sizeof(*usac)); + usac->loudness.input_method_val = -1; freq_idx = get_bits(gb, 5); /* usacSamplingFrequencyIndex */ if (freq_idx == 0x1f) { @@ -696,6 +728,13 @@ int ff_aac_usac_config_decode(AACDecContext *ac, AVCodecContext *avctx, ac->avctx->profile = AV_PROFILE_AAC_USAC; + usac->loudness.input_method_val = select_loudness_measurement(usac); + if (usac->loudness.input_method_val >= 0) + av_log(avctx, AV_LOG_VERBOSE, + "USAC input loudness: %.2f LKFS (bsMethodValue=%d)\n", + -57.75f + 0.25f * usac->loudness.input_method_val, + usac->loudness.input_method_val); + ret = ff_aac_usac_reset_state(ac, oc); if (ret < 0) return ret; @@ -2090,6 +2129,28 @@ int ff_aac_usac_decode_frame(AVCodecContext *avctx, AACDecContext *ac, *got_frame_ptr = 0; } + if (samples && ac->target_level) { + int method_val = usac->loudness.input_method_val; + if (method_val < 0) { + if (!ac->warned_loudness_missing) { + av_log(avctx, AV_LOG_WARNING, + "target_level set but no program/anchor loudness " + "measurement available; normalization skipped\n"); + ac->warned_loudness_missing = 1; + } + } else { + /* Per ISO/IEC 23003-4 Table A.48: L = -57.75 + 0.25 * μ */ + float input_loudness = -57.75f + 0.25f * method_val; + float gain_dB = (float)ac->target_level - input_loudness; + float gain = powf(10.0f, gain_dB / 20.0f); + + for (int ch = 0; ch < frame->ch_layout.nb_channels; ch++) + ac->fdsp->vector_fmul_scalar((float *)frame->extended_data[ch], + (float *)frame->extended_data[ch], + gain, frame->nb_samples); + } + } + /* for dual-mono audio (SCE + SCE) */ is_dmono = ac->dmono_mode && elem_id[0] == 2 && !av_channel_layout_compare(&ac->oc[1].ch_layout, _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
