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

Git pushed a commit to branch master
in repository ffmpeg.

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

    avcodec/aacenc_tns: enable short-block TNS via pooled per-group filters
    
    Per-window filters combined with group-shared scalefactors made
    short-block TNS produce decoder-synthesized silence: a weak residual in
    one sub-window rounds to zero under the group's scalefactor and the
    synthesis filter has nothing to re-amplify, audible as 1.5-3 ms
    dropouts on beat content. Fit ONE filter per window group instead, on
    the concatenated weighted spectra, and require every window in the
    group to pass the prediction-gain bar.
    
    Accepts are Schmitt-gated and run-scoped: within a short run the enter
    bar is raised and the hold bar lowered (accept flicker was audible as
    gravel), while isolated transient frames - where TNS matters most and
    cannot flicker - use the base bar.
---
 libavcodec/aacenc_tns.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 143 insertions(+), 2 deletions(-)

diff --git a/libavcodec/aacenc_tns.c b/libavcodec/aacenc_tns.c
index c1308b1ebf..e7cbd60161 100644
--- a/libavcodec/aacenc_tns.c
+++ b/libavcodec/aacenc_tns.c
@@ -27,6 +27,7 @@
 
 #include "libavutil/libm.h"
 #include "aacenc.h"
+#include <float.h>
 #include "aacenc_tns.h"
 #include "aactab.h"
 #include "aacenc_utils.h"
@@ -123,6 +124,7 @@ void ff_aac_apply_tns(AACEncContext *s, 
SingleChannelElement *sce)
     for (w = 0; w < ics->num_windows; w++) {
         bottom = ics->num_swb;
         for (filt = 0; filt < tns->n_filt[w]; filt++) {
+            int b0, e0;
             top    = bottom;
             bottom = FFMAX(0, top - tns->length[w][filt]);
             order  = tns->order[w][filt];
@@ -132,8 +134,10 @@ void ff_aac_apply_tns(AACEncContext *s, 
SingleChannelElement *sce)
             // tns_decode_coef
             compute_lpc_coefs(tns->coef[w][filt], 0, order, lpc, 0, 0, 0, 
NULL);
 
-            start = ics->swb_offset[FFMIN(bottom, mmm)];
-            end   = ics->swb_offset[FFMIN(   top, mmm)];
+            b0 = FFMIN(bottom, mmm);
+            e0 = FFMIN(   top, mmm);
+            start = ics->swb_offset[b0];
+            end   = ics->swb_offset[e0];
             if ((size = end - start) <= 0)
                 continue;
             if (tns->direction[w][filt]) {
@@ -150,6 +154,7 @@ void ff_aac_apply_tns(AACEncContext *s, 
SingleChannelElement *sce)
                     sce->coeffs[start] += lpc[i-1]*hist[start - i*inc];
                 }
             }
+
         }
     }
 }
