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

Git pushed a commit to branch master
in repository ffmpeg.

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

    avcodec/aacenc: rework NMR rate control, pool CPE budgets, add decision 
memory
    
    Rate control: replace the integral servo with a stateless pressure
    offset (exp2(-K*fill/R)) so a drained reservoir cannot wind up and
    crater quality after loud stretches; slew-limit the final operating
    lambda per frame (bits deviate instead, the reservoir absorbs); seed
    the reservoir full at stream start; and track rate strain explicitly:
    a long-frame lambda EMA against anchors that scale up when the achieved
    distortion/mask ratio flags noise-class content (whose psy masks are
    wholesale violated and lambda reads inflated), plus a lambda min-tracker
    separating sustained starvation from transient spikes at a comfortable
    operating point. The resulting pressure ramp gates every
    pressure-adaptive tool from one place.
    
    CPE budget pooling: solve both channels of a pair jointly under one
    shared lambda against a pooled budget (NMRSlot defer/solve/commit)
    instead of an equal per-channel split, which starved the mid/carrier
    while the side gold-plated. Mono and VBR output are unchanged.
    
    Transients: isolated onsets are coded uniformly finer across the short
    run and repaid from steady stretches; dense-beat runs get a
    starvation-scaled boost; a transition premask clamps START-frame
    thresholds toward the previous long frame (an attack cannot mask
    backwards).
    
    Decision memory: marginal per-frame re-decisions oscillate audibly, so
    every stateful choice now carries hysteresis - band zeroing, PNS
    enter/leave with debounce (and near-masked bands staying noise until a
    loudness guard), per-grid stereo mode banks that survive window
    switches, and the short-TNS accept state.
    
    TNS-covered bands price distortion by the synthesis filter's
    re-amplification gain, so the trellis spends where noise will actually
    be heard.
---
 libavcodec/aaccoder_nmr.h                          | 988 ++++++++++++++-------
 libavcodec/aacenc.h                                |  58 +-
 tests/ref/fate/id3v2-reenc-delete-metadata         |   4 +-
 tests/ref/fate/id3v2-reenc-delete-metadata-keep    |   4 +-
 .../fate/id3v2-reenc-delete-metadata-keep-format   |   4 +-
 .../fate/id3v2-reenc-delete-metadata-keep-stream   |   4 +-
 .../fate/id3v2-reenc-delete-metadata-map-metadata  |   4 +-
 7 files changed, 711 insertions(+), 355 deletions(-)

diff --git a/libavcodec/aaccoder_nmr.h b/libavcodec/aaccoder_nmr.h
index 7a01a57570..224cb24d7d 100644
--- a/libavcodec/aaccoder_nmr.h
+++ b/libavcodec/aaccoder_nmr.h
@@ -83,15 +83,29 @@
  * smooth while per-frame demand is tracked; 1.5 cuts lambda jitter ~25%. */
 #define NMR_RC_CORR   1.5f
 
-/* Leaky-bucket half-depth (bits/ch); 512 is the sweet spot — tighter rebounds 
as
- * frames cannot hit the narrow window. Clamped to the 6144 bits/ch decoder 
buffer. */
-#define NMR_CBR_BUF   512
+/* Reservoir half-window (bits/ch); swept 512/1536/3072, 1536 optimal. */
+#define NMR_CBR_BUF   1536
+/* Slew limit on the FINAL operating lambda per frame; bits deviate instead,
+ * the reservoir absorbs. See memory: aac-castanets-transient-rc. */
+#define NMR_SLEW      1.6f
+#define NMR_SLEW_RUN  1.15f /* within short runs */
 #define NMR_RC_CITERS 3 /* corridor coarse-pass iters */
 
+/* Transition premask: an attack cannot mask backwards; clamp a START frame's
+ * thresholds toward the previous long frame's. */
+#define NMR_TRANS_PM 2.0f
+
+/* Zero-decision hysteresis: previously-coded bands need this margin below
+ * threshold to zero (marginal bands flicker audibly otherwise). */
+#define NMR_ZERO_STICKY 0.5f
+
 /* Transient bit-burst: an isolated onset (preceded by >= NMR_BURST_GAP long 
frames)
  * is coded NMR_BURST_GAIN x finer, held uniform across the run, repaid from 
steady stretches. */
 #define NMR_BURST_GAP   10
 #define NMR_BURST_GAIN  8.0f
+/* Dense-beat boost: short runs with gap < NMR_BURST_GAP get a budget factor
+ * ramping with the gap (starvation-scaled at the use site). */
+#define NMR_SHORT_BOOST 2.0f
 #define NMR_RC_FITERS 4 /* corridor fine-pass iters */
 #define NMR_RC_TRACK  0.1f /* per-frame pull of the corridor centre toward the 
realized lambda */
 
@@ -106,6 +120,14 @@
  * substituting real texture for 9 signalling bits is net-negative. */
 #define NMR_PNS_LAM 100.0f
 
+/* PNS decision hysteresis: enter and leave both cost a margin. */
+#define NMR_PNS_ENTER 0.7f
+#define NMR_PNS_STAY  1.4f
+/* PNS debounce: enter after NMR_PNS_ON consecutive wants, leave after
+ * NMR_PNS_OFF (chronically marginal bands never qualify). */
+#define NMR_PNS_ON  8
+#define NMR_PNS_OFF 4
+
 /**
  * Viterbi over the coding sequence act[0..nact-1] (indices into the per-band
  * curves nd/nb), with lambda binary-searched so the coded size ~ destbits.
@@ -201,128 +223,86 @@ static int nmr_band_curve(AACEncContext *s, 
SingleChannelElement *sce, int w, in
     return ncand;
 }
 
-static void search_for_quantizers_nmr(AVCodecContext *avctx,
-                                      AACEncContext *s,
-                                      SingleChannelElement *sce,
-                                      const float lambda)
+/* Zero a channel with nothing codeable; stale band_types would resurrect
+ * bands with chain-illegal scalefactors. */
+static void nmr_bail_channel(SingleChannelElement *sce)
 {
-    int bch = ((avctx->flags & AV_CODEC_FLAG_QSCALE) ? 2.0f : 
avctx->ch_layout.nb_channels);
-    int destbits = avctx->bit_rate * 1024.0 / avctx->sample_rate / bch * 
(lambda / 120.f);
-    int allz = 0, cutoff = 1024, nbnd = 0;
+    for (int i = 0; i < 128; i++) {
+        if (sce->band_type[i] == INTENSITY_BT || sce->band_type[i] == 
INTENSITY_BT2)
+            continue;
+        sce->zeroes[i]    = 1;
+        sce->band_type[i] = 0;
+    }
+}
 
-    float thr[128];                 /* allocation-law effective threshold 
(drives the trellis) */
-    float thr_real[128];            /* real masking threshold (perceptual 
gates: PNS) */
-    float pener[128];               /* band energy (for PNS noise target)  */
-    float pspread[128];             /* band tonality spread (1 = noise)     */
-    int   minsf[128];
-    float maxvals[128];
-
-    /* coded-band trellis state (indexed 0..nbnd-1) */
-    int bidx[128];                  /* sce band index (w*16+g) */
-    int bw[128], bg[128], bst[128]; /* window group, swb, coef start per coded 
band */
-    int blo[128];                   /* finest candidate scalefactor */
-    int bnc[128];                   /* number of candidates */
-    int chosen[128];
-    int act[128];                   /* active (non-PNS) band coding order */
-    uint8_t is_pns[128];            /* trellis band coded as noise */
-
-    float (*nd)[NMR_NCAND] = s->nmr->nd; /* dist / threshold per candidate 
(heap) */
-    int   (*nb)[NMR_NCAND] = s->nmr->nb; /* spectral bits per candidate (heap) 
   */
