PR #25585 opened by ronag
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25585
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25585.patch

Fixes #25580.

The signal-relative mask ceiling can lower a near-silent band's threshold below 
the existing threshold in quiet. That band becomes codeable, but its 
scalefactor candidates cannot reach the following onset band. The NMR trellis 
has no legal path and the outer hard-overflow retry can loop forever.

- **Confidence: reproduced.** The new psychoacoustic regression fails with 
unpatched upstream: energy `3.2e-15`, threshold `8.03804e-16`. The onset encode 
reproducer is taken directly from #25580.
- **Severity: high.** Encoding can consume a core indefinitely without 
completing a frame.
- **Likelihood: possible.** Default native AAC options suffice; the input needs 
near-silent energy immediately before an onset.

### Changes

- Preserve the absolute hearing floor under the signal-relative mask ceiling, 
retaining the existing bitrate-demand shaping and the ceiling for audible bands.
- Repair infeasible scalefactor chains and enforce decoder-buffer caps in both 
initial and bootstrapped CBR frames.
- Bound hard-overflow and strict-rate retries to 16 retries, restore both 
channels of each CPE before retrying, and reset lambda after an oversized-frame 
fallback.
- Add `fate-aacpsy` for quiet/audible bands in CBR, VBR and ABR with long/short 
windows, plus three sample-free encode regressions.

AAC output can change where the old mask ceiling forced bands below the hearing 
floor. Bit-exactness with unpatched upstream is not claimed.

### Validation

Head: `3764bc009ee9c328ada7167502f3d390d5ddbfb7`; upstream base: 
`2602f39f55468b3c5ca9a2bf40f86ef8bcefad98`.

- Native arm64: build, `fate-aacpsy`, `fate-aac-nmr-chain-encode`, 
`fate-aac-nmr-onset-encode`, `fate-aac-strict-rate-encode`, `fate-source`, and 
`fate-checkasm-aacencdsp` pass.
- 22 supported encode/decode cases pass on each platform, covering the upstream 
reproducer, strict-rate silence with all three coders, CBR/ABR/VBR, and 
stereo/5.1/7.1 layouts. All outputs decode with `-xerror`, without a 
decoder-buffer warning.
- Linux amd64, GCC 14.2.0 and NASM 2.16.03, x86 SIMD enabled, isolated 
container limited to 4 CPUs and 4 GiB: all 13 AAC encode FATE targets, 
`fate-aacpsy`, and `fate-checkasm-aacencdsp` pass, including the sample-backed 
legacy and NMR VBR/ABR distortion checks.
- `git diff --check` passes.

No new external FATE samples are required. A perceptual listening corpus was 
not run.



>From 3764bc009ee9c328ada7167502f3d390d5ddbfb7 Mon Sep 17 00:00:00 2001
From: ronag <[email protected]>
Date: Thu, 29 Oct 2026 13:51:59 +0000
Subject: [PATCH] avcodec/aacenc: fix near-silent onset hangs and bound retries

Preserve the hearing floor under the signal-relative mask ceiling, repair 
infeasible scalefactor chains, enforce decoder-buffer caps, and bound 
hard-overflow and strict-rate retries. Restore both channels of each CPE before 
retrying and reset the reference lambda after an oversized-frame fallback.

Add psychoacoustic and encode regressions covering long/short windows, 
CBR/VBR/ABR, the upstream onset reproducer, and unattainable strict rates.

Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25580
Reported-by: Niklas Haas <[email protected]>
---
 libavcodec/Makefile       |   1 +
 libavcodec/aaccoder_nmr.h | 147 ++++++++++++++++++++++++++++++--------
 libavcodec/aacenc.c       |  45 +++++++-----
 libavcodec/aacpsy.c       |   5 +-
 libavcodec/tests/aacpsy.c |  99 +++++++++++++++++++++++++
 tests/fate/aac.mak        |  20 +++++-
 tests/fate/libavcodec.mak |   5 ++
 7 files changed, 272 insertions(+), 50 deletions(-)
 create mode 100644 libavcodec/tests/aacpsy.c

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 2ea4f9417e6..eb06eb4bc4d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1392,6 +1392,7 @@ TESTPROGS = avcodec                                       
              \
             mathops                                                    \
             packet_side_data_handle                                     \
 
