Files
AskaEth 4a2d276881
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
2026-10-05 13:02:28 +08:00

315 lines
9.1 KiB
Go

package amrnb
// MR795 gain quantization, ported from opencore-amrnb qgain795.cpp
// (MR795_gain_quant, MR795_gain_code_quant3, MR795_gain_code_quant_mod), the
// gain adaptor g_adapt.cpp (gain_adapt) + gmed_n.cpp (gmed_n), and the unfiltered
// energies calc_en.cpp (calc_unfilt_energies). MR795 jointly searches the three
// nearest pitch-gain candidates against the code-gain VQ, then — when the LTP is
// strong enough (alpha>0) — re-quantizes the code gain with a perceptually
// adapted criterion. It emits two indices per subframe. Bit-exact with the
// reference.
const (
cLTP_GAIN_THR1 = 2721 // Q13
cLTP_GAIN_THR2 = 5443 // Q13
cLTPG_MEM_SIZE = 5
)
// gmedN returns the median of the first n values of ind, matching the
// index-based tie-breaking of the reference. Mirrors gmed_n.
func gmedN(ind []int16, n int) int16 {
tmp2 := make([]int16, n)
copy(tmp2, ind[:n])
tmp := make([]int16, n)
ix := 0
for i := 0; i < n; i++ {
max := int16(-32767)
for j := 0; j < n; j++ {
if tmp2[j] >= max {
max = tmp2[j]
ix = j
}
}
tmp2[ix] = -32768
tmp[i] = int16(ix)
}
return ind[tmp[n>>1]]
}
// gainAdaptState is the gain adaptor's persistent state. Mirrors GainAdaptState.
type gainAdaptState struct {
onset int16
prevAlpha int16
prevGc int16
ltpgMem [cLTPG_MEM_SIZE]int16
}
// gainAdapt computes the gain adaptation factor alpha (Q15) from the LTP coding
// gain ltpg and the code gain, updating the onset/history state. Mirrors
// gain_adapt.
func (st *gainAdaptState) gainAdapt(ltpg, gainCod int16) int16 {
var adapt int16
switch {
case ltpg <= cLTP_GAIN_THR1:
adapt = 0
case ltpg <= cLTP_GAIN_THR2:
adapt = 1
default:
adapt = 2
}
if tmp := shr_r(gainCod, 1); tmp > st.prevGc && gainCod > 200 {
st.onset = 8
} else if st.onset != 0 {
st.onset--
}
if st.onset != 0 && adapt < 2 {
adapt++
}
st.ltpgMem[0] = ltpg
filt := gmedN(st.ltpgMem[:], 5)
var result int16
if adapt == 0 {
switch {
case filt > 5443:
result = 0
case filt < 0:
result = 16384
default:
filt = shl(filt, 2)
result = 16384 - mult(24660, filt)
}
}
if st.prevAlpha == 0 {
result = shr(result, 1)
}
st.prevAlpha = result
st.prevGc = gainCod
for i := cLTPG_MEM_SIZE - 1; i > 0; i-- {
st.ltpgMem[i] = st.ltpgMem[i-1]
}
return result
}
// calcUnfiltEnergies computes the unfiltered residual/excitation/cross/LTP-error
// energies (as 4 normalized exp/frac pairs) and the LTP coding gain ltpg.
// Mirrors calc_unfilt_energies.
func calcUnfiltEnergies(res, exc, code []int16, gainPit int16, Lsubfr int) (fracEn, expEn [4]int16, ltpg int16) {
var s1, s2, s3, s4 int32
for i := 0; i < Lsubfr; i++ {
tmp1 := res[i]
tmp2 := exc[i]
s1 += int32(tmp1) * int32(tmp1) // wrapping MAC, like the reference
s2 += int32(tmp2) * int32(tmp2)
s3 += int32(tmp2) * int32(code[i])
Ltemp := L_shl(L_mult(tmp2, gainPit), 1)
d := sub(tmp1, round_(Ltemp))
s4 = L_mac(s4, d, d)
}
s1 <<= 1
s2 <<= 1
s3 <<= 1
if s1 < 0 { // overflow (sign bit set)
s1 = maxInt32
}
if s1 < 400 {
fracEn[0] = 0
expEn[0] = -15
} else {
exp := norm_l(s1)
fracEn[0] = int16(L_shl(s1, exp) >> 16)
expEn[0] = 15 - exp
}
if s2 < 0 {
s2 = maxInt32
}
exp := norm_l(s2)
fracEn[1] = int16(L_shl(s2, exp) >> 16)
expEn[1] = 15 - exp
exp = norm_l(s3)
fracEn[2] = int16(L_shl(s3, exp) >> 16)
expEn[2] = 2 - exp
exp = norm_l(s4)
ltpResEn := int16(L_shl(s4, exp) >> 16)
expS4 := 15 - exp
fracEn[3] = ltpResEn
expEn[3] = expS4
if ltpResEn > 0 && fracEn[0] != 0 {
predGain := div_s(shr(fracEn[0], 1), ltpResEn)
e := sub(expS4, expEn[0])
Ltemp := L_shr(int32(predGain)<<16, e+3)
ltpgExp, ltpgFrac := log2(Ltemp)
Ltemp = L_Comp(ltpgExp-27, ltpgFrac)
ltpg = round_(L_shl(Ltemp, 13)) // Q13
}
return
}
// mr795GainCodeQuant3 jointly searches the three pitch-gain candidates against
// the code-gain VQ for the minimum weighted error, returning the selected pitch
// and code gains, their indices, and the predictor-update energies. Mirrors
// MR795_gain_code_quant3.
func mr795GainCodeQuant3(expGcode0, gcode0 int16, gpCand, gpCind, fracCoeff, expCoeff []int16) (gainPit, gainPitInd, gainCod, gainCodInd, quaEnerMR122, quaEner int16) {
var coeff, coeffLo, expMax [5]int16
expCode := expGcode0 - 10
expMax[0] = expCoeff[0] - 13
expMax[1] = expCoeff[1] - 14
expMax[2] = expCoeff[2] + shl(expCode, 1) + 15
expMax[3] = expCoeff[3] + expCode
expMax[4] = expCoeff[4] + (expCode + 1)
eMax := expMax[0]
for i := 1; i < 5; i++ {
if expMax[i] > eMax {
eMax = expMax[i]
}
}
eMax = add(eMax, 1)
for i := 0; i < 5; i++ {
coeff[i], coeffLo[i] = L_Extract(L_shr(int32(fracCoeff[i])<<16, eMax-expMax[i]))
}
distMin := int32(maxInt32)
var codInd, pitInd int16
for j := 0; j < 3; j++ {
gPitch := gpCand[j]
g2Pitch := mult(gPitch, gPitch)
Ltmp0 := mpy32_16(coeff[0], coeffLo[0], g2Pitch)
Ltmp0 = L_add(Ltmp0, mpy32_16(coeff[1], coeffLo[1], gPitch))
for i := 0; i < cNB_QUA_CODE; i++ {
gCode := mult(qua_gain_code[i*3], gcode0)
g2h, g2l := L_Extract(L_mult(gCode, gCode))
gpch, gpcl := L_Extract(L_mult(gCode, gPitch))
Ltmp := L_add(Ltmp0, mpy_32(coeff[2], coeffLo[2], g2h, g2l))
Ltmp = L_add(Ltmp, mpy32_16(coeff[3], coeffLo[3], gCode))
Ltmp = L_add(Ltmp, mpy_32(coeff[4], coeffLo[4], gpch, gpcl))
if Ltmp < distMin {
distMin = Ltmp
codInd = int16(i)
pitInd = int16(j)
}
}
}
base := int(codInd) * 3
gCode := qua_gain_code[base]
quaEnerMR122 = qua_gain_code[base+1]
quaEner = qua_gain_code[base+2]
gainCod = int16(L_shr(L_mult(gCode, gcode0), 9-expGcode0) >> 16)
gainCodInd = codInd
gainPit = gpCand[pitInd]
gainPitInd = gpCind[pitInd]
return
}
// mr795GainCodeQuantMod re-quantizes the code gain with the gain-adapted
// criterion once the LTP is strong (alpha>0), returning the new index and
// predictor energies and updating gainCod in place. Mirrors
// MR795_gain_code_quant_mod.
func mr795GainCodeQuantMod(gainPit, expGcode0, gcode0 int16, fracEn, expEn []int16, alpha, gainCodUnq int16, gainCod *int16) (index, quaEnerMR122, quaEner int16) {
var coeff, coeffLo, expCoeff [5]int16
gainCode := shl(*gainCod, 10-expGcode0) // Q1 -> Q11(-ec0)
g2Pitch := mult(gainPit, gainPit)
oneAlpha := add(32767-alpha, 1) // 32768 - alpha
Lt1 := L_shl(L_mult(alpha, fracEn[1]), 1)
Lt1 = L_mult(int16(Lt1>>16), g2Pitch)
expCoeff[1] = expEn[1] - 15
coeff[2] = mult(int16(L_shl(L_mult(alpha, fracEn[2]), 1)>>16), gainPit)
expCoeff[2] = add(expEn[2], expGcode0-10)
coeff[3] = int16(L_shl(L_mult(alpha, fracEn[3]), 1) >> 16)
expCoeff[3] = add(expEn[3], shl(expGcode0, 1)-7)
coeff[4] = mult(oneAlpha, fracEn[3])
expCoeff[4] = add(expCoeff[3], 1)
Lt0, sqExp := sqrtLExp(L_mult(alpha, fracEn[0]))
sqExp += 47
expCoeff[0] = expEn[0] - sqExp
eMax := expCoeff[0] + 31
for i := 1; i <= 4; i++ {
if expCoeff[i] > eMax {
eMax = expCoeff[i]
}
}
Lt1 = L_shr(Lt1, eMax-expCoeff[1])
for i := 2; i <= 4; i++ {
coeff[i], coeffLo[i] = L_Extract(L_shr(int32(coeff[i])<<16, eMax-expCoeff[i]))
}
newExp := eMax - 31
tmp := newExp - expCoeff[0]
Lt0 = L_shr(Lt0, shr(tmp, 1))
if tmp&0x1 != 0 {
coeff[0], coeffLo[0] = L_Extract(Lt0)
Lt0 = mpy32_16(coeff[0], coeffLo[0], 23170) // 1/sqrt(2) Q15
}
distMin := int32(maxInt32)
index = 0
for i := 0; i < cNB_QUA_CODE; i++ {
gCode := mult(qua_gain_code[i*3], gcode0)
if gCode >= gainCode {
break
}
g2h, g2l := L_Extract(L_mult(gCode, gCode))
d := sub(gCode, gainCodUnq)
d2h, d2l := L_Extract(L_mult(d, d))
Ltmp := L_add(Lt1, mpy32_16(coeff[2], coeffLo[2], gCode))
Ltmp = L_add(Ltmp, mpy_32(coeff[3], coeffLo[3], g2h, g2l))
sq, e := sqrtLExp(Ltmp)
sq = L_shr(sq, shr(e, 1))
t := round_(L_sub(sq, Lt0))
Ltmp = L_mult(t, t)
Ltmp = L_add(Ltmp, mpy_32(coeff[4], coeffLo[4], d2h, d2l))
if Ltmp < distMin {
distMin = Ltmp
index = int16(i)
}
}
base := int(index) * 3
gCode := qua_gain_code[base]
quaEnerMR122 = qua_gain_code[base+1]
quaEner = qua_gain_code[base+2]
*gainCod = int16(L_shr(L_mult(gCode, gcode0), 9-expGcode0) >> 16)
return
}
// mr795GainQuant orchestrates the MR795 gain quantization for one subframe,
// updating *gainPit and returning the quantized gains, predictor energies and
// the two analysis indices. Mirrors MR795_gain_quant.
func (st *coderState) mr795GainQuant(res, exc, code, fracCoeff, expCoeff []int16,
expCodeEn, fracCodeEn, expGcode0, fracGcode0, codGainFrac, codGainExp, gpLimit int16,
gainPit *int16) (gainCod, quaEnerMR122, quaEner, gainPitIndex, gainCodIndex int16) {
var gpCand, gpCind [3]int16
qGainPitch(dMR795, gpLimit, gainPit, gpCand[:], gpCind[:])
gcode0 := int16(pow2(14, fracGcode0))
gp, gpInd, gc, gcInd, qe122, qe := mr795GainCodeQuant3(expGcode0, gcode0, gpCand[:], gpCind[:], fracCoeff, expCoeff)
*gainPit = gp
gainPitIndex, gainCod = gpInd, gc
gainCodIndex, quaEnerMR122, quaEner = gcInd, qe122, qe
fracEn, expEn, ltpg := calcUnfiltEnergies(res, exc, code, *gainPit, cL_SUBFR)
alpha := st.adapt.gainAdapt(ltpg, gainCod)
if fracEn[0] != 0 && alpha > 0 {
fracEn[3] = fracCodeEn
expEn[3] = expCodeEn
gainCodUnq := shl(codGainFrac, sub(codGainExp, expGcode0)+10)
gainCodIndex, quaEnerMR122, quaEner = mr795GainCodeQuantMod(*gainPit, expGcode0, gcode0,
fracEn[:], expEn[:], alpha, gainCodUnq, &gainCod)
}
return
}