-
-    /* two-pass coarse->fine grid step (see NMR_COARSE), the lambda search 
runs on
-     * the cheap coarse grid, PASS 2 refines the winner at NMR_STEP 
granularity */
+/* Per-channel setup into slot t: short-block threshold shaping, the
+ * allocation law, zero decisions, and the PASS 1 coarse candidate curves.
+ * Returns the coded-band count; 0 = nothing codeable (caller bails). */
+static int nmr_setup_channel(AVCodecContext *avctx, AACEncContext *s,
+                             SingleChannelElement *sce, NMRSlot *t)
+{
+    float (*nd)[NMR_NCAND] = s->nmr->nd[t->si];
+    int   (*nb)[NMR_NCAND] = s->nmr->nb[t->si];
     const int cstep = NMR_COARSE > 0 ? NMR_COARSE : NMR_STEP;
+    int allz = 0, cutoff = 1024, nbnd = 0;
 
-    s->nmr->counted[s->cur_channel] = 0;
-
-    /* Global-lambda RC: one solve per frame at a servoed centre lambda; the 
reservoir
-     * holds the long-run mean rate. Bypassed for VBR (-q:a) and the bootstrap 
frame. */
-    int rc_eligible = !(avctx->flags & AV_CODEC_FLAG_QSCALE) && 
avctx->bit_rate > 0 &&
-                      avctx->bit_rate_tolerance != 0;
-    /* Leaky-bucket reservoir: rc_fill (signed +-rc_bmax); the spend-floor/cap 
below force
-     * lambda so no frame banks past +rc_bmax or borrows past -rc_bmax. */
-    int rc_rate_frame = avctx->bit_rate * 1024.0 / avctx->sample_rate;
-    int rc_bmax = FFMIN(FFMAX(6144 * s->channels - rc_rate_frame, 256), 
NMR_CBR_BUF * s->channels);
-    if (rc_eligible && avctx->frame_num != s->nmr->rc_frame_num) {
-        if (s->nmr->rc_frame_num > 0 && s->nmr->lam_rc > 0.0f)
-            s->nmr->rc_fill = av_clip(s->nmr->rc_fill + rc_rate_frame - 
s->last_frame_pb_count,
-                                      -rc_bmax, rc_bmax);
-        s->nmr->rc_frame_num = avctx->frame_num;
-
-        /* Transient burst run state: set at run start and held across the run 
so
-         * coding stays uniform; repaid from the reservoir's steady stretches. 
*/
-        int is_short = sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE;
-        if (is_short) {
-            if (!s->nmr->prev_was_short)        /* run start */
-                s->nmr->run_burst = s->nmr->frames_since_short >= NMR_BURST_GAP
-                                  ? NMR_BURST_GAIN : 1.0f;
-            s->nmr->frames_since_short = 0;
-        } else {
-            s->nmr->run_burst = 1.0f;
-            s->nmr->frames_since_short++;
-        }
-        s->nmr->prev_was_short = is_short;
+    uint8_t *zprev = s->nmr->zero_prev[s->cur_channel & 15];
+    if (s->nmr->zero_nw[s->cur_channel & 15] != sce->ics.num_windows) {
+        memset(zprev, 1, 128);
+        s->nmr->zero_nw[s->cur_channel & 15] = sce->ics.num_windows;
     }
-    int rc_global = rc_eligible && s->nmr->lam_rc > 0.0f;
-
-    if (s->psy.bitres.alloc >= 0)
-        destbits = s->psy.bitres.alloc *
-                   (lambda / (avctx->global_quality ? avctx->global_quality : 
120));
-    if (rc_global && s->psy.bitres.alloc >= 0)
-        /* uniform CBR target: nominal rate plus fast reservoir repayment */
-        destbits = (avctx->bit_rate * 1024.0 / avctx->sample_rate
-                    + s->nmr->rc_fill / 2.0) / s->channels;
-    destbits = FFMIN(destbits, 5800);
-    /* honest budget: subtract the measured non-trellis overhead (section 
data, ICS,
-     * sf/PNS signalling), which is rate-dependent hence adaptive. */
-    if (s->nmr->side_inited)
-        destbits = av_clip(destbits - (int)(s->nmr->side_ema / s->channels), 
64, 5800);
 
-    /* Apply the held transient burst factor (set in the run-state machine 
above). */
-    if (sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE && 
s->nmr->run_burst > 1.0f)
-        destbits = av_clip((int)(destbits * s->nmr->run_burst), 64, 6800);
+    t->sce    = sce;
+    t->cur_ch = s->cur_channel;
+    t->is8    = sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE;
+    t->nbnd   = t->nact = 0;
 
     /* band cutoff index for this frame's window size; the bandwidth is fixed
      * at init and shared with the psy model */
     cutoff = s->bandwidth * 2 * (1024 / sce->ics.num_windows) / 
avctx->sample_rate;
 
-    /* Short-block transient noise shaping (pairs with short-block TNS): 
temporal
-     * premasking clamps each window's threshold toward the preceding windows'
-     * (Apple's preEchoReduction), and flat-residual flattens each window's 
thresholds
-     * to their per-window mean so TNS synthesis has a white floor to 
concentrate. */
+    /* Short-block shaping: temporal premask + per-window threshold flatten. */
     if (sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE) {
         const float pm_p1 = 0.1f, pm_p2 = 2.0f, pm_p3 = 4.0f;
         for (int g = 0; g < sce->ics.num_swb; g++) {
             float t1 = FLT_MAX, t2 = FLT_MAX;   /* original thr of w-1, w-2 */
             for (int w = 0; w < sce->ics.num_windows; w++) {
                 FFPsyBand *b = &s->psy.ch[s->cur_channel].psy_bands[w*16+g];
-                float t = b->threshold;
-                float c = FFMIN(t, FFMIN(t1*pm_p2, t2*pm_p3));
-                b->threshold = FFMAX(c, t*pm_p1);
-                t2 = t1; t1 = t;
+                float th = b->threshold;
+                float c = FFMIN(th, FFMIN(t1*pm_p2, t2*pm_p3));
+                b->threshold = FFMAX(c, th*pm_p1);
+                t2 = t1; t1 = th;
             }
         }
         {
             for (int w = 0; w < sce->ics.num_windows; w++) {
-                float sum = 0.0f; int n = 0;
+                float sum = 0.0f, esum = 0.0f; int n = 0;
                 for (int g = 0; g < sce->ics.num_swb; g++) {
                     FFPsyBand *b = 
&s->psy.ch[s->cur_channel].psy_bands[w*16+g];
-                    if (b->energy > b->threshold && b->threshold > 0.0f) { sum 
+= b->threshold; n++; }
+                    if (b->energy > b->threshold && b->threshold > 0.0f) { sum 
+= b->threshold; esum += b->energy; n++; }
                 }
                 if (n > 0) {
-                    float mean = sum / n;
+                    /* keep each window codeable: cap the mean 12dB under the
+                     * window's mean audible energy */
+                    float mean = FFMIN(sum / n, (esum / n) * expf(-12.0f * 
(float)M_LN10 / 10.0f));
                     for (int g = 0; g < sce->ics.num_swb; g++) {
                         FFPsyBand *b = 
&s->psy.ch[s->cur_channel].psy_bands[w*16+g];
                         if (b->energy > b->threshold && b->threshold > 0.0f)
-                            b->threshold = mean;
+                            b->threshold = FFMIN(mean, b->threshold * 1e9f);
                     }
                 }
             }
         }
     }
 
-    /* Allocation curve to favour high frequencies */
-    const float a_ae = 0.443f, a_at = 0.111f;
+    /* Allocation law; short frames blend to softer energy exponents under
+     * pressure (roll anti-starvation, see memory). */
+    float a_ae = 0.443f, a_at = 0.111f;
+    if (sce->ics.num_windows == 8 && s->nmr) {
+        /* blend to mask-weighted exponents under rate pressure */
+        a_ae += (0.35f - a_ae) * s->nmr->press;
+        a_at += (0.3f  - a_at) * s->nmr->press;
+    }
     for (int w = 0; w < sce->ics.num_windows; w += sce->ics.group_len[w]) {
         int start = 0;
         for (int g = 0; g < sce->ics.num_swb; start += 
sce->ics.swb_sizes[g++]) {
@@ -336,69 +316,128 @@ static void search_for_quantizers_nmr(AVCodecContext 
*avctx,
                     sce->zeroes[(w+w2)*16+g] = 0;
                 continue;
             }
+            float zthr_mul = zprev[w*16+g] ? 1.0f : NMR_ZERO_STICKY;
+            /* M/S side bands: zero-reluctance scaled by side/mid ratio (a tiny
+             * side IS the image; zeroing it flickers). */
+            if ((t->cur_ch & 1) && s->nmr && s->nmr->pair &&
+                s->nmr->smode_band[(t->cur_ch >> 1) & 7][w*16+g] == 1) {
+                const FFPsyBand *mb = &s->psy.ch[s->cur_channel - 
1].psy_bands[w*16+g];
+                float ratio = 0.0f;
+                float eside = 0.0f;
+                for (int w2 = 0; w2 < sce->ics.group_len[w]; w2++) {
+                    const FFPsyBand *bb = 
&s->psy.ch[s->cur_channel].psy_bands[(w+w2)*16+g];
+                    eside += bb->energy;
+                }
+                ratio = eside / FFMAX(mb->energy * sce->ics.group_len[w], 
1e-9f);
+                zthr_mul *= 0.25f + 0.75f * av_clipf(ratio / 0.3f, 0.0f, 1.0f);
+            }
             for (int w2 = 0; w2 < sce->ics.group_len[w]; w2++) {
                 FFPsyBand *band = 
&s->psy.ch[s->cur_channel].psy_bands[(w+w2)*16+g];
                 ener   += band->energy;
                 spread  = FFMIN(spread, band->spread);
-                if (start >= cutoff || band->energy <= band->threshold || 
band->threshold == 0.0f) {
+                if (start >= cutoff || band->energy <= band->threshold * 
zthr_mul ||
+                    band->threshold == 0.0f) {
                     sce->zeroes[(w+w2)*16+g] = 1;
                     continue;
                 }
                 uplim += band->threshold;
                 nz = 1;
             }
+            zprev[w*16+g] = !nz;
             sce->zeroes[w*16+g] = !nz;
-            thr_real[w*16+g] = uplim;       /* real mask, before the 
allocation law (PNS gate) */
-            if (nz && ener > 0.0f && uplim > 0.0f)
+            t->thr_real[w*16+g] = uplim;    /* real mask, before the 
allocation law (PNS gate) */
+            if (nz && ener > 0.0f && uplim > 0.0f)   /* allocation law */
                 uplim = expf(a_ae * logf(ener) + a_at * logf(uplim));
-            thr[w*16+g]     = uplim;
-            pener[w*16+g]   = ener;
-            pspread[w*16+g] = spread;
+            t->thr[w*16+g]     = uplim;
+            t->pener[w*16+g]   = ener;
+            t->pspread[w*16+g] = spread;
             allz |= nz;
         }
     }
     if (!allz)
-        goto bail;
+        return 0;
+
+    /* transition premask (see NMR_TRANS_PM) */
+    if (sce->ics.num_windows == 1) {
+        int ci = t->cur_ch & 15;
+        if (sce->ics.window_sequence[0] == LONG_START_SEQUENCE &&
+            s->nmr->thr_prev_ok[ci]) {
+            for (int g = 0; g < sce->ics.num_swb && g < 64; g++)
+                if (t->thr[g] > 0.0f && s->nmr->thr_prev[ci][g] > 0.0f)
+                    t->thr[g] = FFMIN(t->thr[g], s->nmr->thr_prev[ci][g] * 
NMR_TRANS_PM);
+        }
+        for (int g = 0; g < sce->ics.num_swb && g < 64; g++)
+            s->nmr->thr_prev[ci][g] = t->thr[g];
+        s->nmr->thr_prev_ok[ci] = 1;
+    } else {
+        s->nmr->thr_prev_ok[t->cur_ch & 15] = 0;
+    }
 
     s->aacdsp.abs_pow34(s->scoefs, sce->coeffs, 1024);
     ff_quantize_band_cost_cache_init(s);
 
+    /* TNS synthesis gain per band: the decoder re-amplifies residual-domain
+     * quantization noise by the whitening gain (shorts only). */
+    for (int i = 0; i < 128; i++)
+        t->tnsg[i] = 1.0f;
+    if (sce->ics.num_windows == 8 && sce->tns.present) {
+        const int mmm2 = FFMIN(sce->ics.tns_max_bands, sce->ics.max_sfb ? 
sce->ics.max_sfb : sce->ics.num_swb);
+        for (int w = 0; w < 8; w++) {
+            int bottom2 = sce->ics.num_swb;
+            for (int filt = 0; filt < sce->tns.n_filt[w]; filt++) {
+                int top2 = bottom2;
+                bottom2 = FFMAX(0, top2 - sce->tns.length[w][filt]);
+                if (!sce->tns.order[w][filt])
+                    continue;
+                for (int g = FFMIN(bottom2, mmm2); g < FFMIN(top2, mmm2); g++) 
{
+                    int s0 = sce->ics.swb_offset[g] + w*128;
+                    int s1 = sce->ics.swb_offset[g+1] + w*128;
+                    float eres = 0.0f;
+                    const FFPsyBand *pb = 
&s->psy.ch[s->cur_channel].psy_bands[w*16+g];
+                    for (int k = s0; k < s1; k++)
+                        eres += sce->coeffs[k]*sce->coeffs[k];
+                    t->tnsg[w*16+g] = av_clipf(pb->energy / FFMAX(eres, 
1e-12f), 1.0f, 64.0f);
+                }
+            }
+        }
+    }
+
     /* finest codeable scalefactor and max value per band */
     for (int w = 0; w < sce->ics.num_windows; w += sce->ics.group_len[w]) {
         int start = w*128;
         for (int g = 0; g < sce->ics.num_swb; g++) {
-            maxvals[w*16+g] = find_max_val(sce->ics.group_len[w], 
sce->ics.swb_sizes[g], s->scoefs + start);
-            minsf[w*16+g]   = maxvals[w*16+g] > 0 ? 
coef2minsf(maxvals[w*16+g]) : 0;
+            t->maxvals[w*16+g] = find_max_val(sce->ics.group_len[w], 
sce->ics.swb_sizes[g], s->scoefs + start);
+            t->minsf[w*16+g]   = t->maxvals[w*16+g] > 0 ? 
coef2minsf(t->maxvals[w*16+g]) : 0;
             start += sce->ics.swb_sizes[g];
         }
     }
 
-    /* PASS 1:
-     * precompute each coded band's cost curve at the coarse candidate step
+    /* PASS 1: coarse candidate curves per coded band
      * (the lambda search runs on this cheap grid, PASS 2 refines the winner) 
*/
     {
         for (int w = 0; w < sce->ics.num_windows; w += sce->ics.group_len[w]) {
             int start = w*128;
             for (int g = 0; g < sce->ics.num_swb; g++) {
-                if (!sce->zeroes[w*16+g] && maxvals[w*16+g] > 0 && nbnd < 128) 
{
-                    int lo = av_clip(minsf[w*16+g], 0, SCALE_MAX_POS);
-                    float invthr = 1.0f / FFMAX(thr[w*16+g], 1e-9f);
+                if (!sce->zeroes[w*16+g] && t->maxvals[w*16+g] > 0 && nbnd < 
128) {
+                    int lo = av_clip(t->minsf[w*16+g], 0, SCALE_MAX_POS);
+                    float invthr = 1.0f / FFMAX(t->thr[w*16+g], 1e-9f);
                     int ncand = nmr_band_curve(s, sce, w, g, start, lo, cstep, 
NMR_NCAND,
-                                               invthr, maxvals[w*16+g], 
nd[nbnd], nb[nbnd]);
+                                               invthr, t->maxvals[w*16+g], 
nd[nbnd], nb[nbnd]);
+                    if (t->tnsg[w*16+g] > 1.0f)
+                        for (int o = 0; o < ncand; o++)
+                            nd[nbnd][o] *= t->tnsg[w*16+g];
                     if (ncand == 0) {
-                        /* nothing codeable -> drop the whole group band. The
-                         * subwindow flags must be cleared too: the encoder 
later
-                         * re-derives the group flag by ANDing them, which 
would
-                         * resurrect the band with a never-assigned 
scalefactor. */
+                        /* nothing codeable: drop the group band incl. 
subwindow
+                         * flags (group flag is re-derived by ANDing) */
                         for (int w2 = 0; w2 < sce->ics.group_len[w]; w2++)
                             sce->zeroes[(w+w2)*16+g] = 1;
                     } else {
-                        bidx[nbnd] = w*16+g;
-                        bw[nbnd] = w;
-                        bg[nbnd] = g;
-                        bst[nbnd] = start;
-                        blo[nbnd] = lo;
-                        bnc[nbnd] = ncand;
+                        t->bidx[nbnd] = w*16+g;
+                        t->bw[nbnd] = w;
+                        t->bg[nbnd] = g;
+                        t->bst[nbnd] = start;
+                        t->blo[nbnd] = lo;
+                        t->bnc[nbnd] = ncand;
                         nbnd++;
                     }
                 }
@@ -406,223 +445,94 @@ static void search_for_quantizers_nmr(AVCodecContext 
*avctx,
             }
         }
     }
-    if (!nbnd)
-        goto bail;
+    t->nbnd = nbnd;
+    for (int b = 0; b < nbnd; b++) {
+        t->act[b]    = b;
+        t->is_pns[b] = 0;
+    }
+    t->nact = nbnd;
+    return nbnd;
+}
 
-    /* solve the trellis over all coded bands, then offer PNS at the operating
-     * lambda and re-solve over the survivors with the freed budget */
-    {
-        int nact = nbnd, pns_count = 0;
-        float lam0 = s->nmr->lam[s->cur_channel];
-        float lam;
+/* total bits of a slot's current chosen[] on grid `step`, incl. sf deltas */
+static int nmr_slot_bits(const NMRSlot *t, const int (*nb)[NMR_NCAND], int 
step)
+{
+    int tot = 0;
+    for (int k = 0; k < t->nact; k++)
+        tot += nb[t->act[k]][t->chosen[t->act[k]]];
+    for (int k = 1; k < t->nact; k++)
+        tot += NMR_SFBITS((t->blo[t->act[k]]+t->chosen[t->act[k]]*step) -
+                          (t->blo[t->act[k-1]]+t->chosen[t->act[k-1]]*step));
+    return tot;
+}
 
-        for (int b = 0; b < nbnd; b++) {
-            act[b] = b;
-            is_pns[b] = 0;
-        }
-        if (rc_global) {
-            /* bisect to this frame's bit demand within the corridor around the
-             * servoed lambda: per-frame psy demand is tracked, but lambda 
cannot
-             * jump, which keeps quality smooth across frames */
-            float lo = s->nmr->lam_rc / NMR_RC_CORR;
-            /* Transient burst: widen the lower lambda bound so the bisection 
can actually
-             * pour the boosted destbits into an onset frame (finer coding 
kills the
-             * pre-echo); reservoir servo repays it from the steady frames. 
run_burst==1 on
-             * non-onset frames leaves the corridor unchanged. */
-            if (sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE && 
s->nmr->run_burst > 1.0f)
-                lo /= s->nmr->run_burst;
-            lam = nmr_solve(s, nd, nb, blo, bnc, cstep, act, nact, destbits, 
chosen,
-                            lo, s->nmr->lam_rc * NMR_RC_CORR,
-                            NMR_RC_CITERS);
-
-            int tot = 0;
-            for (int k = 0; k < nact; k++)
-                tot += nb[act[k]][chosen[act[k]]];
-            for (int k = 1; k < nact; k++)
-                tot += NMR_SFBITS((blo[act[k]]+chosen[act[k]]*cstep) - 
(blo[act[k-1]]+chosen[act[k-1]]*cstep));
-            int hardcap = av_clip((int)(5800.f * FFMIN(1.f, lambda / 120.f)), 
256, 5800);
-            /* leaky-bucket window: don't borrow past -rc_bmax (cap) or bank 
past +rc_bmax (floor) */
-            int rc_cap   = FFMIN(hardcap, (s->nmr->rc_fill + rc_rate_frame + 
rc_bmax) / s->channels);
-            int rc_floor = FFMAX(0, (s->nmr->rc_fill + rc_rate_frame - 
rc_bmax) / s->channels);
-            if (tot > rc_cap)
-                lam = nmr_solve(s, nd, nb, blo, bnc, cstep, act, nact, rc_cap, 
chosen,
-                                lam, 1e4f, NMR_CITERS);
-            else if (tot < rc_floor)
-                lam = nmr_solve(s, nd, nb, blo, bnc, cstep, act, nact, 
rc_floor, chosen,
-                                1e-9f, lam, NMR_CITERS);
-        } else if (NMR_COARSE > 0 && lam0 > 0.0f) {
-            /* per-frame bisection; lambda is strongly frame-correlated, so 
when a
-             * previous frame's operating lambda exists, bisect a narrow 
bracket
-             * around it. A result near the bracket edge means the budget 
crossing
-             * lies outside (hard content transition) == redo the full search. 
*/
-            lam = nmr_solve(s, nd, nb, blo, bnc, cstep, act, nact, destbits, 
chosen,
-                            lam0/32.0f, lam0*32.0f, NMR_CWARM);
-            if (lam < lam0/16.0f || lam > lam0*16.0f)
-                lam0 = 0.0f;
-        }
-        if (!rc_global && lam0 <= 0.0f)
-            lam = nmr_solve(s, nd, nb, blo, bnc, cstep, act, nact, destbits, 
chosen,
-                            1e-9f, 1e4f, NMR_COARSE > 0 ? NMR_CITERS : 
NMR_ITERS);
-
-        /* PASS 2:
-         * refine each band at full granularity (NMR_STEP) in a +/-cstep window
-         * around the coarse pick, then re-solve. Recovers single-pass quality 
while the
-         * lambda search stayed cheap on the coarse grid. */
-        if (NMR_COARSE > 0) {
-            /* nmr_speed, 0 = slowest/best, higher = faster. It narrows the 
fine
-             * refine +/-window (scalefactors) below NMR_COARSE: at speed 0 
the window
-             * spans the whole coarse-grid gap, so the two-pass result matches 
the
-             * exhaustive single-pass search.
-             * Each speed level shaves one sf off the window.
-             * At @64k mono (Zim / xRT): speed 0 -> 0.00095/15x,
-             * 2 -> 0.00096/18x, 3 -> 0.00100/20x, 4 -> 0.00103/22x */
-            int win = NMR_COARSE - av_clip(s->options.nmr_speed, 0, 4);
-            for (int b = 0; b < nbnd; b++) {
-                int center = blo[b] + chosen[b]*cstep;
-                int flo    = av_clip(center - win, av_clip(minsf[bidx[b]], 0, 
SCALE_MAX_POS), SCALE_MAX_POS);
-                int maxn   = FFMIN(NMR_NCAND, 2*win/NMR_STEP + 1);
-                float invthr = 1.0f / FFMAX(thr[bidx[b]], 1e-9f);
-                int ncand  = nmr_band_curve(s, sce, bw[b], bg[b], bst[b], flo, 
NMR_STEP, maxn,
-                                            invthr, maxvals[bidx[b]], nd[b], 
nb[b]);
-                blo[b] = flo;
-                bnc[b] = FFMAX(1, ncand);
-            }
-            /* fine pass: narrow corridor around the coarse solve */
-            if (rc_global)
-                lam = nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
destbits, chosen,
-                                lam/2.0f, lam*2.0f, NMR_RC_FITERS);
-            else
-                lam = nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
destbits, chosen,
-                                lam/16.0f, lam*16.0f, NMR_IFINE);
-        }
+/* 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)
+{
+    int total = 0;
+    for (int k = 0; k < nsl; k++) {
+        NMRSlot *t = sl[k];
+        if (!t->nact)
+            continue;
+        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);
+    }
+    return total;
+}
 
-        if (rc_global) {
-            /* leaky-bucket clamp: keep the frame within [rc_floor, rc_cap] so 
the reservoir
-             * stays in +-rc_bmax -- clamp lambda UP if it would borrow past 
the cap, DOWN if it
-             * would bank past the floor (spend-floor). The hard cap follows 
the encoder's outer
-             * lambda so the (rare) hard-overflow re-encode -- which shrinks 
that lambda -- always
-             * converges; on the first pass lambda is nominal and this is 
5800. */
-            int hardcap = av_clip((int)(5800.f * FFMIN(1.f, lambda / 120.f)), 
256, 5800);
-            int tot = 0;
-            for (int k = 0; k < nact; k++)
-                tot += nb[act[k]][chosen[act[k]]];
-            for (int k = 1; k < nact; k++)
-                tot += NMR_SFBITS((blo[act[k]]+chosen[act[k]]*NMR_STEP) - 
(blo[act[k-1]]+chosen[act[k-1]]*NMR_STEP));
-            int rc_cap   = FFMIN(hardcap, (s->nmr->rc_fill + rc_rate_frame + 
rc_bmax) / s->channels);
-            int rc_floor = FFMAX(0, (s->nmr->rc_fill + rc_rate_frame - 
rc_bmax) / s->channels);
-            if (tot > rc_cap)
-                lam = nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
rc_cap, chosen,
-                                lam, 1e4f, NMR_RC_ITERS);
-            else if (tot < rc_floor)
-                lam = nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
rc_floor, chosen,
-                                1e-9f, lam, NMR_RC_ITERS);
-        }
+/* 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. */
+static float nmr_solve_slots(AACEncContext *s, NMRSlot *const *sl, int nsl, 
int step,
+                             int destbits, float lo_l, float hi_l, int iters)
+{
+    float lam = 1.0f;
+    for (int it = 0; it < iters; it++) {
+        lam = sqrtf(lo_l * hi_l);
+        int total = nmr_eval_slots(s, sl, nsl, step, lam);
+        if (it == iters - 1)
+            break;
+        /* over budget -> go coarser */
+        if (total > destbits)
+            lo_l = lam;
+        else
+            hi_l = lam;
+    }
+    return lam;
+}
 