+TESTPROGS-$(CONFIG_AAC_ENCODER)           += aacpsy
 TESTPROGS-$(CONFIG_AC3DSP)                += ac3
 TESTPROGS-$(CONFIG_APV_DECODER)           += apv
 TESTPROGS-$(CONFIG_AV1_VAAPI_ENCODER)     += av1_levels
diff --git a/libavcodec/aaccoder_nmr.h b/libavcodec/aaccoder_nmr.h
index fd7d6e1cbcd..11eb56ec687 100644
--- a/libavcodec/aaccoder_nmr.h
+++ b/libavcodec/aaccoder_nmr.h
@@ -561,6 +561,67 @@ static int nmr_slot_bits(const NMRSlot *t, const int 
(*nb)[NMR_NCAND], int step)
     return tot;
 }
 
+/* Zero a coded band for legality, subwindow flags included (the group flag is
+ * re-derived by ANDing). PNS skips bands with zeroes[] set, so it stays out. 
*/
+static void nmr_drop_band(NMRSlot *t, int b)
+{
+    SingleChannelElement *sce = t->sce;
+    for (int w2 = 0; w2 < sce->ics.group_len[t->bw[b]]; w2++)
+        sce->zeroes[(t->bw[b] + w2)*16 + t->bg[b]] = 1;
+}
+
+/* Keep act[] chain-legal on the current candidate grid. A band's candidates
+ * only span [minsf, the sf at which it quantizes to zero], so a near-silent
+ * band that is codeable only around sf 0 next to a loud band whose minsf is
+ * more than SCALE_MAX_DIFF away leaves the trellis no legal path. It then
+ * fills every later band with FLT_MAX and backtracks to their finest
+ * candidates at any lambda, the commit clamps the loud bands below their
+ * minsf, and the oversized frame traps the outer re-encode loop forever.
+ * Drop the quieter band of each such pair. A no-op on a feasible chain. */
+static void nmr_chain_repair(NMRSlot *t, int step)
+{
+    int keep[128], rlo[128], rhi[128], n = 0;
+
+    for (int k = 0; k < t->nact; k++) {
+        const int b  = t->act[k];
+        const int lo = t->blo[b], hi = lo + (t->bnc[b] - 1) * step;
+        int clo = lo, chi = hi, drop = 0;
+        while (n) {
+            /* b's candidates within reach of the previous kept band's */
+            const int p = keep[n - 1];
+            int rl = FFMAX(lo, rlo[n - 1] - SCALE_MAX_DIFF);
+            int rh = FFMIN(hi, rhi[n - 1] + SCALE_MAX_DIFF);
+            if (rh >= lo) {
+                rl = lo + (rl - lo + step - 1) / step * step;
+                rh = lo + (rh - lo) / step * step;
+                if (rl <= rh) {
+                    clo = rl;
+                    chi = rh;
+                    break;
+                }
+            }
+            if (t->maxvals[t->bidx[p]] > t->maxvals[t->bidx[b]]) {
+                drop = 1;
+                break;
+            }
+            nmr_drop_band(t, p);
+            n--;
+        }
+        if (drop) {
+            nmr_drop_band(t, b);
+            continue;
+        }
+        keep[n] = b;
+        rlo[n]  = clo;
+        rhi[n]  = chi;
+        n++;
+    }
+    if (n != t->nact) {
+        memcpy(t->act, keep, n * sizeof(t->act[0]));
+        t->nact = n;
+    }
+}
+
 /* Run every slot's trellis at one fixed lambda; returns the pooled bits. */
 static int nmr_eval_slots(AACEncContext *s, NMRSlot *const *sl, int nsl, int 
step, float lam)
 {
@@ -569,6 +630,7 @@ static int nmr_eval_slots(AACEncContext *s, NMRSlot *const 
*sl, int nsl, int ste
         NMRSlot *t = sl[k];
         if (!t->nact)
             continue;
+        nmr_chain_repair(t, step);
         nmr_solve(s, s->nmr->nd[t->si], s->nmr->nb[t->si], t->blo, t->bnc, 
step,
                   t->act, t->nact, 0, t->chosen, lam, lam, 1);
         total += nmr_slot_bits(t, s->nmr->nb[t->si], step);
@@ -576,6 +638,36 @@ static int nmr_eval_slots(AACEncContext *s, NMRSlot *const 
*sl, int nsl, int ste
     return total;
 }
 
+/* Dense short frames at high rates can exceed the decoder buffer even at the
+ * coarsest candidate grid; an illegal frame traps the outer re-encode loop
+ * forever. Shed the highest bands until the pooled bits fit cap. act[] runs
+ * window group by window group, so pick the highest band across all groups;
+ * the first band anchors the scalefactor chain and stays. Returns the bits. */
+static int nmr_shed_to_cap(AACEncContext *s, NMRSlot *const *sl, int nsl,
+                           float lam, int tot, int cap)
+{
+    while (tot > cap) {
+        int dropped = 0;
+        for (int k = 0; k < nsl; k++) {
+            NMRSlot *t = sl[k];
+            int hi = 1;
+            if (t->nact <= 1)
+                continue;
+            for (int b_ = 2; b_ < t->nact; b_++)
+                if (t->bg[t->act[b_]] >= t->bg[t->act[hi]])
+                    hi = b_;
+            nmr_drop_band(t, t->act[hi]);
+            memmove(&t->act[hi], &t->act[hi + 1], (t->nact - hi - 1) * 
sizeof(t->act[0]));
+            t->nact--;
+            dropped = 1;
+        }
+        if (!dropped)
+            break;
+        tot = nmr_eval_slots(s, sl, nsl, NMR_STEP, lam);
+    }
+    return tot;
+}
+
 /* Bisect ONE shared lambda across the slots so the POOLED bits meet destbits.
  * This is the CPE budget pool: bits flow to whichever channel of the pair has
  * demand at the common operating point, instead of an equal per-channel 
split. */
@@ -597,6 +689,25 @@ static float nmr_solve_slots(AACEncContext *s, NMRSlot 
*const *sl, int nsl, int
     return lam;
 }
 
+/* Decoder-buffer legality is absolute, as on the VBR path: a frame over the
+ * hard cap is re-solved toward it up to the coarsest lambda, and shed if
+ * that still does not fit. Returns the operating lambda. */
+static float nmr_fit_hardcap(AACEncContext *s, NMRSlot *const *sl, int nsl,
+                             float lam, int hardcap)
+{
+    int tot = 0;
+    for (int k = 0; k < nsl; k++)
+        tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
+    if (tot > hardcap) {
+        lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, hardcap, lam, 1e4f, 
NMR_RC_ITERS);
+        tot = 0;
+        for (int k = 0; k < nsl; k++)
+            tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
+        nmr_shed_to_cap(s, sl, nsl, lam, tot, hardcap);
+    }
+    return lam;
+}
+
 /* Mean log2 achieved dist/real-mask over the active bands of all slots at
  * their current chosen[] - average dB-distance from the mask (Brandenburg
  * mean-NMR). Log compression keeps unavoidably-loud bands from swamping the
@@ -1090,35 +1201,7 @@ static void nmr_solve_group(AVCodecContext *avctx, 
AACEncContext *s,
             tot = 0;
             for (int k = 0; k < nsl; k++)
                 tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
-            /* dense short frames at high rates can exceed the decoder
-             * buffer even at the coarsest candidate grid; an illegal frame
-             * traps the outer re-encode loop forever. Shed the highest
-             * bands until the frame fits. act[] runs window group by window
-             * group, so pick the highest band across all groups; the first
-             * band anchors the scalefactor chain and stays. */
-            while (tot > hardcap) {
-                int dropped = 0;
-                for (int k = 0; k < nsl; k++) {
-                    NMRSlot *t = sl[k];
-                    SingleChannelElement *sce = t->sce;
-                    int hi = 1, b, w0, g;
-                    if (t->nact <= 1)
-                        continue;
-                    for (int b_ = 2; b_ < t->nact; b_++)
-                        if (t->bg[t->act[b_]] >= t->bg[t->act[hi]])
-                            hi = b_;
-                    b  = t->act[hi];
-                    w0 = t->bw[b]; g = t->bg[b];
-                    for (int w2 = 0; w2 < sce->ics.group_len[w0]; w2++)
-                        sce->zeroes[(w0+w2)*16+g] = 1;
-                    memmove(&t->act[hi], &t->act[hi + 1], (t->nact - hi - 1) * 
sizeof(t->act[0]));
-                    t->nact--;
-                    dropped = 1;
-                }
-                if (!dropped)
-                    break;
-                tot = nmr_eval_slots(s, sl, nsl, NMR_STEP, lam);
-            }
+            nmr_shed_to_cap(s, sl, nsl, lam, tot, hardcap);
         }
     }
 
