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

Git pushed a commit to branch master
in repository ffmpeg.

commit c8dc3cf4003cb09f9fc4b2d3dbce2d01c7b0f77e
Author:     Lynne <[email protected]>
AuthorDate: Fri Jul 17 14:19:52 2026 +0800
Commit:     Lynne <[email protected]>
CommitDate: Sun Jul 19 20:41:53 2026 +0800

    avcodec/aacpsy: sync block switching across channel pairs
    
    Implement the window_pair() hook: detect both channels of a CPE, then
    decide together - if either channel attacks, both switch, with one
    merged attack map so the grouping matches across the pair and
    common_window survives transients. Pairs the encoder has flagged as
    decoupled (joint tools dead) keep independent per-channel decisions,
    where forcing the steady channel short at every event in the other
    only costs quality.
    
    Attacks must also be NOVEL against the trailing high-pass max-envelope:
    a steady pitch-pulse train repeats its peak every period and re-fires
    the ratio test forever, producing long false short runs on periodic
    content (low brass and the like); a genuine onset towers over the
    recent past.
    
    The pre-echo threshold relaxation for gentler isolated onsets is kept,
    and START frames entering a short sequence clamp their thresholds
    toward the previous long frame's per-band values on the mapped long
    grid.
---
 libavcodec/aacpsy.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 136 insertions(+), 10 deletions(-)

diff --git a/libavcodec/aacpsy.c b/libavcodec/aacpsy.c
index e38c43a323..e0faa2a19a 100644
--- a/libavcodec/aacpsy.c
+++ b/libavcodec/aacpsy.c
@@ -143,6 +143,9 @@ typedef struct AacPsyChannel{
     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)
+    float prev_frame_energy;             ///< previous frame's full-band 
lookahead energy (attack veto)
+    int64_t win_count;                   ///< window() calls so far (frame 
counter for pair sync)
+    int64_t last_att;                    ///< win_count value of this 
channel's last own attack
 
     /* rate-loop re-analysis rewind state, see psy_3gpp_analyze() */
     int64_t    rc_frame_num;             ///< frame this channel last saved 
rewind state for
@@ -677,6 +680,23 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, 
int channel,
     float pe = pctx->chan_bitrate > 32000 ? 0.0f : FFMAX(50.0f, 100.0f - 
pctx->chan_bitrate * 100.0f / 32000.0f);
     const int      num_bands   = ctx->num_bands[wi->num_windows == 8];
     const uint8_t *band_sizes  = ctx->bands[wi->num_windows == 8];
+    uint8_t s2l[16] = {0};
+    int start_after_long = wi->num_windows == 8 &&
+                           wi->window_type[1] == LONG_START_SEQUENCE;
+    {   /* short->long grid band map for cross-transition pre-echo control */
+        if (start_after_long) {
+            const uint8_t *ls = ctx->bands[0];
+            const int      ln = ctx->num_bands[0];
+            const uint8_t *ss = ctx->bands[1];
+            int lacc = 0, sacc = 0, gl = 0;
+            for (int gs = 0; gs < num_bands && gs < 16; gs++) {
+                int center8 = (sacc + ss[gs] / 2) * 8;
+                while (gl < ln - 1 && lacc + ls[gl] <= center8) { lacc += 
ls[gl]; gl++; }
+                s2l[gs] = gl;
+                sacc += ss[gs];
+            }
+        }
+    }
     AacPsyCoeffs  *coeffs      = pctx->psy_coef[wi->num_windows == 8];
     const float avoid_hole_thr = wi->num_windows == 8 ? PSY_3GPP_AH_THR_SHORT 
: PSY_3GPP_AH_THR_LONG;
     const int bandwidth        = ctx->cutoff ? ctx->cutoff : 
