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]

Reply via email to