Commit 98afaae587 for ffmpeg
commit 98afaae587865cfe451b3ff2f5be0b97190edefd
Author: Lynne <dev@lynne.ee>
Date: Wed Sep 23 20:32:59 2026 +0900
avcodec/aaccoder_nmr, aacenc, aacpsy: -aac_rc abr
-aac_rc cbr|abr (default cbr). ABR runs the nd-target quality solve with
the set-point owned by a slow rate servo, and joins VBR as a
quality-target mode in the psy model (fixed-reference PE reduction,
quality attack map, broadband mask floor).
The servo integrates the log rate error but applies it to the nd
set-point in rare, discrete, calm-gated steps. The set-point must be
quasi-static: any drift on content timescales moves lambda, and with it
band/PNS/scalefactor state, tracking sections in antiphase and
coarsening exactly what needs bits - measured 2x worse than a fixed
target at equal mean rate. Locally this mode IS fixed-target VBR; rate
honesty converges on the minutes scale.
Bootstrap. The set-point is seeded from the VBR calibration anchor (one
constant, NMR_VBR_ANCHOR: the two are required to agree, and moving only
one copy made ABR overshoot its rate target by up to 9%). After ~2s of
fast rate measurement off a representative window (an all-transient
head reads over-target and would coarsen the very content that needs
bits), an open-loop jump over the measured loop gain (~ -0.24 log2 rate
per target unit) corrects the seed's per-content error, applied at
1.2x - the estimator's own inverse warm-up factor, since the EMA is
seeded on target (a cold start must not read as a fake deficit) and so
recovers only ~82% of the error. One correction is not enough whatever
the gain estimate, because the 0.24 loop gain is only nominal: the boot
re-arms while the rate is still off, up to a bounded number of times,
re-measuring each time, and re-seeds the estimator on target rather than
to zero (a zero estimate pins the integrator at its clip via log2f(0)
and reads as a 100% deficit to the fill in the same frame). Its settle
window sits below the stepper's hold, or the stepper resets the shared
counter first and the re-arm never fires.
Every set-point correction is glided in at ~0.5 log2-units/s rather than
stepped: a one-frame noise-floor jump at a fixed stream time (the boot
lands at ~2s in every stream) is audible against revealing content even
at step sizes the corpus cannot see.
Bits-fill. Under the long-run target, an easy frame is topped up toward
the ask by moving lambda finer only - the quality target is a floor,
never traded away. It spends the rate the user asked for on sub-mask
margin the nd statistic cannot see (it clips at -4/band); without it,
stat-transparent content caps the rate below any ask. The fill runs from
frame one and fades continuously as the rate EMA converges (a boot-gated
fill flipped regimes at ~2s, an audible quality step), follows the psy
PE demand shape, and is slew-bounded, writing the lambda continuity
state from long frames only instead of free-diving up to 64x per frame.
The un-booted stream head behaves CBR-like until the servo takes over
(love.flac: >6k energy had swung 0.61-1.83x vs the source at frame rate
while the head ran 25% under the ask).
The reachable-range clamps do not track the calibration anchor: their
floors are set by the stat's -4/band sub-mask clip. A floor at -1.65
railed the servo on easy content (male_speech at a 128k ask pinned at
the clamp with a 10% standing deficit); -3.0 keeps 384k asks reachable
without the stall. Easy content can still fall short of very high asks
(main_theme: 196k at a 256k ask) - there is no padding. The coarse
clamp is a quality guard: set-points past it buy little rate for a lot
of quality (2.5 -> 6.0 at a 48k ask: -5% rate, +32% Zimtohrli), so asks
below about 56 kbps stereo land above the ask (24k/32k asks: ~40k).
The documentation points those rates at CBR.
Rate accuracy against the -b:a ask:
corpus bias mean|err| worst
IgorC-3 -1.3% 2.2% -11.8%
IgorC-1 +1.1% 2.0% +5.0%
32 random -0.3% 2.2% -10.0%
A 4.5-minute concatenation of the IgorC-3 samples lands at 128.2 kbps
on a 128k ask. castanets/velvet ABR beat CBR; ABR corpus 0.00060 at
126k.
-aac_rc is documented, and fate-aac-nmr-abr-encode pins the per-packet
sizes through the servo's boot.
diff --git a/doc/encoders.texi b/doc/encoders.texi
index 98c5f22da5..fcb085f2b8 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -85,6 +85,22 @@ and much faster at higher bitrates.
@end table
+@item aac_rc
+Rate-control mode of the @samp{nmr} coder when a bitrate is set with @option{b}
+(other coders ignore it; @option{q} takes precedence). Possible values:
+
+@table @samp
+@item cbr
+Constant bitrate within the decoder's bit reservoir. This is the default.
+
+@item abr
+Average bitrate: a constant-quality target, as in VBR, whose set-point a slow
+servo moves to meet @option{b} over the length of the stream. Local quality
+stays constant, so short files and individual passages can deviate from the
+requested rate; the average converges over minutes rather than seconds. Below
+roughly 56 kbps stereo the rate lands above the request; use @samp{cbr} there.
+@end table
+
@item aac_ms
Sets mid/side coding mode. The default value of "auto" will automatically use
M/S with bands which will benefit from such coding. Can be forced for all bands
diff --git a/libavcodec/aaccoder_nmr.h b/libavcodec/aaccoder_nmr.h
index 398cc91059..fd7d6e1cbc 100644
--- a/libavcodec/aaccoder_nmr.h
+++ b/libavcodec/aaccoder_nmr.h
@@ -91,18 +91,45 @@
/* Quality-target calibration anchor: the nd set-point of -q:a 1, which lands
* near 128-136 kbps stereo on the tuning corpora with the q ladder's own
- * bandwidth. It moved -2.1 -> -0.75 with the quality-target mask floor
- * (PSY_THRFL_QUALITY), which lowers thr_real and so lifts the whole statistic.
- * The reachable-range clamp is set by the stat's -4/band sub-mask clip, which
- * the mask floor does not touch: targets below it are asymptotically
- * unreachable - the bisect rails at the finest lambda, the frame exceeds the
- * decoder buffer and the outer re-encode loop never converges. */
+ * bandwidth. The ABR servo seeds off the same constant (referenced to 81.5
+ * kbps/ch, where it measured before the bandwidth retune; the boot corrects
+ * the per-content seed error), so they are one constant. It moved
+ * -2.1 -> -0.75 with the quality-target mask floor (PSY_THRFL_QUALITY), which
+ * lowers thr_real and so lifts the whole statistic.
+ * The reachable-range clamps did NOT move
+ * with it: their floors are set by the stat's -4/band sub-mask clip, which the
+ * mask floor does not touch. Targets below them are asymptotically unreachable
+ * - the bisect rails at the finest lambda, the frame exceeds the decoder buffer
+ * and the outer re-encode loop never converges (the 384k stall). Raising them
+ * with the anchor railed the ABR servo instead: it wanted a finer target to
+ * meet its rate ask on easy content and could not ask for one. */
#define NMR_VBR_ANCHOR (-0.75f)
#define NMR_VBR_TMIN (-3.8f)
+/* ABR set-point range. The coarse end is a quality guard: set-points past it
+ * buy little rate for a lot of quality (2.5 -> 6.0 at a 48k ask: -5% rate,
+ * +32% Zimtohrli), so asks below ~56 kbps stereo land above the ask - CBR
+ * is the mode for those rates. At the fine end, easy content can fall short
+ * of very high asks (no padding). */
+#define NMR_ABR_TMIN (-3.0f)
+#define NMR_ABR_TMAX 2.5f
+
+/* ABR servo: integrator gain on the log rate error, rate EMA weight after the
+ * boot, set-point step size and minimum frames between steps */
+#define NMR_ABR_K 0.003f
+#define NMR_ABR_EMA 0.0023f
+#define NMR_ABR_STEP 0.15f
+#define NMR_ABR_HOLD 120
+/* ABR boot: open-loop correction gain over the nominal loop gain, the rate
+ * error that re-arms it, the re-arm budget and the settle time before one */
+#define NMR_ABR_BOOT_GAIN 1.2f
+#define NMR_ABR_TOL 0.02f
+#define NMR_ABR_BOOTS 6
+#define NMR_ABR_SETTLE 100
/* VBR shorts: the measured stat inflation counts fully once this share of
* the frame's energy sits above 6 kHz */
#define NMR_INFL_HF 0.10f
+
/* 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,
@@ -754,9 +781,14 @@ static void nmr_solve_group(AVCodecContext *avctx, AACEncContext *s,
}
int vbr = (avctx->flags & AV_CODEC_FLAG_QSCALE) && s->nmr;
+ int abr = !vbr && s->options.rc == 1 && avctx->bit_rate > 0 && s->nmr;
int vbr_subst = 1;
float vbr_t = 0.0f;
- if (vbr) {
+ if (abr) {
+ /* ABR: same quality-target solve, set-point owned by the rate servo */
+ vbr_t = s->nmr->abr_t;
+ vbr = 1;
+ } else if (vbr) {
/* nd-target VBR: constant achieved noise-to-mask, bits float. */
/* target in log2(dist/real-mask), anchored so -q:a 1 lands near
* 128 kbps stereo on the tuning corpus; higher q is finer, each q
@@ -1011,6 +1043,48 @@ static void nmr_solve_group(AVCodecContext *avctx, AACEncContext *s,
hardcap = FFMAX(hardcap - (int)(FFMAX(s->nmr->side_ema, 0.0f) * chans / s->channels), 256);
for (int k = 0; k < nsl; k++)
tot += nmr_slot_bits(sl[k], s->nmr->nb[sl[k]->si], NMR_STEP);
+ if (abr && s->nmr->abr_ema < rc_rate_frame) {
+ /* bits-fill: under the long-run target, top an easy frame up
+ * toward the ask by moving lambda FINER only - the quality
+ * target is a floor, never traded away. Spends the rate the
+ * user asked for on sub-mask margin the nd statistic cannot
+ * see (it clips at -4/band); without this, stat-transparent
+ * content caps the rate below any ask. Active from frame one
+ * (the immature EMA reads as a deficit, so the stream head is
+ * held near the ask) and fades continuously as the EMA
+ * converges - a boot-gated fill flips regimes at ~2s, an
+ * audible quality step.
+ * The fill target follows the psy PE demand shape: per-frame
+ * demand carries perceptual information the nd model does not
+ * (the constant-lambda lesson) - and routing it through the
+ * fill keeps the quality floor intact. */
+ float dshape = 1.0f;
+ int fill;
+ if (s->psy.bitres.alloc > 0) {
+ float *aema = &s->nmr->abr_alloc_ema;
+ if (*aema <= 0.0f) *aema = s->psy.bitres.alloc;
+ else *aema += 0.01f * (s->psy.bitres.alloc - *aema);
+ dshape = av_clipf(s->psy.bitres.alloc / *aema, 0.6f, 1.7f);
+ }
+ fill = (int)((rc_rate_frame + (rc_rate_frame - (int)s->nmr->abr_ema)) * dshape) *
+ chans / s->channels;
+ fill = FFMIN3(fill, 3 * rc_rate_frame * chans / s->channels / 2, hardcap);
+ if (tot < fill) {
+ /* the fill dive is the easy-frame operating point: slew it
+ * like any other lambda move and let it carry the
+ * continuity state, or per-frame depth variation reads as
+ * frame-rate HF wobble */
+ float flo = lam / 64.0f;
+ if (*vslew_st > 0.0f)
+ flo = FFMIN(FFMAX(flo, *vslew_st / NMR_SLEW), lam);
+ lam = nmr_solve_slots(s, sl, nsl, NMR_STEP, fill, flo, lam, NMR_RC_ITERS);
+ if (!is8_any)
+ *vslew_st = s->nmr->lam_slew = lam;
+ 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;
@@ -1280,7 +1354,7 @@ static void search_for_quantizers_nmr(AVCodecContext *avctx,
/* 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;
+ avctx->bit_rate_tolerance != 0 && s->options.rc == 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;
@@ -1291,6 +1365,108 @@ static void search_for_quantizers_nmr(AVCodecContext *avctx,
s->nmr->counted[s->cur_channel] = 0;
+ if (s->options.rc == 1 && !(avctx->flags & AV_CODEC_FLAG_QSCALE) &&
+ avctx->bit_rate > 0 && avctx->frame_num != n->abr_frame_num) {
+ /* ABR servo: integrate the log rate error, but apply it to the nd
+ * set-point in RARE, DISCRETE steps. The set-point must be
+ * quasi-static: any drift on content timescales moves lambda, and
+ * with it band/PNS/scalefactor state - measured 2x worse than a
+ * fixed target at equal mean rate. Locally this mode IS fixed-target
+ * VBR; rate honesty converges on the minutes scale. */
+ if (n->abr_frame_num == 0 && n->abr_ema <= 0.0f) {
+ /* seed the set-point from the VBR calibration anchor (see
+ * NMR_VBR_ANCHOR for the 81.5 kbps/ch reference); the servo
+ * trims the rest */
+ n->abr_t = av_clipf(NMR_VBR_ANCHOR - 2.5f * log2f(avctx->bit_rate /
+ (81500.0f * s->channels)), NMR_ABR_TMIN, NMR_ABR_TMAX);
+ n->abr_ema = rc_rate_frame;
+ } else if (s->last_frame_pb_count > 0) {
+ /* bootstrap: fast rate measurement for ~2s, then one open-loop
+ * jump over the measured loop gain (~ -0.24 log2 rate per target
+ * unit) corrects the seed's per-content error; the quasi-static
+ * stepper handles drift from there */
+ /* seed on-target: an EMA warming up from zero reads as a fake
+ * deficit and the fill overspends the whole stream head */
+ if (n->abr_ema <= 0.0f)
+ n->abr_ema = rc_rate_frame;
+ n->abr_ema += (n->abr_booted ? NMR_ABR_EMA : 0.02f) *
+ (s->last_frame_pb_count - n->abr_ema);
+ n->abr_hold++;
+ if (!n->abr_booted) {
+ n->abr_longs += sce->ics.window_sequence[0] != EIGHT_SHORT_SEQUENCE;
+ /* boot only off a REPRESENTATIVE window: an all-transient
+ * head (castanets roll) reads over-target and would coarsen
+ * the very content that needs bits. Time out eventually. */
+ if ((n->abr_hold >= 86 && n->abr_longs >= n->abr_hold / 2) ||
+ n->abr_hold >= 400) {
+ /* glide the seed correction in, never step it: a
+ * one-frame noise-floor jump at a fixed stream time is
+ * audible against revealing content */
+ n->abr_glide = NMR_ABR_BOOT_GAIN * log2f(n->abr_ema / rc_rate_frame) / 0.24f;
+ n->abr_booted = 1;
+ n->abr_boots++;
+ n->abr_hold = 0;
+ }
+ } else {
+ /* Re-arm the open-loop correction while the rate is still
+ * off. One boot leaves a residual whenever the real loop gain
+ * differs from the nominal 0.24, and the estimator is biased
+ * toward the ask by construction (the EMA is seeded ON target
+ * so a cold start cannot read as a deficit), so it understates
+ * the error it is correcting. The quasi-static stepper cannot
+ * drain that inside a track - measured, male_speech ended a
+ * 774-frame file 23% short and still moving. Re-measuring and
+ * firing again converges regardless of the gain estimate, and
+ * keeps the set-point quasi-static: corrections stay rare,
+ * glided, and bounded in number. */
+ if (n->abr_boots < NMR_ABR_BOOTS && n->abr_glide == 0.0f &&
+ n->abr_hold >= NMR_ABR_SETTLE &&
+ fabsf(log2f(n->abr_ema / rc_rate_frame)) > NMR_ABR_TOL) {
+ /* Re-seed the estimator ON target, exactly as the cold
+ * start does - never to zero. The value is read again
+ * before anything re-seeds it: by the integrator below
+ * (log2f(0) is -inf, which pinned the accumulator at its
+ * clip) and by the bits-fill later in this same frame
+ * (a zero EMA reads as a 100% deficit and authorises a
+ * one-frame overspend). Drop the residual integral with
+ * it: it describes the regime being left, and the boot
+ * is about to re-measure that same error open-loop. */
+ n->abr_booted = 0;
+ n->abr_ema = rc_rate_frame;
+ n->abr_acc = 0.0f;
+ n->abr_hold = 0;
+ n->abr_longs = 0;
+ } else {
+ n->abr_acc = av_clipf(n->abr_acc + NMR_ABR_K * log2f(n->abr_ema / rc_rate_frame),
+ -4.0f * NMR_ABR_STEP, 4.0f * NMR_ABR_STEP);
+ /* step in calm stretches (a set-point move during a transient
+ * section coarsens exactly what needs the bits) - but dense
+ * content must not deadlock the servo: after 3x the hold the
+ * step fires regardless. Large errors step at double size;
+ * the remainder carries over instead of being discarded. */
+ if (fabsf(n->abr_acc) >= NMR_ABR_STEP && n->abr_hold >= NMR_ABR_HOLD &&
+ (n->frames_since_short >= 12 || n->abr_hold >= 3 * NMR_ABR_HOLD)) {
+ float ms = NMR_ABR_STEP * (fabsf(n->abr_acc) > 3.0f * NMR_ABR_STEP ? 2.0f : 1.0f);
+ float step = av_clipf(n->abr_acc, -ms, ms);
+ n->abr_glide += step;
+ n->abr_acc -= step;
+ n->abr_hold = 0;
+ }
+ }
+ }
+ if (n->abr_glide != 0.0f) {
+ /* drain pending set-point corrections smoothly (~0.5
+ * log2-units/s): the decision stays quasi-static and
+ * calm-gated, only the application glides */
+ float d = av_clipf(n->abr_glide, -0.012f, 0.012f);
+ n->abr_t = av_clipf(n->abr_t + d, NMR_ABR_TMIN, NMR_ABR_TMAX);
+ n->abr_glide -= d;
+ if (fabsf(n->abr_glide) < 1e-4f)
+ n->abr_glide = 0.0f;
+ }
+ }
+ n->abr_frame_num = avctx->frame_num;
+ }
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;
diff --git a/libavcodec/aacenc.c b/libavcodec/aacenc.c
index e415f54247..b6d72e9368 100644
--- a/libavcodec/aacenc.c
+++ b/libavcodec/aacenc.c
@@ -1692,12 +1692,12 @@ static int aac_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
avpkt->size = put_bytes_output(&s->pb);
/* NMR reports its real operating lambda: the corridor centre in CBR,
- * the quality-mode slew state in VBR - the lambda of the last long
+ * the quality-mode slew state in VBR/ABR - the lambda of the last long
* operating-point solve, which short frames and legality re-solves do
* not update (the outer-loop s->lambda is never touched for this coder
* and would pin Qavg at its 120 init) */
s->lambda_sum += (s->nmr && s->nmr->lam_slew > 0.0f &&
- (avctx->flags & AV_CODEC_FLAG_QSCALE)) ?
+ ((avctx->flags & AV_CODEC_FLAG_QSCALE) || s->options.rc == 1)) ?
s->nmr->lam_slew :
(s->nmr && s->nmr->lam_rc > 0.0f) ? s->nmr->lam_rc : s->lambda;
s->lambda_count++;
@@ -1964,7 +1964,7 @@ static av_cold int aac_encode_init(AVCodecContext *avctx)
lengths[1] = ff_aac_num_swb_128[s->samplerate_index];
for (i = 0; i < s->chan_map[0]; i++)
grouping[i] = s->chan_map[i + 1] == TYPE_CPE;
- s->psy.unbounded_pe = (avctx->flags & AV_CODEC_FLAG_QSCALE) &&
+ s->psy.unbounded_pe = ((avctx->flags & AV_CODEC_FLAG_QSCALE) || s->options.rc == 1) &&
s->options.coder == AAC_CODER_NMR;
if ((ret = ff_psy_init(&s->psy, avctx, 2, sizes, lengths,
s->chan_map[0], grouping, s->bandwidth)) < 0)
@@ -1991,6 +1991,9 @@ static const AVOption aacenc_options[] = {
{"aac_pns", "Perceptual noise substitution", offsetof(AACEncContext, options.pns), AV_OPT_TYPE_BOOL, {.i64 = 1}, -1, 1, AACENC_FLAGS},
{"aac_tns", "Temporal noise shaping", offsetof(AACEncContext, options.tns), AV_OPT_TYPE_BOOL, {.i64 = 1}, -1, 1, AACENC_FLAGS},
{"aac_pce", "Forces the use of PCEs", offsetof(AACEncContext, options.pce), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, AACENC_FLAGS},
+ {"aac_rc", "Rate-control mode (NMR coder)", offsetof(AACEncContext, options.rc), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AACENC_FLAGS, .unit = "aac_rc"},
+ {"cbr", "Constant bitrate (corridor + leaky bucket)", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, INT_MAX, AACENC_FLAGS, .unit = "aac_rc"},
+ {"abr", "Average bitrate (constant-quality target, slow rate servo)", 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN, INT_MAX, AACENC_FLAGS, .unit = "aac_rc"},
{"aac_nmr_speed", "NMR coder speed level: 0 = slowest/best, higher trades quality for speed", offsetof(AACEncContext, options.nmr_speed), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 4, AACENC_FLAGS},
{"aac_allow_71wide", "Allow non-PCE use of 7.1(wide) channel layout", offsetof(AACEncContext, options.allow_71wide), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AACENC_FLAGS},
FF_AAC_PROFILE_OPTS
diff --git a/libavcodec/aacenc.h b/libavcodec/aacenc.h
index 9d2ba89056..057c2a59d4 100644
--- a/libavcodec/aacenc.h
+++ b/libavcodec/aacenc.h
@@ -72,6 +72,7 @@ typedef struct AACEncOptions {
int intensity_stereo;
int nmr_speed; ///< NMR coder speed level: 0 = slowest/best, higher is faster
int allow_71wide;
+ int rc; ///< rate-control mode: 0 = cbr (corridor+bucket), 1 = abr (nd-target + slow servo)
} AACEncOptions;
/**
@@ -248,6 +249,18 @@ typedef struct AACNMRCurves {
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
+
+ /* ABR: nd-target set-point servoed to hold the long-run mean rate */
+ float abr_t; ///< current nd target (log2 dist/mask)
+ float abr_glide; ///< pending set-point correction, drained per-frame (no discrete quality steps)
+ float abr_ema; ///< EMA of real frame bits
+ float abr_acc; ///< accumulated set-point correction
+ int abr_hold; ///< frames since the target last stepped
+ int abr_booted; ///< seed correction applied (re-armed while the rate is still off)
+ int abr_boots; ///< open-loop corrections fired so far
+ float abr_alloc_ema; ///< EMA of psy bit demand (fill shaping)
+ int abr_longs; ///< long frames seen during bootstrap
+ int64_t abr_frame_num; ///< once-per-frame servo guard
} AACNMRCurves;
typedef struct AACPCEInfo {
diff --git a/libavcodec/aacpsy.c b/libavcodec/aacpsy.c
index 0fd0c87b85..ac0e17de5b 100644
--- a/libavcodec/aacpsy.c
+++ b/libavcodec/aacpsy.c
@@ -332,14 +332,16 @@ static float lame_calc_attack_threshold(int bitrate)
/**
* LAME psy model specific initialization
*/
-static av_cold void lame_window_init(AacPsyContext *ctx, AVCodecContext *avctx)
+static av_cold void lame_window_init(FFPsyContext *fctx, AacPsyContext *ctx, AVCodecContext *avctx)
{
int i, j;
for (i = 0; i < avctx->ch_layout.nb_channels; i++) {
AacPsyChannel *pch = &ctx->ch[i];
- if (avctx->flags & AV_CODEC_FLAG_QSCALE)
+ if ((avctx->flags & AV_CODEC_FLAG_QSCALE) || fctx->unbounded_pe)
+ /* quality-target coders (VBR and ABR) switch windows for quality,
+ * not rate: use the quality attack map regardless of bit_rate */
pch->attack_threshold = psy_vbr_map[av_clip(avctx->global_quality / FF_QP2LAMBDA, 0, 10)].st_lrm;
else
pch->attack_threshold = lame_calc_attack_threshold(avctx->bit_rate / avctx->ch_layout.nb_channels / 1000);
@@ -456,7 +458,7 @@ static av_cold int psy_3gpp_init(FFPsyContext *ctx) {
for (i = 0; i < ctx->avctx->ch_layout.nb_channels; i++)
pctx->ch[i].rc_frame_num = -1;
- lame_window_init(pctx, ctx->avctx);
+ lame_window_init(ctx, pctx, ctx->avctx);
return 0;
}
@@ -953,7 +955,7 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, int channel,
* it cannot fail. A band we still choose to code must never be allowed
* noise within PSY_THRFL_* dB of its own energy, whatever the hole logic said. */
{
- int qmode = !!(ctx->avctx->flags & AV_CODEC_FLAG_QSCALE);
+ int qmode = ctx->unbounded_pe || (ctx->avctx->flags & AV_CODEC_FLAG_QSCALE);
float lim = qmode ? PSY_THRFL_QUALITY : PSY_THRFL_CBR;
float knee = qmode ? 0.0f : PSY_THRFL_CBR_KNEE;
float lo = ff_exp10f(-lim / 10.0f);
diff --git a/tests/fate/aac.mak b/tests/fate/aac.mak
index b1f49aff2a..7da94cf30b 100644
--- a/tests/fate/aac.mak
+++ b/tests/fate/aac.mak
@@ -268,6 +268,10 @@ fate-aac-yoraw-encode: FUZZ = 17
FATE_AAC_ENCODE_TRANSCODE += fate-aac-nmr-vbr-encode
fate-aac-nmr-vbr-encode: CMD = transcode wav $(TARGET_SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav adts "-af aresample -c:a aac -q:a 8 -t 2" "-c copy"
+# ABR through the rate servo's boot (~2s)
+FATE_AAC_ENCODE_TRANSCODE += fate-aac-nmr-abr-encode
+fate-aac-nmr-abr-encode: CMD = transcode wav $(TARGET_SAMPLES)/audio-reference/luckynight_2ch_44kHz_s16.wav adts "-af aresample -c:a aac -aac_rc abr -b:a 96k -t 3" "-c copy"
+
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
diff --git a/tests/ref/fate/aac-nmr-abr-encode b/tests/ref/fate/aac-nmr-abr-encode
new file mode 100644
index 0000000000..9418f60149
--- /dev/null
+++ b/tests/ref/fate/aac-nmr-abr-encode
@@ -0,0 +1,138 @@
+8a93613d6b0acab3fc863fda864a48e5 *tests/data/fate/aac-nmr-abr-encode.adts
+31792 tests/data/fate/aac-nmr-abr-encode.adts
+#tb 0: 1/28224000
+#media_type 0: audio
+#codec_id 0: aac
+#sample_rate 0: 44100
+#channel_layout_name 0: stereo
+0, 0, 0, 655360, 269, 0xfa9b83e0
+0, 655360, 655360, 655360, 464, 0xa230e9b1
+0, 1310720, 1310720, 655360, 260, 0x549a7ba3
+0, 1966080, 1966080, 655360, 262, 0xe5ce7d83
+0, 2621440, 2621440, 655360, 259, 0x26067677
+0, 3276800, 3276800, 655360, 261, 0x2bdc7bc4
+0, 3932160, 3932160, 655360, 244, 0xc16d750e
+0, 4587520, 4587520, 655360, 252, 0xb09d7762
+0, 5242880, 5242880, 655360, 263, 0x91157965
+0, 5898240, 5898240, 655360, 259, 0xb3e972f4
+0, 6553600, 6553600, 655360, 268, 0xcbe9813d
+0, 7208960, 7208960, 655360, 252, 0xf9c076f3
+0, 7864320, 7864320, 655360, 398, 0x2851be3c
+0, 8519680, 8519680, 655360, 320, 0x166d991d
+0, 9175040, 9175040, 655360, 408, 0xca4cd36f
+0, 9830400, 9830400, 655360, 239, 0x78c2742b
+0, 10485760, 10485760, 655360, 246, 0xca377334
+0, 11141120, 11141120, 655360, 240, 0x635e7250
+0, 11796480, 11796480, 655360, 220, 0x3bbd66ae
+0, 12451840, 12451840, 655360, 210, 0xf244638d
+0, 13107200, 13107200, 655360, 209, 0x64a6563b
+0, 13762560, 13762560, 655360, 221, 0xa96762cd
+0, 14417920, 14417920, 655360, 226, 0x749f66c4
+0, 15073280, 15073280, 655360, 226, 0xd0eb6387
+0, 15728640, 15728640, 655360, 223, 0xc3826574
+0, 16384000, 16384000, 655360, 220, 0x78a86d47
+0, 17039360, 17039360, 655360, 230, 0xb4446dc3
+0, 17694720, 17694720, 655360, 215, 0x306f7104
+0, 18350080, 18350080, 655360, 229, 0x91de69c3
+0, 19005440, 19005440, 655360, 219, 0x078e672d
+0, 19660800, 19660800, 655360, 214, 0xb5b766a4
+0, 20316160, 20316160, 655360, 220, 0x7023610b
+0, 20971520, 20971520, 655360, 210, 0x182a5ee8
+0, 21626880, 21626880, 655360, 203, 0xdf3655a9
+0, 22282240, 22282240, 655360, 231, 0x13d565d0
+0, 22937600, 22937600, 655360, 229, 0x5e0063f5
+0, 23592960, 23592960, 655360, 232, 0xf03468e7
+0, 24248320, 24248320, 655360, 244, 0x882a7785
+0, 24903680, 24903680, 655360, 236, 0xf8bd7012
+0, 25559040, 25559040, 655360, 228, 0x1956677a
+0, 26214400, 26214400, 655360, 266, 0x3e6b81a9
+0, 26869760, 26869760, 655360, 233, 0x5ab66d73
+0, 27525120, 27525120, 655360, 244, 0x25b56b66
+0, 28180480, 28180480, 655360, 184, 0xd9695100
+0, 28835840, 28835840, 655360, 229, 0x22f771cd
+0, 29491200, 29491200, 655360, 260, 0xc6fa8312
+0, 30146560, 30146560, 655360, 256, 0x48197e58
+0, 30801920, 30801920, 655360, 247, 0xea997935
+0, 31457280, 31457280, 655360, 239, 0xa22f7015
+0, 32112640, 32112640, 655360, 259, 0x8b2384c0
+0, 32768000, 32768000, 655360, 252, 0xd23e790d
+0, 33423360, 33423360, 655360, 240, 0x25056b8c
+0, 34078720, 34078720, 655360, 256, 0xab1077f8
+0, 34734080, 34734080, 655360, 238, 0xd99a6cc5
+0, 35389440, 35389440, 655360, 223, 0x4ffe6c3f
+0, 36044800, 36044800, 655360, 222, 0x8c7c5f7a
+0, 36700160, 36700160, 655360, 231, 0xcc3172bf
+0, 37355520, 37355520, 655360, 224, 0xc23b68cf
+0, 38010880, 38010880, 655360, 222, 0xf0986a4d
+0, 38666240, 38666240, 655360, 233, 0x92a36bc9
+0, 39321600, 39321600, 655360, 251, 0x11516fb9
+0, 39976960, 39976960, 655360, 263, 0x0f0b7e11
+0, 40632320, 40632320, 655360, 408, 0xbf1ccc26
+0, 41287680, 41287680, 655360, 531, 0xa5690ccd
+0, 41943040, 41943040, 655360, 308, 0x5eed9716
+0, 42598400, 42598400, 655360, 277, 0x3668903e
+0, 43253760, 43253760, 655360, 239, 0x2bab729b
+0, 43909120, 43909120, 655360, 227, 0x3c8b7095
+0, 44564480, 44564480, 655360, 190, 0x6c3257ab
+0, 45219840, 45219840, 655360, 209, 0x19125f77
+0, 45875200, 45875200, 655360, 196, 0xcec25cec
+0, 46530560, 46530560, 655360, 250, 0x3e2875d4
+0, 47185920, 47185920, 655360, 276, 0x37138362
+0, 47841280, 47841280, 655360, 210, 0x77d55f66
+0, 48496640, 48496640, 655360, 202, 0x88c06283
+0, 49152000, 49152000, 655360, 201, 0x364a5f76
+0, 49807360, 49807360, 655360, 218, 0x38ee6916
+0, 50462720, 50462720, 655360, 205, 0x017c6140
+0, 51118080, 51118080, 655360, 205, 0xba40628e
+0, 51773440, 51773440, 655360, 211, 0x3ab1628d
+0, 52428800, 52428800, 655360, 208, 0x9b636846
+0, 53084160, 53084160, 655360, 207, 0xcbd06506
+0, 53739520, 53739520, 655360, 207, 0x487f6291
+0, 54394880, 54394880, 655360, 218, 0x7aa870d9
+0, 55050240, 55050240, 655360, 256, 0x3495754e
+0, 55705600, 55705600, 655360, 335, 0xd95aa995
+0, 56360960, 56360960, 655360, 196, 0x54e75268
+0, 57016320, 57016320, 655360, 209, 0x04de5e1e
+0, 57671680, 57671680, 655360, 206, 0xa2f463a5
+0, 58327040, 58327040, 655360, 204, 0xad526074
+0, 58982400, 58982400, 655360, 209, 0xde2e5aec
+0, 59637760, 59637760, 655360, 209, 0x8c6d6905
+0, 60293120, 60293120, 655360, 202, 0x44ca5964
+0, 60948480, 60948480, 655360, 206, 0xd8685ef3
+0, 61603840, 61603840, 655360, 209, 0xff6360ce
+0, 62259200, 62259200, 655360, 207, 0xa9fc61b1
+0, 62914560, 62914560, 655360, 209, 0x312d6796
+0, 63569920, 63569920, 655360, 218, 0x4c9f695f
+0, 64225280, 64225280, 655360, 224, 0x34996723
+0, 64880640, 64880640, 655360, 234, 0x02096ec5
+0, 65536000, 65536000, 655360, 246, 0xa63a70c2
+0, 66191360, 66191360, 655360, 277, 0x8559914c
+0, 66846720, 66846720, 655360, 240, 0xa2087438
+0, 67502080, 67502080, 655360, 245, 0xc61a826b
+0, 68157440, 68157440, 655360, 248, 0xd92a7776
+0, 68812800, 68812800, 655360, 244, 0xa4517d63
+0, 69468160, 69468160, 655360, 258, 0x79cb75e2
+0, 70123520, 70123520, 655360, 274, 0x98708776
+0, 70778880, 70778880, 655360, 239, 0xcd83750c
+0, 71434240, 71434240, 655360, 232, 0x05336d82
+0, 72089600, 72089600, 655360, 234, 0x55847ac8
+0, 72744960, 72744960, 655360, 234, 0x76da6e4c
+0, 73400320, 73400320, 655360, 221, 0x919969bf
+0, 74055680, 74055680, 655360, 223, 0xfbfb69c2
+0, 74711040, 74711040, 655360, 228, 0xf9e373d7
+0, 75366400, 75366400, 655360, 224, 0xd0975f50
+0, 76021760, 76021760, 655360, 234, 0xa66d6e10
+0, 76677120, 76677120, 655360, 249, 0x2a276d92
+0, 77332480, 77332480, 655360, 239, 0x53516e65
+0, 77987840, 77987840, 655360, 270, 0xa6b77886
+0, 78643200, 78643200, 655360, 274, 0x076d7c58
+0, 79298560, 79298560, 655360, 269, 0x93fc811b
+0, 79953920, 79953920, 655360, 239, 0x9dfc73de
+0, 80609280, 80609280, 655360, 255, 0x6aa47d01
+0, 81264640, 81264640, 655360, 236, 0x755271f0
+0, 81920000, 81920000, 655360, 241, 0x8bab73ea
+0, 82575360, 82575360, 655360, 246, 0xa0ac7c7a
+0, 83230720, 83230720, 655360, 251, 0x74447d8a
+0, 83886080, 83886080, 655360, 223, 0xefc36a30
+0, 84541440, 84541440, 655360, 241, 0x88ab6e54
+0, 85196800, 85196800, 655360, 191, 0x89b45c00