@@ -1165,11 +1248,15 @@ static void nmr_solve_group(AVCodecContext *avctx, 
AACEncContext *s,
                 }
             }
         }
+        /* the cap solves above can rail at the coarsest lambda and still
+         * not fit */
+        lam = nmr_fit_hardcap(s, sl, nsl, lam, hardcap);
         s->nmr->lam_slew = lam;
     } else if (rc_eligible && !vbr) {
         /* corridor not yet bootstrapped: the same bucket-full floor, so a
          * fade-in bootstraps lam_rc at a spending operating point instead
          * of memorizing the intro's starvation lambda */
+        int hardcap = av_clip((int)(5800.f * FFMIN(1.f, lambda / 120.f)), 256, 
5800) * chans;
         int headroom = rc_bmax - av_clip(s->nmr->rc_fill, -rc_bmax, rc_bmax);
         int fbits = (rc_rate_frame - headroom) * chans / s->channels;
         int tot = 0;
@@ -1177,6 +1264,8 @@ static void nmr_solve_group(AVCodecContext *avctx, 
AACEncContext *s,
             tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
         if (tot < fbits)
             lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, fbits, lam / 64.0f, 
lam, NMR_RC_ITERS);
+        /* the hard cap applies before the servo bootstraps, too */
+        lam = nmr_fit_hardcap(s, sl, nsl, lam, hardcap);
     }
 
     if (!vbr || vbr_subst)