-        s->nmr->lam[s->cur_channel] = lam;   /* warm start for the next frame 
*/
-        if (rc_global) {
-            /* drag the corridor centre toward the realized lambda so it 
follows
-             * content drift faster than the reservoir term alone */
-            float c = s->nmr->lam_rc * powf(lam / s->nmr->lam_rc, 
NMR_RC_TRACK);
-            /* then servo the centre off the reservoir error so the long-run 
rate
-             * returns to nominal. rc_fill>0 = bits banked (undershooting) -> 
lower
-             * lambda to spend them; <0 -> raise it. This is what holds the 
mean;
-             * the corridor tracking alone has no rate authority and a bad 
centre
-             * would otherwise drift for dozens of frames, starving each one. 
*/
-            float R = avctx->bit_rate * 1024.0 / avctx->sample_rate;
-            c *= exp2f(-NMR_RC_K_CBR * s->nmr->rc_fill / R);
-            s->nmr->lam_rc = av_clipf(c, 1e-6f, 1e4f);
-        } else if (rc_eligible && nbnd >= 8) {
-            /* bootstrap the servo off the first substantive frame; near-silent
-             * lead-in frames have degenerate budgets that rail the bisection 
to
-             * a nonsense lambda and would poison the whole stream */
-            s->nmr->lam_rc = av_clipf(lam, 1e-4f, 10.0f);
+/* Write a solved slot back into its channel: band types, scalefactors, and the
+ * SCALE_MAX_DIFF legality fixups. Verbatim from the pre-pool single-channel 
tail. */
+static void nmr_commit_channel(AACEncContext *s, NMRSlot *t)
+{
+    SingleChannelElement *sce = t->sce;
+    const int (*nb)[NMR_NCAND] = (const int (*)[NMR_NCAND])s->nmr->nb[t->si];
+
+    for (int b = 0; b < t->nbnd; b++) {
+        int bi = t->bidx[b];
+        if (t->is_pns[b]) {
+            sce->band_type[bi] = NOISE_BT;
+            sce->zeroes[bi]    = 0;
+            sce->pns_ener[bi]  = t->pener[bi] * FFMIN(1.0f, 
t->pspread[bi]*t->pspread[bi]);
+        } else {
+            sce->sf_idx[bi] = av_clip(t->blo[b] + t->chosen[b]*NMR_STEP, 0, 
SCALE_MAX_POS);
         }
+    }
 
-        {   /* PNS */
-            const float pns_lam = NMR_PNS_LAM;
-            /* band 0 (lowest freq) is kept as the global-gain / sf-chain 
anchor */
-            for (int b = 1; b < nbnd; b++) {
-                int bi = bidx[b];
-                float spread = pspread[bi];
-                float nmr_pns, cost_keep, cost_pns, frac;
-                if (!sce->can_pns[bi])
-                    continue;
-
-                /* Loud-band guard: never substitute a band whose energy is 
far above the
-                 * masking threshold -- energy-matched noise on a dominant 
band clips/pops
-                 * (and is audibly wrong). PNS is for near-masked noise only. 
*/
-                if (pener[bi] > NMR_PNS_MAX_ET * thr_real[bi])
-                    continue;
-
-                /* Struggle gate: no PNS at all unless the encoder is 
genuinely under bit
-                 * pressure (high operating lambda). */
-                if (lam <= pns_lam)
-                    continue;
-
-                /* Spectral-hole fill: a noise-like band the trellis left 
mostly empty */
-                frac = nd[b][chosen[b]] * thr[bi] / FFMAX(pener[bi], 1e-9f);
-                if (spread > NMR_PNS_HOLE_SPREAD && frac > NMR_PNS_HOLE_FRAC) {
-                    is_pns[b] = 1;
-                    pns_count++;
-                    continue;
-                }
-
-                /* Only replace a band that is being coded audibly badly */
-                if (nd[b][chosen[b]] * thr[bi] <= NMR_PNS_NDGATE * 
thr_real[bi])
-                    continue;
 
-                /* perceptual cost of replacing the band with energy-matched 
noise:
-                 * the non-noise-like fraction of its energy, in 
dist/threshold units */
-                nmr_pns = FFMAX(0.0f, pener[bi] * (1.0f - spread*spread))
-                          / FFMAX(thr[bi], 1e-9f);
-                cost_keep = nd[b][chosen[b]] + lam * nb[b][chosen[b]];
-                cost_pns  = nmr_pns + lam * NMR_PNS_BITS;
-                if (cost_pns < cost_keep) {
-                    is_pns[b] = 1;
-                    pns_count++;
-                }
-            }
-            if (pns_count) {
-                int budget2 = destbits - pns_count * NMR_PNS_BITS;
-                nact = 0;
-                for (int b = 0; b < nbnd; b++)
-                    if (!is_pns[b])
-                        act[nact++] = b;
-                /* re-solve over the survivors: at fixed lambda the allocation 
is
-                 * the same except for the repaired sf-delta chain; in 
bisection
-                 * mode re-spend the freed budget */
-                if (rc_global)
-                    nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
budget2, chosen,
-                              lam, lam, 1);
-                else
-                    nmr_solve(s, nd, nb, blo, bnc, NMR_STEP, act, nact, 
budget2, chosen,
-                              1e-9f, 1e4f, NMR_ITERS);
-            }
-        }
-        for (int b = 0; b < nbnd; b++) {
-            int bi = bidx[b];
-            if (is_pns[b]) {
-                sce->band_type[bi] = NOISE_BT;
-                sce->zeroes[bi]    = 0;
-                sce->pns_ener[bi]  = pener[bi] * FFMIN(1.0f, 
pspread[bi]*pspread[bi]);
-            } else {
-                sce->sf_idx[bi] = av_clip(blo[b] + chosen[b]*NMR_STEP, 0, 
SCALE_MAX_POS);
-            }
-        }
-
-        {   /* record the bits this solve accounted for; the encoder compares 
them
-             * against the channel's real output to keep the budget honest */
-            int tot = 0, prevb = -1;
-            for (int b = 0; b < nbnd; b++) {
-                if (is_pns[b])
-                    continue;
-                tot += nb[b][chosen[b]];
-                if (prevb >= 0)
-                    tot += NMR_SFBITS((blo[b]+chosen[b]*NMR_STEP) - 
(blo[prevb]+chosen[prevb]*NMR_STEP));
-                prevb = b;
-            }
-            s->nmr->counted[s->cur_channel] = tot;
+    {   /* record the bits this solve accounted for; the encoder compares them
+         * against the channel's real output to keep the budget honest */
+        int tot = 0, prevb = -1;
+        for (int b = 0; b < t->nbnd; b++) {
+            if (t->is_pns[b])
+                continue;
+            tot += nb[b][t->chosen[b]];
+            if (prevb >= 0)
+                tot += NMR_SFBITS((t->blo[b]+t->chosen[b]*NMR_STEP) - 
(t->blo[prevb]+t->chosen[prevb]*NMR_STEP));
+            prevb = b;
         }
+        s->nmr->counted[t->cur_ch] = tot;
     }
 
     /* SCALE_MAX_DIFF condition:
@@ -645,7 +555,7 @@ static void search_for_quantizers_nmr(AVCodecContext *avctx,
 
                 if (prev != -1)
                     sce->sf_idx[w*16+g] = av_clip(sce->sf_idx[w*16+g], prev - 
SCALE_MAX_DIFF, prev + SCALE_MAX_DIFF);
-                sce->band_type[w*16+g] = find_min_book(maxvals[w*16+g], 
sce->sf_idx[w*16+g]);
+                sce->band_type[w*16+g] = find_min_book(t->maxvals[w*16+g], 
sce->sf_idx[w*16+g]);
                 if (sce->band_type[w*16+g] <= 0) {
                     if (!ff_sfdelta_can_remove_band(sce, nextband, prev, 
w*16+g)) {
                         sce->band_type[w*16+g] = 1;
@@ -663,11 +573,8 @@ static void search_for_quantizers_nmr(AVCodecContext 
*avctx,
             }
         }
 
-        /* Every band, coded or not, must carry a chain-legal scalefactor: the
-         * codebook trellis (encode_window_bands_info) may later absorb a 
dropped
-         * band into a nonzero section, resurrecting it, and its sf then gets
-         * coded. Forward-fill with the previous coded sf (delta 0, cheapest);
-         * leading bands get the global gain. */
+        /* every band must carry a chain-legal scalefactor (re-clamp, codebook
+     * fixup, global gain) */
         if (prev != -1) {
             int last = sce->sf_idx[0];
             for (int w = 0; w < sce->ics.num_windows; w += 
sce->ics.group_len[w]) {
@@ -681,19 +588,414 @@ static void search_for_quantizers_nmr(AVCodecContext 
*avctx,
             }
         }
     }
-    return;
+}
 
-bail:
-    /* Nothing codeable in this channel. Leave a fully consistent state: any
-     * stale nonzero band_type acts as a codebook lower bound in the encoder's
-     * section trellis (encode_window_bands_info), which would forbid the zero
-     * section and resurrect the band with a stale, chain-illegal scalefactor.
-     * Pre-decided intensity bands keep their signalling. */
-    for (int i = 0; i < 128; i++) {
-        if (sce->band_type[i] == INTENSITY_BT || sce->band_type[i] == 
INTENSITY_BT2)
-            continue;
-        sce->zeroes[i]    = 1;
-        sce->band_type[i] = 0;
+/* Solve one element group (a solo channel, or a CPE pair pooled under one
+ * shared lambda and one pooled budget), then PNS and commit. */
+static void nmr_solve_group(AVCodecContext *avctx, AACEncContext *s,
+                            const float lambda, NMRSlot *const *sl, int nsl,
+                            int chans, int rc_eligible, int rc_global,
+                            int rc_rate_frame, int rc_bmax)
+{
+    const int cstep = NMR_COARSE > 0 ? NMR_COARSE : NMR_STEP;
+    int bch = ((avctx->flags & AV_CODEC_FLAG_QSCALE) ? 2.0f : 
avctx->ch_layout.nb_channels);
+    int destbits = avctx->bit_rate * 1024.0 / avctx->sample_rate / bch * 
(lambda / 120.f) * chans;
+    int is8_any = 0;
+    float lam;
+    float rc_off = 1.0f, lam_dem = 0.0f;
+
+    for (int k = 0; k < nsl; k++)
+        is8_any |= sl[k]->is8;
+
+    if (s->psy.bitres.alloc >= 0)
+        destbits = s->psy.bitres.alloc *
+                   (lambda / (avctx->global_quality ? avctx->global_quality : 
120)) * chans;
+    if (rc_global && s->psy.bitres.alloc >= 0) {
+        /* CBR target: nominal + repayment, bounded +-30%/frame */
+        double rr = avctx->bit_rate * 1024.0 / avctx->sample_rate;
+        destbits = (rr + av_clipd(s->nmr->rc_fill / 2.0, -0.3 * rr, 0.3 * rr)) 
* chans / s->channels;
+    } else if (rc_eligible && s->psy.bitres.alloc >= 0) {
+        /* pre-bootstrap CBR frames: target nominal (psy bitres is cold) */
+        destbits = (avctx->bit_rate * 1024.0 / avctx->sample_rate) * chans / 
s->channels;
+    }
+    destbits = FFMIN(destbits, 5800 * chans);
+    /* honest budget: subtract the measured non-trellis overhead (section 
data, ICS,
+     * sf/PNS signalling), which is rate-dependent hence adaptive. */
+    if (s->nmr->side_inited)
+        destbits = av_clip(destbits - (int)(s->nmr->side_ema * chans / 
s->channels), 64, 5800 * chans);
+
+    /* Held transient burst, bank-aware: spend banked bits, never borrow deep
+     * (payback troughs starve the next transient). */
+    if (s->nmr->run_burst > 1.0f) {
+        int extra = destbits * (s->nmr->run_burst - 1.0f);
+        int avail = FFMAX(0, (int)((s->nmr->rc_fill + rc_bmax / 2) * 
(int64_t)chans / s->channels));
+        destbits = av_clip(destbits + FFMIN(extra, avail), 64, 6800 * chans);
+    }
+
+    if (rc_global) {
+        /* corridor bisect around the servoed centre; pressure = stateless
+         * rc_off multiplier (folding it into lam_rc winds up) */
+        float R = avctx->bit_rate * 1024.0 / avctx->sample_rate;
+        float cen;
+        int tot, hardcap, rc_cap;
+        float lo;
+        rc_off = exp2f(-NMR_RC_K_CBR * s->nmr->rc_fill / R);
+        cen    = s->nmr->lam_rc * rc_off;
+        lo     = cen / NMR_RC_CORR;
+        /* transient burst: widen the lower bound so the boosted destbits can
+         * actually pour into the onset frame */
+        if (is8_any && s->nmr->run_burst > 1.0f)
+            lo /= s->nmr->run_burst;
+        lam = nmr_solve_slots(s, sl, nsl, cstep, destbits,
+                              lo, cen * NMR_RC_CORR, NMR_RC_CITERS);
+
+        tot = 0;
+        for (int k = 0; k < nsl; k++)
+            tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], cstep);
+        hardcap = av_clip((int)(5800.f * FFMIN(1.f, lambda / 120.f)), 256, 
5800) * chans;
+        /* legality cap only; no spend-floor (rc_off spends the bank) */
+        rc_cap   = FFMIN(hardcap, (s->nmr->rc_fill + rc_rate_frame + rc_bmax) 
* chans / s->channels);
+        if (tot > rc_cap) {
+            lam = nmr_solve_slots(s, sl, nsl, cstep, rc_cap, lam, 1e4f, 
NMR_CITERS);
+        }
+    } else {
+        /* per-frame bisection, warm-started off the previous frame's lambda;
+         * a result at the bracket edge means redo the full search */
+        float lam0 = s->nmr->lam[sl[0]->cur_ch];
+        lam = 1.0f;
+        if (NMR_COARSE > 0 && lam0 > 0.0f) {
+            lam = nmr_solve_slots(s, sl, nsl, cstep, destbits, lam0/32.0f, 
lam0*32.0f, NMR_CWARM);
+            if (lam < lam0/16.0f || lam > lam0*16.0f)
+                lam0 = 0.0f;
+        }
+        if (lam0 <= 0.0f)
+            lam = nmr_solve_slots(s, sl, nsl, cstep, destbits,
+                                  1e-9f, 1e4f, NMR_COARSE > 0 ? NMR_CITERS : 
NMR_ITERS);
+    }
+
+    /* PASS 2:
+     * refine each band at full granularity (NMR_STEP) in a +/-cstep window
+     * around the coarse pick, then re-solve. Recovers single-pass quality 
while the
+     * lambda search stayed cheap on the coarse grid. */
+    if (NMR_COARSE > 0) {
+        /* nmr_speed, 0 = slowest/best, higher = faster; see the option docs. 
*/
+        int win = NMR_COARSE - av_clip(s->options.nmr_speed, 0, 4);
+        for (int k = 0; k < nsl; k++) {
+            NMRSlot *t = sl[k];
+            float (*ndk)[NMR_NCAND] = s->nmr->nd[t->si];
+            int   (*nbk)[NMR_NCAND] = s->nmr->nb[t->si];
+            if (!t->nact)
+                continue;
+            /* the pow34 spectrum and the quantize cache are per-channel state 
*/
+            s->aacdsp.abs_pow34(s->scoefs, t->sce->coeffs, 1024);
+            ff_quantize_band_cost_cache_init(s);
+            for (int b = 0; b < t->nbnd; b++) {
+                int center = t->blo[b] + t->chosen[b]*cstep;
+                int flo    = av_clip(center - win, 
av_clip(t->minsf[t->bidx[b]], 0, SCALE_MAX_POS), SCALE_MAX_POS);
+                int maxn   = FFMIN(NMR_NCAND, 2*win/NMR_STEP + 1);
+                float invthr = 1.0f / FFMAX(t->thr[t->bidx[b]], 1e-9f);
+                int ncand  = nmr_band_curve(s, t->sce, t->bw[b], t->bg[b], 
t->bst[b], flo, NMR_STEP, maxn,
+                                            invthr, t->maxvals[t->bidx[b]], 
ndk[b], nbk[b]);
+                if (t->tnsg[t->bidx[b]] > 1.0f)
+                    for (int o = 0; o < ncand; o++)
+                        ndk[b][o] *= t->tnsg[t->bidx[b]];
+                t->blo[b] = flo;
+                t->bnc[b] = FFMAX(1, ncand);
+            }
+        }
+        /* fine pass: narrow corridor around the coarse solve */
+        if (rc_global)
+            lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, destbits, lam/2.0f, 
lam*2.0f, NMR_RC_FITERS);
+        else
+            lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, destbits, lam/16.0f, 
lam*16.0f, NMR_IFINE);
+    }
+
+    lam_dem = lam;   /* demand-solved lambda, pre bucket clamp: what content 
wants */
+
+    if (rc_global) {
+        /* legality clamp, then the quality slew limiter */
+        int hardcap = av_clip((int)(5800.f * FFMIN(1.f, lambda / 120.f)), 256, 
5800) * chans;
+        int tot = 0, rc_cap;
+        for (int k = 0; k < nsl; k++)
+            tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
+        rc_cap   = FFMIN(hardcap, (s->nmr->rc_fill + rc_rate_frame + rc_bmax) 
* chans / s->channels);
+        if (tot > rc_cap) {
+            lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, rc_cap, lam, 1e4f, 
NMR_RC_ITERS);
+        }
+        if (s->nmr->lam_slew > 0.0f) {
+            float kup, kdn;
+            /* hold lambda near-constant within short runs; bits follow 
content */
+            kup = (is8_any && s->nmr->prev_was_short) ? NMR_SLEW_RUN : 
NMR_SLEW;
+            /* a deliberate onset burst may dive as far as its widened corridor
+             * allows; the RECOVERY back up is what must stay gradual */
+            kdn = (is8_any && s->nmr->run_burst > 1.0f) ? NMR_SLEW * 
s->nmr->run_burst :
+                  (is8_any && s->nmr->prev_was_short)    ? NMR_SLEW_RUN : 
NMR_SLEW;
+            if (lam > s->nmr->lam_slew * kup || lam < s->nmr->lam_slew / kdn) {
+                lam = av_clipf(lam, s->nmr->lam_slew / kdn, s->nmr->lam_slew * 
kup);
+                tot = nmr_eval_slots(s, sl, nsl, NMR_STEP, lam);
+                /* never at the price of an illegal reservoir excursion */
+                if (tot > rc_cap) {
+                    lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, rc_cap, lam, 
1e4f, NMR_RC_ITERS);
+                }
+            }
+        }
+        s->nmr->lam_slew = lam;
+    }
+
+    for (int k = 0; k < nsl; k++)
+        s->nmr->lam[sl[k]->cur_ch] = lam;   /* warm start for the next frame */
+    {   /* nd: mean achieved dist/real-mask (dimensionless starvation +
+         * noise-class signal) */
+        float ndsum = 0.0f; int ndn = 0;
+        for (int k = 0; k < nsl; k++) {
+            NMRSlot *t = sl[k];
+            float (*ndk)[NMR_NCAND] = s->nmr->nd[t->si];
+            for (int b_ = 0; b_ < t->nact; b_++) {
+                int b = t->act[b_], bi = t->bidx[b];
+                if (t->thr_real[bi] > 0.0f && t->thr[bi] > 0.0f) {
+                    ndsum += ndk[b][t->chosen[b]] * t->thr[bi] / 
t->thr_real[bi];
+                    ndn++;
+                }
+            }
+        }
+        /* long frames only (short groups inflate the ratio) */
+        if (ndn >= 8 && !is8_any) {
+            float nd = ndsum / ndn;
+            s->nmr->nd_ema = s->nmr->nd_ema > 0.0f ?
+                             0.95f * s->nmr->nd_ema + 0.05f * nd : nd;
+        }
+    }
+    {   /* track short vs long operating lambda (dense-beat boost scaling) */
+        float *ema = is8_any ? &s->nmr->lam_short_ema : &s->nmr->lam_long_ema;
+        *ema = *ema > 0.0f ? 0.9f * *ema + 0.1f * lam : lam;
+        /* sustained-strain floor: snaps down at any comfortable moment,
+         * recovers only slowly, so bursty content cannot bank pressure
+         * credit between its lambda valleys. */
+        s->nmr->lam_floor = s->nmr->lam_floor > 0.0f ?
+            fminf(s->nmr->lam_floor * 1.02f, lam) : lam;
+    }
+    {   /* shared rate-pressure ramp: lambda vs nd-scaled anchors */
+        float scale, ramp;
+        scale = 1.0f + av_clipf(s->nmr->nd_ema / 50.0f, 0.0f, 8.0f);
+        ramp  = s->nmr->lam_long_ema > 0.0f ?
+                av_clipf((s->nmr->lam_long_ema - 120.0f * scale) /
+                         (350.0f * scale - 120.0f * scale), 0.0f, 1.0f) : 0.0f;
+        /* transparency veto: lambda*nd below ~74 = comfortable */
+        if (s->nmr->nd_ema > 0.0f)
+            ramp *= av_clipf((s->nmr->lam_long_ema * s->nmr->nd_ema - 60.0f) /
+                             (120.0f - 60.0f), 0.0f, 1.0f);
+        s->nmr->press = ramp;
+    }
+    if (rc_global) {
+        /* track the centre toward the CONTENT lambda (demand-solved, pressure
+         * divided out); clamped lambda is rate noise, not content */
+        float c = s->nmr->lam_rc * powf(lam_dem / rc_off / s->nmr->lam_rc, 
NMR_RC_TRACK);
+        s->nmr->lam_rc = av_clipf(c, 1e-6f, 1e4f);
+    } else if (rc_eligible) {
+        /* bootstrap the servo off the first substantive frame (silent lead-ins
+         * have degenerate budgets) */
+        int nbnd_max = 0;
+        for (int k = 0; k < nsl; k++)
+            nbnd_max = FFMAX(nbnd_max, sl[k]->nbnd);
+        if (nbnd_max >= 8) {
+            s->nmr->lam_rc  = av_clipf(lam, 1e-4f, 1e4f);
+            s->nmr->lam_slew = s->nmr->lam_rc;
+        }
+    }
+
+    {   /* PNS, per channel at the group's operating lambda */
+        const float pns_lam = NMR_PNS_LAM;
+        int pns_total = 0;
+        for (int k = 0; k < nsl; k++) {
+            NMRSlot *t = sl[k];
+            const float (*ndk)[NMR_NCAND] = (const float 
(*)[NMR_NCAND])s->nmr->nd[t->si];
+            const int   (*nbk)[NMR_NCAND] = (const int 
(*)[NMR_NCAND])s->nmr->nb[t->si];
+            int pns_count = 0;
+            /* band 0 (lowest freq) is kept as the global-gain / sf-chain 
anchor */
+            for (int b = 1; b < t->nbnd; b++) {
+                int bi = t->bidx[b];
+                float spread = t->pspread[bi];
+                float nmr_pns, cost_keep, cost_pns, frac;
+                if (!t->sce->can_pns[bi])
+                    continue;
+
+                int was  = s->nmr->pns_prev[t->cur_ch & 15][bi];
+                float bias = was ? NMR_PNS_STAY : NMR_PNS_ENTER;
+                int want = 0, force_exit = 0;
+
+                /* (can_pns was already checked above; gates below fill 
`want`) */
+                if (t->pener[bi] > NMR_PNS_MAX_ET * t->thr_real[bi]) {
+                    force_exit = 1;                       /* loud-band guard */
+                } else if (lam > pns_lam) {
+                    /* Spectral-hole fill: a noise-like band left mostly empty 
*/
+                    frac = ndk[b][t->chosen[b]] * t->thr[bi] / 
FFMAX(t->pener[bi], 1e-9f);
+                    if (spread > NMR_PNS_HOLE_SPREAD &&
+                        frac > NMR_PNS_HOLE_FRAC * (was ? 0.7f : 1.0f)) {
+                        want = 1;
+                    } else if (ndk[b][t->chosen[b]] * t->thr[bi] >
+                               NMR_PNS_NDGATE * t->thr_real[bi] * (was ? 0.5f 
: 1.0f)) {
+                        /* replace only a band coded audibly badly; cost of
+                         * energy-matched noise = its non-noise-like fraction 
*/
+                        nmr_pns = FFMAX(0.0f, t->pener[bi] * (1.0f - 
spread*spread))
+                                  / FFMAX(t->thr[bi], 1e-9f);
+                        cost_keep = ndk[b][t->chosen[b]] + lam * 
nbk[b][t->chosen[b]];
+                        cost_pns  = nmr_pns + lam * NMR_PNS_BITS;
+                        want = cost_pns < cost_keep * bias;
+                    }
+                }
+                {   /* debounce; near-mask deletion candidates skip entry
+                     * (noise beats the ~silent rendition they'd get) */
+                    uint8_t *ron  = &s->nmr->pns_run_on [t->cur_ch & 15][bi];
+                    uint8_t *roff = &s->nmr->pns_run_off[t->cur_ch & 15][bi];
+                    int near = t->pener[bi] < 2.0f * t->thr_real[bi];
+                    if (want) { if (*ron  < 255) (*ron)++;  *roff = 0; }
+                    else      { if (*roff < 255) (*roff)++; *ron  = 0; }
+                    if (force_exit)
+                        want = 0;
+                    else if (!was)
+                        want = near ? want : *ron >= NMR_PNS_ON;
+                    else if (near)
+                        want = 1;   /* physics-hysteresis: noise until audible 
*/
+                    else
+                        want = !(*roff >= NMR_PNS_OFF);
+                }
+                if (want) {
+                    t->is_pns[b] = 1;
+                    pns_count++;
+                }
+            }
+            if (pns_count) {
+                t->nact = 0;
+                for (int b = 0; b < t->nbnd; b++)
+                    if (!t->is_pns[b])
+                        t->act[t->nact++] = b;
+            }
+            pns_total += pns_count;
+        }
+        if (pns_total) {
+            /* re-solve over the survivors: at fixed lambda the allocation is
+             * the same except for the repaired sf-delta chain; in bisection
+             * mode re-spend the freed budget */
+            if (rc_global)
+                nmr_eval_slots(s, sl, nsl, NMR_STEP, lam);
+            else
+                nmr_solve_slots(s, sl, nsl, NMR_STEP, destbits - pns_total * 
NMR_PNS_BITS,
+                                1e-9f, 1e4f, NMR_ITERS);
+        }
+    }
+
+    for (int k = 0; k < nsl; k++) {
+        NMRSlot *t = sl[k];
+        uint8_t *pp = s->nmr->pns_prev[t->cur_ch & 15];
+        uint8_t now[128] = {0};
+        for (int b = 0; b < t->nbnd; b++)
+            if (t->is_pns[b])
+                now[t->bidx[b]] = 1;
+        memcpy(pp, now, 128);
+    }
+    for (int k = 0; k < nsl; k++)
+        nmr_commit_channel(s, sl[k]);
+
+}
+
+static void search_for_quantizers_nmr(AVCodecContext *avctx,
+                                      AACEncContext *s,
+                                      SingleChannelElement *sce,
+                                      const float lambda)
+{
+    AACNMRCurves *n = s->nmr;
+    /* Global-lambda RC: one solve per frame at a servoed centre lambda; the 
reservoir
+     * holds the long-run mean rate. Bypassed for VBR (-q:a) and the bootstrap 
frame. */
+    int rc_eligible = !(avctx->flags & AV_CODEC_FLAG_QSCALE) && 
avctx->bit_rate > 0 &&
+                      avctx->bit_rate_tolerance != 0;
+    /* Signed reservoir; soft steering (bounded repay + rc_off), hard cap =
+     * legality only. */
+    int rc_rate_frame = avctx->bit_rate * 1024.0 / avctx->sample_rate;
+    int rc_bmax = FFMIN(FFMAX(6144 * s->channels - rc_rate_frame, 256), 
NMR_CBR_BUF * s->channels);
+
+    int rc_global, defer;
+    NMRSlot *t;
+
+    s->nmr->counted[s->cur_channel] = 0;
+
+    if (rc_eligible && !n->rc_fill_seeded) {
+        /* the decoder bit reservoir starts FULL: seed it so the head may 
frontload */
+        n->rc_fill = rc_bmax;
+        n->rc_fill_seeded = 1;
+    }
+    if (rc_eligible && avctx->frame_num != n->rc_frame_num) {
+        if (n->rc_frame_num > 0 && n->lam_rc > 0.0f)
+            n->rc_fill = av_clip(n->rc_fill + rc_rate_frame - 
s->last_frame_pb_count,
+                                 -rc_bmax, rc_bmax);
+        n->rc_frame_num = avctx->frame_num;
+        n->pending = 0;    /* a deferred first channel never crosses a frame */
+        /* latch the RC mode per frame: a mid-frame bootstrap must not flip
+         * the CPE defer logic between channels */
+        n->rc_gl = rc_eligible && n->lam_rc > 0.0f;
+
+        /* Transient burst run state: set at run start and held across the run 
so
+         * coding stays uniform; repaid from the reservoir's steady stretches. 
*/
+        int is_short = sce->ics.window_sequence[0] == EIGHT_SHORT_SEQUENCE;
+        if (is_short) {
+            if (!n->prev_was_short) {           /* run start */
+                if (n->frames_since_short >= NMR_BURST_GAP) {
+                    n->run_burst = NMR_BURST_GAIN;
+                } else {
+                    /* dense-beat boost, scaled by measured short-frame 
starvation */
+                    float imb = 0.0f;
+                    if (n->lam_long_ema > 0.0f && n->lam_short_ema > 0.0f)
+                        imb = av_clipf(n->lam_short_ema / n->lam_long_ema - 
1.0f,
+                                       0.0f, 1.0f);
+                    n->run_burst = 1.0f + (NMR_SHORT_BOOST - 1.0f) * imb *
+                                   n->frames_since_short / 
(float)NMR_BURST_GAP;
+                }
+            }
+            n->frames_since_short = 0;
+        } else {
+            /* the frame closing a run (the STOP) absorbs the corridor recoil
+             * of the boosted shorts; give it half the run's factor so the
+             * repayment spreads into the steady stretch instead */
+            n->run_burst = n->prev_was_short ? sqrtf(n->run_burst) : 1.0f;
+            n->frames_since_short++;
+        }
+        n->prev_was_short = is_short;
+    }
+    rc_global = rc_eligible && n->rc_gl;
+
+    /* CPE budget pool: under global-lambda RC, defer the pair's first channel
+     * and solve both against one pooled budget when the second one arrives. */
+    defer = n->pair && rc_global;
+
+    t = &n->slot[(defer && n->pending) ? 1 : 0];
+    t->si = (defer && n->pending) ? 1 : 0;
+
+    if (!nmr_setup_channel(avctx, s, sce, t)) {
+        nmr_bail_channel(sce);
+        t->nbnd = t->nact = 0;
+    }
+
+    if (defer && !n->pending) {
+        n->pending = 1;                          /* wait for the partner 
channel */
+        return;
+    }
+
+    {
+        NMRSlot *sl[2];
+        int nsl = 0, chans = 1;
+        if (defer) {
+            n->pending = 0;
+            chans = 2;
+            if (n->slot[0].nact)
+                sl[nsl++] = &n->slot[0];
+            if (n->slot[1].nact)
+                sl[nsl++] = &n->slot[1];
+        } else if (t->nact) {
+            sl[nsl++] = t;
+        }
+        if (!nsl)
+            return;                              /* nothing codeable in the 
group */
+        nmr_solve_group(avctx, s, lambda, sl, nsl, chans,
+                        rc_eligible, rc_global, rc_rate_frame, rc_bmax);
     }
 }
 
