This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit c8dc3cf4003cb09f9fc4b2d3dbce2d01c7b0f77e Author: Lynne <[email protected]> AuthorDate: Fri Jul 17 14:19:52 2026 +0800 Commit: Lynne <[email protected]> CommitDate: Sun Jul 19 20:41:53 2026 +0800 avcodec/aacpsy: sync block switching across channel pairs Implement the window_pair() hook: detect both channels of a CPE, then decide together - if either channel attacks, both switch, with one merged attack map so the grouping matches across the pair and common_window survives transients. Pairs the encoder has flagged as decoupled (joint tools dead) keep independent per-channel decisions, where forcing the steady channel short at every event in the other only costs quality. Attacks must also be NOVEL against the trailing high-pass max-envelope: a steady pitch-pulse train repeats its peak every period and re-fires the ratio test forever, producing long false short runs on periodic content (low brass and the like); a genuine onset towers over the recent past. The pre-echo threshold relaxation for gentler isolated onsets is kept, and START frames entering a short sequence clamp their thresholds toward the previous long frame's per-band values on the mapped long grid. --- libavcodec/aacpsy.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 10 deletions(-) diff --git a/libavcodec/aacpsy.c b/libavcodec/aacpsy.c index e38c43a323..e0faa2a19a 100644 --- a/libavcodec/aacpsy.c +++ b/libavcodec/aacpsy.c @@ -143,6 +143,9 @@ typedef struct AacPsyChannel{ int prev_attack; ///< attack value for the last short block in the previous sequence int next_attack0_zero; ///< whether attack[0] of the next frame is zero int frames_since_short; ///< consecutive long frames (pre-echo-aware isolated-onset gate) + float prev_frame_energy; ///< previous frame's full-band lookahead energy (attack veto) + int64_t win_count; ///< window() calls so far (frame counter for pair sync) + int64_t last_att; ///< win_count value of this channel's last own attack /* rate-loop re-analysis rewind state, see psy_3gpp_analyze() */ int64_t rc_frame_num; ///< frame this channel last saved rewind state for @@ -677,6 +680,23 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, int channel, float pe = pctx->chan_bitrate > 32000 ? 0.0f : FFMAX(50.0f, 100.0f - pctx->chan_bitrate * 100.0f / 32000.0f); const int num_bands = ctx->num_bands[wi->num_windows == 8]; const uint8_t *band_sizes = ctx->bands[wi->num_windows == 8]; + uint8_t s2l[16] = {0}; + int start_after_long = wi->num_windows == 8 && + wi->window_type[1] == LONG_START_SEQUENCE; + { /* short->long grid band map for cross-transition pre-echo control */ + if (start_after_long) { + const uint8_t *ls = ctx->bands[0]; + const int ln = ctx->num_bands[0]; + const uint8_t *ss = ctx->bands[1]; + int lacc = 0, sacc = 0, gl = 0; + for (int gs = 0; gs < num_bands && gs < 16; gs++) { + int center8 = (sacc + ss[gs] / 2) * 8; + while (gl < ln - 1 && lacc + ls[gl] <= center8) { lacc += ls[gl]; gl++; } + s2l[gs] = gl; + sacc += ss[gs]; + } + } + } AacPsyCoeffs *coeffs = pctx->psy_coef[wi->num_windows == 8]; const float avoid_hole_thr = wi->num_windows == 8 ? PSY_3GPP_AH_THR_SHORT : PSY_3GPP_AH_THR_LONG; const int bandwidth = ctx->cutoff ? ctx->cutoff : AAC_CUTOFF(ctx->avctx); @@ -708,6 +728,12 @@ static void psy_3gpp_analyze_channel(FFPsyContext *ctx, int channel, if (!(wi->window_type[0] == LONG_STOP_SEQUENCE || (!w && wi->window_type[1] == LONG_START_SEQUENCE))) band->thr = FFMAX(PSY_3GPP_RPEMIN*band->thr, FFMIN(band->thr, PSY_3GPP_RPELEV*pch->prev_band[w+g].thr_quiet)); + else if (!w && start_after_long) + /* w0 after a START frame: grid-mapped, scaled continuity + * clamp instead of the spec's skip (cannot bind on noise + * content - see memory - but correct for tonal) */ + band->thr = FFMAX(PSY_3GPP_RPEMIN*band->thr, FFMIN(band->thr, + PSY_3GPP_RPELEV*pch->prev_band[s2l[FFMIN(g,15)]].thr / 8.0f)); /* 5.6.1.3.1 "Preparatory steps of the perceptual entropy calculation" */ pe += calc_pe_3gpp(band); @@ -925,16 +951,16 @@ static void lame_apply_block_type(AacPsyChannel *ctx, FFPsyWindowInfo *wi, int u ctx->next_window_seq = blocktype; } -static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, - const float *la, int channel, int prev_type) +/* Attack detection half of the LAME window decision: everything up to (and + * excluding) the block-type state machine. Fills attacks[] and returns the raw + * uselongblock; mutates only the detection history. Split out so a channel + * pair can be detected first and DECIDED together (synced block switching). */ +static int psy_lame_detect(AacPsyContext *pctx, AacPsyChannel *pch, + const float *la, int channel, int prev_type, + int attacks[AAC_NUM_BLOCKS_SHORT + 1]) { - AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data; - AacPsyChannel *pch = &pctx->ch[channel]; - int grouping = 0; int uselongblock = 1; - int attacks[AAC_NUM_BLOCKS_SHORT + 1] = { 0 }; int i; - FFPsyWindowInfo wi = { { 0 } }; if (la) { float hpfsmpl[AAC_BLOCK_SIZE_LONG]; @@ -975,8 +1001,15 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, attack_intensity[i + PSY_LAME_NUM_SUBBLOCKS] = p; } - { /* pre-echo-aware threshold relaxation, see PSY_LAME_PE_* */ + { /* pre-echo-aware threshold relaxation + periodicity/novelty veto (a + * pitch-pulse train repeats its peak; a real onset towers) */ float frame_peak = 1.0f; + float eh[8 + (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS]; + const float nov_gate = 1.25f; + for (i = 0; i < 8; i++) + eh[i] = pch->prev_energy_subshort[8 + i]; + for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS; i++) + eh[8 + i] = energy_subshort[i]; for (i = PSY_LAME_NUM_SUBBLOCKS; i < (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS; i++) frame_peak = FFMAX(frame_peak, energy_subshort[i]); for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS; i++) @@ -986,8 +1019,16 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, pch->frames_since_short >= PSY_LAME_PE_GAP && energy_subshort[i - PSY_LAME_NUM_SUBBLOCKS] < PSY_LAME_PE_QUIET * frame_peak) thr *= PSY_LAME_PE_RED; - if (attack_intensity[i] > thr) + if (attack_intensity[i] > thr) { + if (nov_gate > 0.0f && i >= PSY_LAME_NUM_SUBBLOCKS) { + float prevmax = 1.0f; + for (int k = 3; k <= 8; k++) + prevmax = FFMAX(prevmax, eh[8 + i - k]); + if (energy_subshort[i] < nov_gate * prevmax) + continue; /* periodic, not an onset */ + } attacks[i / PSY_LAME_NUM_SUBBLOCKS] = (i % PSY_LAME_NUM_SUBBLOCKS) + 1; + } } } @@ -1009,6 +1050,26 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, att_sum += attacks[i]; } + { /* novelty of each attacking sub-block against the trailing HP + * max-envelope (~1-2 pitch periods): a periodic pulse train repeats + * its peak every period (novelty ~1), a genuine onset towers over + * the recent past. Instrumentation only. */ + float eh[8 + (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS]; + float novmax = 0.0f; + for (i = 0; i < 8; i++) + eh[i] = pch->prev_energy_subshort[8 + i]; + for (i = 0; i < (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS; i++) + eh[8 + i] = energy_subshort[i]; + for (i = PSY_LAME_NUM_SUBBLOCKS; i < (AAC_NUM_BLOCKS_SHORT + 1) * PSY_LAME_NUM_SUBBLOCKS; i++) { + if (attacks[i / PSY_LAME_NUM_SUBBLOCKS] == (i % PSY_LAME_NUM_SUBBLOCKS) + 1) { + float prevmax = 1.0f; + for (int k = 3; k <= 8; k++) + prevmax = FFMAX(prevmax, eh[8 + i - k]); + novmax = FFMAX(novmax, eh[8 + i] / prevmax); + } + } + } + if (pch->next_attack0_zero) attacks[0] = 0; pch->next_attack0_zero = !attacks[AAC_NUM_BLOCKS_SHORT]; @@ -1028,11 +1089,26 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, attacks[i] = 0; } - pch->frames_since_short = uselongblock ? pch->frames_since_short + 1 : 0; } else { /* We have no lookahead info, so just use same type as the previous sequence. */ uselongblock = !(prev_type == EIGHT_SHORT_SEQUENCE); } + return uselongblock; +} + +/* Decision half: the block-type state machine and window/grouping fill, + * given the (possibly pair-synced) final uselongblock. */ +static FFPsyWindowInfo psy_lame_apply(AacPsyContext *pctx, AacPsyChannel *pch, + int uselongblock, + const int attacks[AAC_NUM_BLOCKS_SHORT + 1], + int prev_type, int have_la) +{ + int grouping = 0; + int i; + FFPsyWindowInfo wi = { { 0 } }; + + if (have_la) + pch->frames_since_short = uselongblock ? pch->frames_since_short + 1 : 0; lame_apply_block_type(pch, &wi, uselongblock); @@ -1077,6 +1153,55 @@ static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, return wi; } +static FFPsyWindowInfo psy_lame_window(FFPsyContext *ctx, const float *audio, + const float *la, int channel, int prev_type) +{ + AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data; + AacPsyChannel *pch = &pctx->ch[channel]; + int attacks[AAC_NUM_BLOCKS_SHORT + 1] = { 0 }; + int uselongblock = psy_lame_detect(pctx, pch, la, channel, prev_type, attacks); + + return psy_lame_apply(pctx, pch, uselongblock, attacks, prev_type, !!la); +} + +/* Pair-synced block switching: either channel's attack switches both. */ +static void psy_lame_window_pair(FFPsyContext *ctx, + const float *audio0, const float *la0, + const float *audio1, const float *la1, + int channel0, int channel1, + int prev_type0, int prev_type1, + FFPsyWindowInfo wi[2]) +{ + AacPsyContext *pctx = (AacPsyContext*) ctx->model_priv_data; + AacPsyChannel *pch0 = &pctx->ch[channel0]; + AacPsyChannel *pch1 = &pctx->ch[channel1]; + int att0[AAC_NUM_BLOCKS_SHORT + 1] = { 0 }; + int att1[AAC_NUM_BLOCKS_SHORT + 1] = { 0 }; + int merged[AAC_NUM_BLOCKS_SHORT + 1]; + int u0 = psy_lame_detect(pctx, pch0, la0, channel0, prev_type0, att0); + int u1 = psy_lame_detect(pctx, pch1, la1, channel1, prev_type1, att1); + int u = u0 && u1; + + if (ctx->pair_decoupled[(channel0 >> 1) & 15]) { + /* Joint tools are dead on this pair (encoder-fed state): each channel + * windows for ITS transients - divergence costs nothing there, while + * union-syncing forces the steady channel short at every event in + * the other. Correlated content keeps the sync. */ + wi[0] = psy_lame_apply(pctx, pch0, u0, att0, prev_type0, !!la0); + wi[1] = psy_lame_apply(pctx, pch1, u1, att1, prev_type1, !!la1); + return; + } + + /* One merged attack map for both channels: the grouping (and with it + * common_window) must match across the pair, and the group boundary + * should isolate the first attack heard in EITHER channel. */ + for (int i = 0; i < AAC_NUM_BLOCKS_SHORT + 1; i++) + merged[i] = att0[i] ? att0[i] : att1[i]; + + wi[0] = psy_lame_apply(pctx, pch0, u, merged, prev_type0, !!la0); + wi[1] = psy_lame_apply(pctx, pch1, u, merged, prev_type1, !!la1); +} + const FFPsyModel ff_aac_psy_model = { .name = "3GPP TS 26.403-inspired model", @@ -1084,4 +1209,5 @@ const FFPsyModel ff_aac_psy_model = .window = psy_lame_window, .analyze = psy_3gpp_analyze, .end = psy_3gpp_end, + .window_pair = psy_lame_window_pair, }; _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