AAC_CUTOFF(ctx->avctx);
@@ -708,6 +728,12 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, 
int channel,
             if (!(wi->window_type[0] == LONG_STOP_SEQUENCE || (!w && 
wi->window_type[1] == LONG_START_SEQUENCE)))
                 band->thr = FFMAX(PSY_3GPP_RPEMIN*band->thr, FFMIN(band->thr,
                                   
PSY_3GPP_RPELEV*pch->prev_band[w+g].thr_quiet));
+            else if (!w && start_after_long)
+                /* w0 after a START frame: grid-mapped, scaled continuity
+                 * clamp instead of the spec's skip (cannot bind on noise
+                 * content - see memory - but correct for tonal) */
+                band->thr = FFMAX(PSY_3GPP_RPEMIN*band->thr, FFMIN(band->thr,
+                                  
PSY_3GPP_RPELEV*pch->prev_band[s2l[FFMIN(g,15)]].thr / 8.0f));
 
             /* 5.6.1.3.1 "Preparatory steps of the perceptual entropy 
calculation" */
             pe += calc_pe_3gpp(band);
@@ -925,16 +951,16 @@ static void lame_apply_block_type(AacPsyChannel *ctx, 
FFPsyWindowInfo *wi, int u
     ctx->next_window_seq = blocktype;
 }
 
-static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio,
-                                       const float *la, int channel, int 
prev_type)
+/* Attack detection half of the LAME window decision: everything up to (and
+ * excluding) the block-type state machine. Fills attacks[] and returns the raw
+ * uselongblock; mutates only the detection history. Split out so a channel
+ * pair can be detected first and DECIDED together (synced block switching). */
+static int psy_lame_detect(AacPsyContext *pctx, AacPsyChannel *pch,
+                           const float *la, int channel, int prev_type,
+                           int attacks[AAC_NUM_BLOCKS_SHORT + 1])
 {
-    AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data;
-    AacPsyChannel *pch  = &pctx->ch[channel];
-    int grouping     = 0;
     int uselongblock = 1;
-    int attacks[AAC_NUM_BLOCKS_SHORT + 1] = { 0 };
     int i;
-    FFPsyWindowInfo wi = { { 0 } };
 
     if (la) {
         float hpfsmpl[AAC_BLOCK_SIZE_LONG];
@@ -975,8 +1001,15 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, 
const float *audio,
             attack_intensity[i + PSY_LAME_NUM_SUBBLOCKS] = p;
         }
 
-        {   /* pre-echo-aware threshold relaxation, see PSY_LAME_PE_* */
+        {   /* pre-echo-aware threshold relaxation + periodicity/novelty veto 
(a
+             * pitch-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];
+            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];
             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++)
@@ -986,8 +1019,16 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, 
const float *audio,
                         pch->frames_since_short >= PSY_LAME_PE_GAP &&
                         energy_subshort[i - PSY_LAME_NUM_SUBBLOCKS] < 
PSY_LAME_PE_QUIET * frame_peak)
                         thr *= PSY_LAME_PE_RED;
-                    if (attack_intensity[i] > thr)
+                    if (attack_intensity[i] > thr) {
+                        if (nov_gate > 0.0f && i >= PSY_LAME_NUM_SUBBLOCKS) {
+                            float prevmax = 1.0f;
+                            for (int k = 3; k <= 8; k++)
+                                prevmax = FFMAX(prevmax, eh[8 + i - k]);
+                            if (energy_subshort[i] < nov_gate * prevmax)
+                                continue;    /* periodic, not an onset */
+                        }
                         attacks[i / PSY_LAME_NUM_SUBBLOCKS] = (i % 
PSY_LAME_NUM_SUBBLOCKS) + 1;
+                    }
                 }
         }
 
@@ -1009,6 +1050,26 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext 
*ctx, const float *audio,
             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);
+                }
+            }
+        }
+
         if (pch->next_attack0_zero)
             attacks[0] = 0;
         pch->next_attack0_zero = !attacks[AAC_NUM_BLOCKS_SHORT];
@@ -1028,11 +1089,26 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext 
*ctx, const float *audio,
                     attacks[i] = 0;
         }
 
-        pch->frames_since_short = uselongblock ? pch->frames_since_short + 1 : 
0;
     } else {
         /* We have no lookahead info, so just use same type as the previous 
sequence. */
         uselongblock = !(prev_type == EIGHT_SHORT_SEQUENCE);
     }