diff --git a/libavcodec/aacenc.h b/libavcodec/aacenc.h
index 67069cbdf1..ec04465a6d 100644
--- a/libavcodec/aacenc.h
+++ b/libavcodec/aacenc.h
@@ -173,9 +173,57 @@ typedef struct AACQuantizeBandCostCacheEntry {
 /**
  * NMR coder per-band candidate cost curves (~96 KiB) and rate-control 
carry-over
  */
+/**
+ * Per-channel trellis state for one solve. A channel pair (CPE) is solved
+ * jointly against a pooled budget: the first channel's setup is stored here
+ * and committed together with the second channel under one shared lambda.
+ */
+typedef struct NMRSlot {
+    struct SingleChannelElement *sce;
+    int   si;                                    ///< curve-bank index (nd/nb 
slot)
+    int   cur_ch;                                ///< encoder channel index 
(psy/cache context)
+    int   nbnd;                                  ///< coded-band count, 0 = 
nothing codeable
+    int   is8;                                   ///< EIGHT_SHORT frame
+    int   bidx[128];                             ///< sce band index (w*16+g)
+    int   bw[128], bg[128], bst[128];            ///< window group, swb, coef 
start
+    int   blo[128];                              ///< finest candidate 
scalefactor
+    int   bnc[128];                              ///< number of candidates
+    int   chosen[128];
+    int   act[128];                              ///< active (non-PNS) band 
coding order
+    int   nact;
+    int   minsf[128];
+    float maxvals[128];
+    float thr[128];                              ///< allocation-law effective 
threshold
+    float thr_real[128];                         ///< real masking threshold 
(PNS gates)
+    float tnsg[128];                             ///< TNS synthesis gain per 
band for THIS solve (1 = uncovered), M/S-aware (pair max)
+    float pener[128];                            ///< band energy (PNS noise 
target)
+    float pspread[128];                          ///< band tonality spread (1 
= noise)
+    uint8_t is_pns[128];                         ///< band coded as noise
+} NMRSlot;
+
 typedef struct AACNMRCurves {
-    float nd[128][NMR_NCAND];                    ///< dist / threshold per 
candidate
-    int   nb[128][NMR_NCAND];                    ///< spectral bits per 
candidate
+    float nd[2][128][NMR_NCAND];                 ///< dist / threshold per 
candidate, per pair slot
+    int   nb[2][128][NMR_NCAND];                 ///< spectral bits per 
candidate, per pair slot
+    NMRSlot slot[2];                             ///< pair slots (solo solves 
use slot 0)
+    int   pair;                                  ///< current element is a 
CPE: pool the pair budget
+    int   rc_gl;                                 ///< rc_global latched at 
frame start: the corridor bootstrap must not flip the CPE defer logic between 
channels of one frame
+    int   rc_fill_seeded;                        ///< reservoir seeded full at 
stream start (decoder buffer starts full)
+    int   pending;                               ///< slot 0 holds a deferred 
first channel
+    uint8_t zero_prev[16][128];                  ///< per-channel band zero 
state last frame (zeroing hysteresis)
+    int     zero_nw[16];                         ///< window count zero_prev 
was recorded on
+    float thr_prev[16][64];                      ///< per-channel long-grid 
law thresholds of the previous frame
+    uint8_t thr_prev_ok[16];                     ///< thr_prev holds a 
long-frame measurement
+    uint8_t pns_prev[16][128];                   ///< per-channel PNS state 
last frame (decision hysteresis)
+    uint8_t pns_run_on[16][128];                 ///< consecutive frames the 
band has WANTED PNS
+    uint8_t pns_run_off[16][128];                ///< consecutive frames the 
band has wanted OUT
+    uint8_t smode[16][128];                      ///< per-pair previous stereo 
mode per band, two banks per pair (long/short grid): each grid's memory 
persists across the other's frames instead of being wiped at window switches
+    uint8_t smode_band[8][128];                  ///< last decided stereo mode 
per band index (side-band tests)
+    uint8_t tns8_prev[16];                       ///< short-TNS accepted last 
frame (per channel): Schmitt state for the accept bar
+    uint8_t sinit[16];                           ///< stereo state bank 
initialized
+    int     smode_nw[8];                         ///< window count the stored 
modes were decided on
+    float   sema_es[16][128];                     ///< smoothed side energy 
per band (stereo-decision EMA)
+    float   sema_em[16][128];                     ///< smoothed mid energy per 
band
+    float   sema_img[16][128];                    ///< smoothed I/S 
image-error/mask ratio per band
     float lam[16];                               ///< per-channel operating 