diff --git a/libavcodec/aacenc.c b/libavcodec/aacenc.c
index 5b1e3eff0fb..5d4b374e7a9 100644
--- a/libavcodec/aacenc.c
+++ b/libavcodec/aacenc.c
@@ -1426,6 +1426,17 @@ static int aac_encode_frame(AVCodecContext *avctx, 
AVPacket *avpkt,
         return ret;
     frame_bits = its = 0;
     do {
+        if (its && ((avctx->flags & AV_CODEC_FLAG_QSCALE) ||
+                    is_mode || ms_mode || tns_mode || pred_mode)) {
+            /* Every retry starts from the original coefficients, including
+             * strict-rate retries and both channels of each CPE. */
+            for (i = 0; i < s->chan_map[0]; i++) {
+                chans = s->chan_map[i + 1] == TYPE_CPE ? 2 : 1;
+                for (ch = 0; ch < chans; ch++)
+                    memcpy(s->cpe[i].ch[ch].coeffs, s->cpe[i].ch[ch].pcoeffs,
+                           sizeof(s->cpe[i].ch[ch].coeffs));
+            }
+        }
         init_put_bits(&s->pb, avpkt->data, avpkt->size);
 
         if ((avctx->frame_num & 0xFF)==1 && !(avctx->flags & 
AV_CODEC_FLAG_BITEXACT))
@@ -1577,18 +1588,24 @@ static int aac_encode_frame(AVCodecContext *avctx, 
AVPacket *avpkt,
             if (frame_bits < 6144 * s->channels - 3 || its >= 16)
                 break;
             s->lambda *= FFMIN(0.9f, (6144.0f * s->channels - 3) / frame_bits);
-            for (i = 0; i < s->chan_map[0]; i++) {
-                chans = s->chan_map[i + 1] == TYPE_CPE ? 2 : 1;
-                for (ch = 0; ch < chans; ch++)
-                    memcpy(s->cpe[i].ch[ch].coeffs, s->cpe[i].ch[ch].pcoeffs,
-                           sizeof(s->cpe[i].ch[ch].coeffs));
-            }
             its++;
             continue;
         }
 
         frame_bits = put_bits_count(&s->pb);
 
+        /* A coder that cannot get under the decoder buffer would otherwise
+         * re-encode this frame forever: emit it oversized instead. The
+         * retries drove lambda to its floor; don't carry that into the next
+         * frame (the NMR coder never raises it again, and would hold every
+         * later frame to its smallest legality cap). */
+        if (its >= 16 && frame_bits >= 6144 * s->channels - 3) {
+            av_log(avctx, AV_LOG_WARNING, "Frame of %d bits exceeds the 
decoder buffer\n",
+                   frame_bits);
+            s->lambda = avctx->global_quality > 0 ? avctx->global_quality : 
120;
+            break;
+        }
+
         /* The NMR coder rate-controls itself (global-lambda reservoir servo):
          * per-frame bits intentionally float around the nominal rate, so skip
          * the lambda rate loop and only intervene on a hard overflow. */
@@ -1608,13 +1625,12 @@ static int aac_encode_frame(AVCodecContext *avctx, 
AVPacket *avpkt,
 
         /* When strict bit-rate control is demanded */
         if (avctx->bit_rate_tolerance == 0) {
-            if (rate_bits < frame_bits) {
+            if (rate_bits < frame_bits && its < 16) {
                 float ratio = ((float)rate_bits) / frame_bits;
                 s->lambda *= FFMIN(0.9f, ratio);
+                its++;
                 continue;
             }
-            /* reset lambda when solution is found */
-            s->lambda = avctx->global_quality > 0 ? avctx->global_quality : 
120;
             break;
         }
 
@@ -1647,22 +1663,13 @@ static int aac_encode_frame(AVCodecContext *avctx, 
AVPacket *avpkt,
             if (ratio > 0.9f && ratio < 1.1f) {
                 break;
             } else {
-                if (is_mode || ms_mode || tns_mode || pred_mode) {
-                    for (i = 0; i < s->chan_map[0]; i++) {
-                        // Must restore coeffs
-                        chans = tag == TYPE_CPE ? 2 : 1;
-                        cpe = &s->cpe[i];
-                        for (ch = 0; ch < chans; ch++)
-                            memcpy(cpe->ch[ch].coeffs, cpe->ch[ch].pcoeffs, 
sizeof(cpe->ch[ch].coeffs));
-                    }
-                }
                 its++;
             }
         } else {
             break;
         }
     } while (1);
-    if (avctx->flags & AV_CODEC_FLAG_QSCALE)
+    if ((avctx->flags & AV_CODEC_FLAG_QSCALE) || avctx->bit_rate_tolerance == 
0)
         s->lambda = avctx->global_quality > 0 ? avctx->global_quality : 120;
 
     /* tool-usage stats over the final per-band decisions of this frame */
diff --git a/libavcodec/aacpsy.c b/libavcodec/aacpsy.c
index e730e621b97..0328679df1c 100644
--- a/libavcodec/aacpsy.c
+++ b/libavcodec/aacpsy.c
@@ -965,8 +965,11 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, 
int channel,
             int start = 0;
             for (g = 0; g < num_bands; g++) {
                 AacPsyBand *band = &pch->band[w+g];
+                /* Do not let the signal-relative ceiling override the
+                 * threshold in quiet and make inaudible bands codeable. */
                 if (start * l2f >= knee)
-                    band->thr = FFMIN(band->thr, band->energy * lo);
+                    band->thr = FFMIN(band->thr, FFMAX(coeffs[g].ath,
+                                                     band->energy * lo));
                 start += band_sizes[g];
             }
         }
diff --git a/libavcodec/tests/aacpsy.c b/libavcodec/tests/aacpsy.c
new file mode 100644
index 00000000000..31ea758ff92
--- /dev/null
+++ b/libavcodec/tests/aacpsy.c
@@ -0,0 +1,99 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <math.h>
+#include <stdio.h>
+
+#include "libavcodec/aac.h"
+#include "libavcodec/aactab.h"
+#include "libavcodec/avcodec.h"
+#include "libavcodec/psymodel.h"
+
+static int check_mask(int mode, int short_windows, int quiet)
+{
+    AVCodecContext avctx = {
+        .codec_id    = AV_CODEC_ID_AAC,
+        .sample_rate = 48000,
+        .bit_rate    = 128000,
+        .cutoff      = 20000,
+        .ch_layout   = AV_CHANNEL_LAYOUT_MONO,
+        .flags       = mode == 1 ? AV_CODEC_FLAG_QSCALE : 0,
+    };
+    FFPsyContext psy = { 0 };
+    FFPsyWindowInfo wi = {
+        .num_windows = short_windows ? 8 : 1,
+        .window_type = { short_windows ? EIGHT_SHORT_SEQUENCE : 
ONLY_LONG_SEQUENCE },
+    };
+    const uint16_t *offsets[] = { ff_swb_offset_1024[3], ff_swb_offset_128[3] 
};
+    const int num_bands[] = { ff_aac_num_swb_1024[3], ff_aac_num_swb_128[3] };
+    uint8_t sizes[2][64], group_map[] = { 0 };
+    const uint8_t *bands[] = { sizes[0], sizes[1] };
+    float coeffs[1024];
+    const float *channels[] = { coeffs };
+    int checked = 0, ret = 0;
+
+    for (int i = 0; i < 2; i++)
+        for (int g = 0; g < num_bands[i]; g++)
+            sizes[i][g] = offsets[i][g + 1] - offsets[i][g];
+    for (int i = 0; i < 1024; i++)
+        coeffs[i] = quiet ? 1e-8f : 1e4f;
+
+    if (ff_psy_init(&psy, &avctx, 2, bands, num_bands, 1, group_map, 20000) < 
0) {
+        ret = 1;
+        goto end;
+    }
+    psy.unbounded_pe = mode == 2;
+    psy.model->analyze(&psy, 0, channels, &wi);
+
+    for (int w = 0; w < wi.num_windows; w++) {
+        for (int g = 0; g < num_bands[short_windows]; g++) {
+            const FFPsyBand *band = &psy.ch[0].psy_bands[w * 16 + g];
+            float freq = offsets[short_windows][g] * 24000.0f /
+                         (short_windows ? 128 : 1024);
+            float ceiling = band->energy * (mode ? 0.1f : 0.251189f);
+
+            if (freq < 8000 || freq >= 20000 || band->energy <= 0)
+                continue;
+            checked++;
+            /* Quiet HF bands stay masked; audible bands retain the
+             * signal-relative ceiling in CBR, VBR and ABR modes. */
+            if (!isfinite(band->threshold) ||
+                (quiet ? band->threshold < band->energy :
+                         band->threshold > ceiling * 1.00001f)) {
+                fprintf(stderr, "mode %d, short %d, quiet %d, band %d: "
+                        "energy %g, threshold %g\n", mode, short_windows,
+                        quiet, g, band->energy, band->threshold);
+                ret = 1;
+                goto end;
+            }
+        }
+    }
+end:
+    ff_psy_end(&psy);
+    return ret || !checked;
+}
+
+int main(void)
+{
+    for (int mode = 0; mode < 3; mode++)
+        for (int short_windows = 0; short_windows < 2; short_windows++)
+            for (int quiet = 0; quiet < 2; quiet++)
+                if (check_mask(mode, short_windows, quiet))
+                    return 1;
+    return 0;
+}
diff --git a/tests/fate/aac.mak b/tests/fate/aac.mak
index 802c00784ae..9e0ea413a8c 100644
--- a/tests/fate/aac.mak
+++ b/tests/fate/aac.mak
@@ -279,6 +279,23 @@ fate-aac-nmr-abr-encode: REF = 
$(SAMPLES)/audio-reference/luckynight_2ch_44kHz_s
 fate-aac-nmr-abr-encode: CMP_TARGET = 451
 fate-aac-nmr-abr-encode: FUZZ = 15
 
+# A near-silent impulse in the short window before an onset sits ~90 dB under
+# the onset bands: the NMR trellis found no chain-legal scalefactor path and
+# re-encoded the frame forever
+FATE_AAC_NMR_CHAIN-$(call ALLYES, LAVFI_INDEV AEVALSRC_FILTER ARESAMPLE_FILTER 
PCM_F64LE_DECODER AAC_ENCODER NULL_MUXER) += fate-aac-nmr-chain-encode
+fate-aac-nmr-chain-encode: CMD = ffmpeg -auto_conversion_filters -f lavfi -i 
"aevalsrc=if(eq(n\,24000)\,1e-10\,0)+if(gte(n\,24256)\,0.01*sin(n*n*0.37+n)\,0):s=48000:d=1.5"
 -c:a aac -b:a 128k -fflags +bitexact -flags +bitexact -f null -
+fate-aac-nmr-chain-encode: CMP = null
+
+# Upstream issue #25580: the same chain break in an initial short frame.
+FATE_AAC_NMR_CHAIN-$(call ALLYES, LAVFI_INDEV AEVALSRC_FILTER ARESAMPLE_FILTER 
PCM_F64LE_DECODER AAC_ENCODER NULL_MUXER) += fate-aac-nmr-onset-encode
+fate-aac-nmr-onset-encode: CMD = ffmpeg -auto_conversion_filters -f lavfi -i 
"aevalsrc=1e-10*eq(n\,448)+gte(n\,704)*random(0):d=0.05" -c:a aac -fflags 
+bitexact -flags +bitexact -f null -
+fate-aac-nmr-onset-encode: CMP = null
+
+# Even a silent frame cannot meet a bitrate below its signalling overhead.
+FATE_AAC_NMR_CHAIN-$(call ALLYES, LAVFI_INDEV AEVALSRC_FILTER ARESAMPLE_FILTER 
PCM_F64LE_DECODER AAC_ENCODER NULL_MUXER) += fate-aac-strict-rate-encode
+fate-aac-strict-rate-encode: CMD = ffmpeg -auto_conversion_filters -f lavfi -i 
"aevalsrc=0:s=48000:d=0.1" -c:a aac -b:a 1 -bt 0 -fflags +bitexact -flags 
+bitexact -f null -
+fate-aac-strict-rate-encode: CMP = null
+
 tests/data/fate/aac-5_1_2.adts: TAG = GEN
 tests/data/fate/aac-5_1_2.adts: tests/data/asynth-44100-8.wav
 tests/data/fate/aac-5_1_2.adts: ffmpeg$(PROGSSUF)$(EXESUF) | tests/data/fate
@@ -409,6 +426,7 @@ FATE_SAMPLES_FFPROBE += $(FATE_AAC_PREROLL-yes)
 
 FATE_SAMPLES_FFMPEG += $(FATE_AAC_ALL) $(FATE_AAC_FRAMECRC-yes) 
$(FATE_AAC_ENCODE-yes) $(FATE_AAC_BSF-yes)
 FATE_SAMPLES_FFMPEG_FFPROBE += $(FATE_AAC_ENCODE_FFPROBE-yes)
+FATE_FFMPEG += $(FATE_AAC_NMR_CHAIN-yes)
 
-fate-aac: $(FATE_AAC_ALL) $(FATE_AAC_PREROLL-yes) $(FATE_AAC_FRAMECRC-yes) 
$(FATE_AAC_ENCODE-yes) $(FATE_AAC_ENCODE_FFPROBE-yes) $(FATE_AAC_BSF-yes)
+fate-aac: $(FATE_AAC_ALL) $(FATE_AAC_PREROLL-yes) $(FATE_AAC_FRAMECRC-yes) 
$(FATE_AAC_ENCODE-yes) $(FATE_AAC_ENCODE_FFPROBE-yes) $(FATE_AAC_BSF-yes) 
$(FATE_AAC_NMR_CHAIN-yes)
 fate-aac-latm: $(FATE_AAC_LATM-yes)
diff --git a/tests/fate/libavcodec.mak b/tests/fate/libavcodec.mak
index 713b1feb368..49d00d098d0 100644
--- a/tests/fate/libavcodec.mak
+++ b/tests/fate/libavcodec.mak
@@ -1,3 +1,8 @@
+FATE_LIBAVCODEC-$(CONFIG_AAC_ENCODER) += fate-aacpsy
+fate-aacpsy: libavcodec/tests/aacpsy$(EXESUF)
+fate-aacpsy: CMD = run libavcodec/tests/aacpsy$(EXESUF)
+fate-aacpsy: CMP = null
+
 FATE_LIBAVCODEC-$(CONFIG_AC3DSP) += fate-ac3-bit-allocation
 fate-ac3-bit-allocation: libavcodec/tests/ac3$(EXESUF)
 fate-ac3-bit-allocation: CMD = run libavcodec/tests/ac3$(EXESUF)
-- 
2.54.0

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

Reply via email to