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]