+    return uselongblock;
+}
+
+/* Decision half: the block-type state machine and window/grouping fill,
+ * given the (possibly pair-synced) final uselongblock. */
+static FFPsyWindowInfo psy_lame_apply(AacPsyContext *pctx, AacPsyChannel *pch,
+                                      int uselongblock,
+                                      const int attacks[AAC_NUM_BLOCKS_SHORT + 
1],
+                                      int prev_type, int have_la)
+{
+    int grouping = 0;
+    int i;
+    FFPsyWindowInfo wi = { { 0 } };
+
+    if (have_la)
+        pch->frames_since_short = uselongblock ? pch->frames_since_short + 1 : 
0;
 
     lame_apply_block_type(pch, &wi, uselongblock);
 
@@ -1077,6 +1153,55 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext 
*ctx, const float *audio,
     return wi;
 }
 
+static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio,
+                                       const float *la, int channel, int 
prev_type)
+{
+    AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data;
+    AacPsyChannel *pch  = &pctx->ch[channel];
+    int attacks[AAC_NUM_BLOCKS_SHORT + 1] = { 0 };
+    int uselongblock = psy_lame_detect(pctx, pch, la, channel, prev_type, 
attacks);
+
+    return psy_lame_apply(pctx, pch, uselongblock, attacks, prev_type, !!la);
+}
+
+/* Pair-synced block switching: either channel's attack switches both. */
+static void psy_lame_window_pair(FFPsyContext *ctx,
+                                 const float *audio0, const float *la0,
+                                 const float *audio1, const float *la1,
+                                 int channel0, int channel1,
+                                 int prev_type0, int prev_type1,
+                                 FFPsyWindowInfo wi[2])
+{
+    AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data;
+    AacPsyChannel *pch0 = &pctx->ch[channel0];
+    AacPsyChannel *pch1 = &pctx->ch[channel1];
+    int att0[AAC_NUM_BLOCKS_SHORT + 1] = { 0 };
+    int att1[AAC_NUM_BLOCKS_SHORT + 1] = { 0 };
+    int merged[AAC_NUM_BLOCKS_SHORT + 1];
+    int u0 = psy_lame_detect(pctx, pch0, la0, channel0, prev_type0, att0);
+    int u1 = psy_lame_detect(pctx, pch1, la1, channel1, prev_type1, att1);
+    int u  = u0 && u1;
+
+    if (ctx->pair_decoupled[(channel0 >> 1) & 15]) {
+        /* Joint tools are dead on this pair (encoder-fed state): each channel
+         * windows for ITS transients - divergence costs nothing there, while
+         * union-syncing forces the steady channel short at every event in
+         * the other. Correlated content keeps the sync. */
+        wi[0] = psy_lame_apply(pctx, pch0, u0, att0, prev_type0, !!la0);
+        wi[1] = psy_lame_apply(pctx, pch1, u1, att1, prev_type1, !!la1);
+        return;
+    }
+
+    /* One merged attack map for both channels: the grouping (and with it
+     * common_window) must match across the pair, and the group boundary
+     * should isolate the first attack heard in EITHER channel. */
+    for (int i = 0; i < AAC_NUM_BLOCKS_SHORT + 1; i++)
+        merged[i] = att0[i] ? att0[i] : att1[i];
+
+    wi[0] = psy_lame_apply(pctx, pch0, u, merged, prev_type0, !!la0);
+    wi[1] = psy_lame_apply(pctx, pch1, u, merged, prev_type1, !!la1);
+}
+
 const FFPsyModel ff_aac_psy_model =
 {
     .name    = "3GPP TS 26.403-inspired model",
@@ -1084,4 +1209,5 @@ const FFPsyModel ff_aac_psy_model =
     .window  = psy_lame_window,
     .analyze = psy_3gpp_analyze,
     .end     = psy_3gpp_end,
+    .window_pair = psy_lame_window_pair,
 };

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

Reply via email to