This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

The following commit(s) were added to refs/heads/master by this push:
     new 3a8a1a7c6a avcodec/aacpsy: fix the attack lookback
3a8a1a7c6a is described below

commit 3a8a1a7c6a20cb2738bc3721624b7d10d1fec19d
Author:     Lynne <[email protected]>
AuthorDate: Sun Jul 19 23:45:01 2026 +0800
Commit:     Lynne <[email protected]>
CommitDate: Sun Jul 19 23:50:55 2026 +0800

    avcodec/aacpsy: fix the attack lookback
    
    This fixes a 15+ year old bug.
    
    The novelty check read its previous-frame envelope from
    prev_energy_subshort after the sub-block loop had already overwritten
    it with current-frame values, so early sub-blocks were compared
    against the frame's own future instead of the past. That accident
    suppressed short-period pitch trains well enough, but also suppressed
    genuine onsets, and could not see pulse periods beyond ~12ms at all:
    on quiet slow pulse trains (engine-idle buzz and the like) the ratio
    test leaks sporadic isolated short excursions, each an audible click -
    reported against this encoder since 2015, at every bitrate, and
    immune to disabling every coding tool.
    
    Keep a rolling two-frame history of HP sub-block peaks instead and
    require an attack to tower over it: within ~12ms unconditionally
    (pitch-rate trains), and within ~44ms when coming from steady
    long-window state (slow trains, where an isolated short excursion can
    only click). Dense irregular transients tower locally and reset
    frames_since_short, so their block switching is untouched.
---
 libavcodec/aacpsy.c | 64 +++++++++++++++++++++++++++++------------------------
 tests/fate/aac.mak  |  6 ++---
 2 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/libavcodec/aacpsy.c b/libavcodec/aacpsy.c
index e0faa2a19a..04e0c67ba4 100644
--- a/libavcodec/aacpsy.c
+++ b/libavcodec/aacpsy.c
@@ -107,6 +107,11 @@ enum {
 #define PSY_LAME_PE_QUIET  0.4f     ///< pre-onset must be below this fraction 
of the frame peak
 #define PSY_LAME_PE_RED    0.45f    ///< attack-threshold multiplier for a 
qualifying isolated onset
 
+/* The novelty check must see at least one full period of a pulse train to
+ * recognize its pulses as repeats; 30 sub-blocks reaches down to ~23Hz. */
+#define PSY_LAME_HIST      32       ///< HP sub-block peak history depth
+#define PSY_LAME_NOV_BACK  30       ///< novelty look-back in sub-blocks
+
 /**
  * @}
  */
@@ -140,6 +145,7 @@ typedef struct AacPsyChannel{
     /* LAME psy model specific members */
     float attack_threshold;              ///< attack threshold for this channel
     float prev_energy_subshort[AAC_NUM_BLOCKS_SHORT * PSY_LAME_NUM_SUBBLOCKS];
+    float hp_env_hist[PSY_LAME_HIST];    ///< rolling HP sub-block peak 
envelope
     int   prev_attack;                   ///< attack value for the last short 
block in the previous sequence
     int   next_attack0_zero;          ///< whether attack[0] of the next frame 
is zero
     int   frames_since_short;            ///< consecutive long frames 
(pre-echo-aware isolated-onset gate)
@@ -293,6 +299,8 @@ static av_cold void lame_window_init(AacPsyContext *ctx, 
AVCodecContext *avctx)
 
         for (j = 0; j < AAC_NUM_BLOCKS_SHORT * PSY_LAME_NUM_SUBBLOCKS; j++)
             pch->prev_energy_subshort[j] = 10.0f;
+        for (j = 0; j < PSY_LAME_HIST; j++)
+            pch->hp_env_hist[j] = 10.0f;
     }
 }
 
@@ -1001,15 +1009,14 @@ static int psy_lame_detect(AacPsyContext *pctx, 
AacPsyChannel *pch,
             attack_intensity[i + PSY_LAME_NUM_SUBBLOCKS] = p;
         }
 