@@ -171,6 +176,108 @@ static inline void quantize_coefs(double *coef, int *idx, 
float *lpc, int order,
 /*
  * 3 bits per coefficient with 8 short windows
  */
+/* Short blocks, pooled per group: one filter per scalefactor group so the
+ * shared sf sees uniform residuals (per-window filters caused silent
+ * sub-windows); all-or-none accept. */
+static void search_for_tns_short_pooled(AACEncContext *s, SingleChannelElement 
*sce)
+{
+    TemporalNoiseShaping *tns = &sce->tns;
+    const int mmm = tns_max_nonpns(sce, FFMIN(sce->ics.tns_max_bands, 
sce->ics.max_sfb ? sce->ics.max_sfb : sce->ics.num_swb));
+    const int sfb_start = av_clip(tns_min_sfb[1][s->samplerate_index], 0, mmm);
+    const int sfb_end   = av_clip(sce->ics.num_swb, 0, mmm);
+    const int c_bits = TNS_Q_BITS_IS8 == 4;
+    int count = 0;
+    FFPsyBand *const psy_bands = &s->psy.ch[s->cur_channel].psy_bands[0];
+
+    memset(tns, 0, sizeof(*tns));
+    if (sfb_end - sfb_start <= 0)
+        return;
+    const int c_lo = sce->ics.swb_offset[sfb_start];
+    const int c_hi = sce->ics.swb_offset[sfb_end];
+    const int clen = c_hi - c_lo;
+    const int ord_g = 7;
+    if (clen <= 2*ord_g)
+        return;
+
+    for (int wh = 0; wh < sce->ics.num_windows; wh += sce->ics.group_len[wh]) {
+        int gl = sce->ics.group_len[wh];
+        double coefs[MAX_LPC_ORDER];
+        float pooled[1024], lpc_q[TNS_MAX_ORDER];
+        float gain, gmin;
+        int ok = 1;
+
+        /* per-window weighted spectra, concatenated over the group */
+        for (int w2 = 0; w2 < gl; w2++) {
+            int w = wh + w2;
+            float maxrms = 0.0f, floorrms;
+            for (int g = sfb_start; g < sfb_end; g++) {
+                int s0 = sce->ics.swb_offset[g], s1 = sce->ics.swb_offset[g+1];
+                float rms = sqrtf(FFMAX(psy_bands[w*16 + g].threshold, 0.0f) / 
FFMAX(s1 - s0, 1));
+                maxrms = FFMAX(maxrms, rms);
+            }
+            floorrms = FFMAX(maxrms * TNS_WEIGHT_FLOOR, 1e-9f);
+            for (int g = sfb_start; g < sfb_end; g++) {
+                int s0 = sce->ics.swb_offset[g], s1 = sce->ics.swb_offset[g+1];
+                float rms = sqrtf(FFMAX(psy_bands[w*16 + g].threshold, 0.0f) / 
FFMAX(s1 - s0, 1));
+                float wgt = 1.0f / FFMAX(rms, floorrms);
+                for (int k = s0; k < s1; k++)
+                    pooled[w2*clen + (k - c_lo)] = sce->coeffs[w*128 + k] * 
wgt;
+            }
+        }
+
+        gain = ff_lpc_calc_ref_coefs_f(&s->lpc, pooled, clen*gl, ord_g, coefs, 
0);
+        if (!isfinite(gain) || gain < TNS_PREDGAIN_GATE || gain > TNS_PG_CLAMP)
+            continue;
+        for (int i = 0; i < ord_g; i++)
+            coefs[i] = -coefs[i];
+
+        quantize_coefs(coefs, tns->coef_idx[wh][0], tns->coef[wh][0], ord_g, 
c_bits);
+        compute_lpc_coefs(tns->coef[wh][0], 0, ord_g, lpc_q, 0, 0, 0, NULL);
+
+        /* every window must clear the measured post-quantization bar */
+        gmin = FLT_MAX;
+        for (int w2 = 0; w2 < gl; w2++) {
+            const float *msrc = pooled + w2*clen;
+            float orig_e = 0.0f, filt_e = 0.0f;
+            for (int m = 0; m < clen; m++) {
+                float acc = msrc[m];
+                for (int i = 1; i <= FFMIN(m, ord_g); i++)
+                    acc += lpc_q[i-1] * msrc[m - i];
+                orig_e += msrc[m]*msrc[m];
+                filt_e += acc*acc;
+            }
+            gmin = FFMIN(gmin, orig_e / FFMAX(filt_e, 1e-9f));
+        }
+        {
+            /* accept Schmitt, run-scoped: hard entry / easy hold inside
+             * short runs (anti-gravel); isolated frames use the base bar */
+            int in_run  = s->nmr ? s->nmr->prev_was_short : 0;
+            int prev_on = s->nmr ? s->nmr->tns8_prev[s->cur_channel & 15] : 0;
+            float bar = TNS_PG_C1_SHORT * (!in_run ? 1.0f : prev_on ? 0.5f : 
1.8f);
+            if (gmin < bar)
+                ok = 0;
+        }
+
+        if (ok) {
+            for (int w2 = 0; w2 < gl; w2++) {
+                int w = wh + w2;
+                tns->n_filt[w] = 1;
+                tns->length[w][0] = sfb_end - sfb_start;
+                tns->order[w][0] = ord_g;
+                tns->direction[w][0] = 0;
+                if (w2) {
+                    memcpy(tns->coef_idx[w][0], tns->coef_idx[wh][0], 
sizeof(tns->coef_idx[w][0]));
+                    memcpy(tns->coef[w][0],     tns->coef[wh][0],     
sizeof(tns->coef[w][0]));
+                }
+                count++;
+            }
+        }
+    }
+    sce->tns.present = !!count;
+    if (s->nmr)
+        s->nmr->tns8_prev[s->cur_channel & 15] = !!count;
+}
+
 void ff_aac_search_for_tns(AACEncContext *s, SingleChannelElement *sce)
 {
     TemporalNoiseShaping *tns = &sce->tns;
@@ -197,11 +304,17 @@ void ff_aac_search_for_tns(AACEncContext *s, 
SingleChannelElement *sce)
         sce->tns.present = 0;
         return;
     }
+    if (is8) {
+        search_for_tns_short_pooled(s, sce);
+        return;
+    }
 
     /* time-domain window length backing one coding window: a long MDCT block 
is
      * fed 2048 windowed samples (current 1024 + overlap), each short block 
256. */
     const int tlen = is8 ? 256 : 2048;
 
+    float mgain[8] = {0};
+
     for (w = 0; w < sce->ics.num_windows; w++) {
         int filt, any = 0;
 
@@ -302,11 +415,39 @@ void ff_aac_search_for_tns(AACEncContext *s, 
SingleChannelElement *sce)
 
             tns->order[w][filt] = ord_g;
             tns->direction[w][filt] = dir;
+            mgain[w] = orig_e / filt_e;
             any = 1;
         }
         tns->n_filt[w] = any ? n_filt : 0;
         if (any)
             count++;
     }
+
+    /* per-window path: group-uniformity gate (mismatched whitening within a
+     * shared-sf group silences sub-windows) */
+    if (is8 && count) {
+        const float gspread = 2.0f;
+        count = 0;
+        for (w = 0; w < sce->ics.num_windows; w += sce->ics.group_len[w]) {
+            int gl = sce->ics.group_len[w], drop = 0;
+            float gmin = FLT_MAX, gmax = 0.0f;
+            for (int w2 = w; w2 < w + gl; w2++) {
+                if (!tns->n_filt[w2] || mgain[w2] <= 0.0f) { drop = 1; break; }
+                gmin = FFMIN(gmin, mgain[w2]);
+                gmax = FFMAX(gmax, mgain[w2]);
+            }
+            if (!drop && gmax > gspread * gmin)
+                drop = 1;
+            for (int w2 = w; w2 < w + gl; w2++) {
+                if (drop) {
+                    tns->n_filt[w2] = 0;
+                    for (int f2 = 0; f2 < n_filt; f2++)
+                        tns->order[w2][f2] = 0;
+                } else if (tns->n_filt[w2]) {
+                    count++;
+                }
+            }
+        }
+    }
     sce->tns.present = !!count;
 }

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

Reply via email to