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]