-        {   /* pre-echo-aware threshold relaxation + periodicity/novelty veto 
(a
-             * pitch-pulse train repeats its peak; a real onset towers) */
+        {   /* pre-echo-aware threshold relaxation + periodicity/novelty check
+             * (a pulse train repeats its peak; a real onset towers) */
             float frame_peak = 1.0f;
-            float eh[8 + (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS];
+            float env[PSY_LAME_HIST + AAC_NUM_BLOCKS_SHORT * 
PSY_LAME_NUM_SUBBLOCKS];
             const float nov_gate = 1.25f;
-            for (i = 0; i < 8; i++)
-                eh[i] = pch->prev_energy_subshort[8 + i];
-            for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * 
PSY_LAME_NUM_SUBBLOCKS; i++)
-                eh[8 + i] = energy_subshort[i];
+            memcpy(env, pch->hp_env_hist, sizeof(pch->hp_env_hist));
+            memcpy(env + PSY_LAME_HIST, energy_subshort + 
PSY_LAME_NUM_SUBBLOCKS,
+                   AAC_NUM_BLOCKS_SHORT * PSY_LAME_NUM_SUBBLOCKS * 
sizeof(*env));
             for (i = PSY_LAME_NUM_SUBBLOCKS; i < (AAC_NUM_BLOCKS_SHORT + 1) * 
PSY_LAME_NUM_SUBBLOCKS; i++)
                 frame_peak = FFMAX(frame_peak, energy_subshort[i]);
             for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * 
PSY_LAME_NUM_SUBBLOCKS; i++)
@@ -1020,11 +1027,21 @@ static int psy_lame_detect(AacPsyContext *pctx, 
AacPsyChannel *pch,
                         energy_subshort[i - PSY_LAME_NUM_SUBBLOCKS] < 
PSY_LAME_PE_QUIET * frame_peak)
                         thr *= PSY_LAME_PE_RED;
                     if (attack_intensity[i] > thr) {
+                        /* An attack must tower over the recent HP envelope:
+                         * within ~12ms always (pitch-rate trains), within
+                         * ~44ms only from steady long-window state (slow
+                         * pulse trains, where an isolated short excursion
+                         * is an audible click). */
                         if (nov_gate > 0.0f && i >= PSY_LAME_NUM_SUBBLOCKS) {
-                            float prevmax = 1.0f;
+                            const int pos = PSY_LAME_HIST + i - 
PSY_LAME_NUM_SUBBLOCKS;
+                            float nearmax = 1.0f, deepmax = 1.0f;
                             for (int k = 3; k <= 8; k++)
-                                prevmax = FFMAX(prevmax, eh[8 + i - k]);
-                            if (energy_subshort[i] < nov_gate * prevmax)
+                                nearmax = FFMAX(nearmax, env[pos - k]);
+                            for (int k = 3; k <= PSY_LAME_NOV_BACK; k++)
+                                deepmax = FFMAX(deepmax, env[pos - k]);
+                            if (energy_subshort[i] < nov_gate * nearmax ||
+                                (energy_subshort[i] < nov_gate * deepmax &&
+                                 pch->frames_since_short >= PSY_LAME_PE_GAP))
                                 continue;    /* periodic, not an onset */
                         }
                         attacks[i / PSY_LAME_NUM_SUBBLOCKS] = (i % 
PSY_LAME_NUM_SUBBLOCKS) + 1;
@@ -1050,25 +1067,14 @@ static int psy_lame_detect(AacPsyContext *pctx, 
AacPsyChannel *pch,
             att_sum += attacks[i];
         }
 
-        {   /* novelty of each attacking sub-block against the trailing HP
-             * max-envelope (~1-2 pitch periods): a periodic pulse train 
repeats
-             * its peak every period (novelty ~1), a genuine onset towers over
-             * the recent past. Instrumentation only. */
-            float eh[8 + (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS];
-            float novmax = 0.0f;
-            for (i = 0; i < 8; i++)
-                eh[i] = pch->prev_energy_subshort[8 + i];
-            for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * 
PSY_LAME_NUM_SUBBLOCKS; i++)
-                eh[8 + i] = energy_subshort[i];
-            for (i = PSY_LAME_NUM_SUBBLOCKS; i < (AAC_NUM_BLOCKS_SHORT + 1) * 
PSY_LAME_NUM_SUBBLOCKS; i++) {
-                if (attacks[i / PSY_LAME_NUM_SUBBLOCKS] == (i % 
PSY_LAME_NUM_SUBBLOCKS) + 1) {
-                    float prevmax = 1.0f;
-                    for (int k = 3; k <= 8; k++)
-                        prevmax = FFMAX(prevmax, eh[8 + i - k]);
-                    novmax = FFMAX(novmax, eh[8 + i] / prevmax);
-                }
-            }
-        }
+        /* roll the HP sub-block peak history */
+        memmove(pch->hp_env_hist,
+                pch->hp_env_hist + AAC_NUM_BLOCKS_SHORT * 
PSY_LAME_NUM_SUBBLOCKS,
+                (PSY_LAME_HIST - AAC_NUM_BLOCKS_SHORT * 
PSY_LAME_NUM_SUBBLOCKS) *
+                sizeof(*pch->hp_env_hist));
+        memcpy(pch->hp_env_hist + PSY_LAME_HIST - AAC_NUM_BLOCKS_SHORT * 
PSY_LAME_NUM_SUBBLOCKS,
+               energy_subshort + PSY_LAME_NUM_SUBBLOCKS,
+               AAC_NUM_BLOCKS_SHORT * PSY_LAME_NUM_SUBBLOCKS * 
sizeof(*pch->hp_env_hist));
 
         if (pch->next_attack0_zero)
             attacks[0] = 0;
diff --git a/tests/fate/aac.mak b/tests/fate/aac.mak
index 0724539c58..cf0c70c46a 100644
--- a/tests/fate/aac.mak
+++ b/tests/fate/aac.mak
@@ -224,7 +224,7 @@ FATE_AAC_ENCODE += fate-aac-ln-encode-128k
 fate-aac-ln-encode-128k: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav -c:a aac 
-aac_coder fast -aac_is 0 -aac_pns 0 -aac_ms 0 -aac_tns 0 -b:a 128k -cutoff 
22050 -fflags +bitexact -flags +bitexact
 fate-aac-ln-encode-128k: CMP = stddev
 fate-aac-ln-encode-128k: REF = 
$(SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav
-fate-aac-ln-encode-128k: CMP_TARGET = 638
+fate-aac-ln-encode-128k: CMP_TARGET = 622
 fate-aac-ln-encode-128k: FUZZ = 10
 
 FATE_AAC_ENCODE += fate-aac-pns-encode
@@ -245,7 +245,7 @@ FATE_AAC_ENCODE += fate-aac-is-encode
 fate-aac-is-encode: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav -c:a aac 
-aac_coder fast -aac_pns 0 -aac_is 1 -aac_ms 0 -b:a 128k -aac_tns 0 -cutoff 
22050 -fflags +bitexact -flags +bitexact
 fate-aac-is-encode: CMP = stddev
 fate-aac-is-encode: REF = 
$(SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav
-fate-aac-is-encode: CMP_TARGET = 517
+fate-aac-is-encode: CMP_TARGET = 500
 fate-aac-is-encode: FUZZ = 10
 
 FATE_AAC_ENCODE += fate-aac-ms-encode
@@ -260,7 +260,7 @@ FATE_AAC_ENCODE += fate-aac-yoraw-encode
 fate-aac-yoraw-encode: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_SAMPLES)/audio-reference/yo.raw-short.wav -c:a aac -aac_coder fast 
-fflags +bitexact -flags +bitexact
 fate-aac-yoraw-encode: CMP = stddev
 fate-aac-yoraw-encode: REF = $(SAMPLES)/audio-reference/yo.raw-short.wav
-fate-aac-yoraw-encode: CMP_TARGET = 54
+fate-aac-yoraw-encode: CMP_TARGET = 226
 fate-aac-yoraw-encode: FUZZ = 17
 
 FATE_AAC_LATM += fate-aac-latm_000000001180bc60

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to