lambda of the previous frame, 0 = none yet
     int   counted[16];                           ///< per-channel bits the 
trellis accounted for in the last solve
     float side_ema;                              ///< running estimate of 
real-minus-counted bits per frame
@@ -187,6 +235,12 @@ typedef struct AACNMRCurves {
     int     frames_since_short;                  ///< long-block frames since 
the last short run (the "gap"): large = isolated transient
     int     prev_was_short;                      ///< previous frame was a 
short block (for run-start detection)
     float   run_burst;                           ///< transient bit-burst 
factor, set at run start and held across the short run
+    float   lam_slew;                            ///< final operating lambda 
of the previous RC frame (slew-limiter state)
+    float   nd_ema;                              ///< smoothed achieved 
distortion/real-mask over long-frame coded bands (1 = at threshold; >>1 flags 
psy-unreliable noise-class content)
+    float   press;                               ///< rate-pressure ramp 
[0,1]: lambda EMA against anchors that scale up when nd_ema flags noise-class 
content (psy masks unreliable there, lambda reads inflated)
+    float   lam_short_ema;                       ///< smoothed operating 
lambda of short frames
+    float   lam_long_ema;                        ///< smoothed operating 
lambda of long frames
+    float   lam_floor;                           ///< lambda min-tracker 
(snaps down, +2%/frame up): sustained-strain floor; bursty spikes at a 
comfortable rate cannot raise it
 } AACNMRCurves;
 
 typedef struct AACPCEInfo {
diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata 
b/tests/ref/fate/id3v2-reenc-delete-metadata
index 608d7fe7e4..a9ec6cec02 100644
--- a/tests/ref/fate/id3v2-reenc-delete-metadata
+++ b/tests/ref/fate/id3v2-reenc-delete-metadata
@@ -1,5 +1,5 @@
-31f137d9ea26baee95f75083ac87f60a 
*tests/data/fate/id3v2-reenc-delete-metadata.nut
-2396 tests/data/fate/id3v2-reenc-delete-metadata.nut
+d870ccc9affa856943d6bce9fec4451f 
*tests/data/fate/id3v2-reenc-delete-metadata.nut
+2956 tests/data/fate/id3v2-reenc-delete-metadata.nut
 [FORMAT]
 TAG:title=7rk
 [/FORMAT]
diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata-keep 
b/tests/ref/fate/id3v2-reenc-delete-metadata-keep
index c1562da559..1476c029b2 100644
--- a/tests/ref/fate/id3v2-reenc-delete-metadata-keep
+++ b/tests/ref/fate/id3v2-reenc-delete-metadata-keep
@@ -1,5 +1,5 @@
-805cef2308c41ebbf8e772f166aafe2e 
*tests/data/fate/id3v2-reenc-delete-metadata-keep.nut
-2524 tests/data/fate/id3v2-reenc-delete-metadata-keep.nut
+d3f8b96016d742eece05c82f1dace17d 
*tests/data/fate/id3v2-reenc-delete-metadata-keep.nut
+3084 tests/data/fate/id3v2-reenc-delete-metadata-keep.nut
 [FORMAT]
 TAG:title=7rk
 TAG:iTunSMPB= 00000000 00000210 0000086A 0000000000066486 00000000 0002DA9D 
00000000 00000000 00000000 00000000 00000000 00000000
diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata-keep-format 
b/tests/ref/fate/id3v2-reenc-delete-metadata-keep-format
index d8acf33159..92c6fd3548 100644
--- a/tests/ref/fate/id3v2-reenc-delete-metadata-keep-format
+++ b/tests/ref/fate/id3v2-reenc-delete-metadata-keep-format
@@ -1,5 +1,5 @@
-805cef2308c41ebbf8e772f166aafe2e 
*tests/data/fate/id3v2-reenc-delete-metadata-keep-format.nut
-2524 tests/data/fate/id3v2-reenc-delete-metadata-keep-format.nut
+d3f8b96016d742eece05c82f1dace17d 
*tests/data/fate/id3v2-reenc-delete-metadata-keep-format.nut
+3084 tests/data/fate/id3v2-reenc-delete-metadata-keep-format.nut
 [FORMAT]
 TAG:title=7rk
 TAG:iTunSMPB= 00000000 00000210 0000086A 0000000000066486 00000000 0002DA9D 
00000000 00000000 00000000 00000000 00000000 00000000
diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata-keep-stream 
b/tests/ref/fate/id3v2-reenc-delete-metadata-keep-stream
index 02827345a3..1b120f7c58 100644
--- a/tests/ref/fate/id3v2-reenc-delete-metadata-keep-stream
+++ b/tests/ref/fate/id3v2-reenc-delete-metadata-keep-stream
@@ -1,5 +1,5 @@
-31f137d9ea26baee95f75083ac87f60a 
*tests/data/fate/id3v2-reenc-delete-metadata-keep-stream.nut
-2396 tests/data/fate/id3v2-reenc-delete-metadata-keep-stream.nut
+d870ccc9affa856943d6bce9fec4451f 
*tests/data/fate/id3v2-reenc-delete-metadata-keep-stream.nut
+2956 tests/data/fate/id3v2-reenc-delete-metadata-keep-stream.nut
 [FORMAT]
 TAG:title=7rk
 [/FORMAT]
diff --git a/tests/ref/fate/id3v2-reenc-delete-metadata-map-metadata 
b/tests/ref/fate/id3v2-reenc-delete-metadata-map-metadata
index ec640b7b55..bde3194636 100644
--- a/tests/ref/fate/id3v2-reenc-delete-metadata-map-metadata
+++ b/tests/ref/fate/id3v2-reenc-delete-metadata-map-metadata
@@ -1,5 +1,5 @@
-31f137d9ea26baee95f75083ac87f60a 
*tests/data/fate/id3v2-reenc-delete-metadata-map-metadata.nut
-2396 tests/data/fate/id3v2-reenc-delete-metadata-map-metadata.nut
+d870ccc9affa856943d6bce9fec4451f 
*tests/data/fate/id3v2-reenc-delete-metadata-map-metadata.nut
+2956 tests/data/fate/id3v2-reenc-delete-metadata-map-metadata.nut
 [FORMAT]
 TAG:title=7rk
 [/FORMAT]

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

Reply via email to