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

109 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package amrnb
// Adaptive formant post-filter, ported from opencore-amrnb pstfilt.cpp. For each
// subframe it builds the weighted LP filters A(z/γ3) and A(z/γ4), forms the
// residual through A(z/γ3), derives a spectral-tilt compensation coefficient
// from the truncated impulse response, applies it via preemphasis, synthesizes
// through 1/A(z/γ4), and finally matches the energy back to the synthesis with
// AGC. Uses the building blocks weightAi/residu/synFilt/preemphasis/agc; this is
// the orchestration that chains them. Bit-exact with the reference.
const (
cL_H = 22 // truncated impulse-response length
cMU = 26214 // tilt compensation factor (0.8)
)
// γ bandwidth-expansion factors (pstfilt.cpp).
var gamma3MR122 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
var gamma3 = [cM]int16{18022, 9912, 5451, 2998, 1649, 907, 499, 274, 151, 83}
var gamma4MR122 = [cM]int16{24576, 18432, 13824, 10368, 7776, 5832, 4374, 3281, 2461, 1846}
var gamma4 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
// postFilterState holds the formant post-filter memory across frames.
type postFilterState struct {
res2 [cL_SUBFR]int16
memSynPst [cM]int16
preemph int16
agc agcState
synthBuf [cM + cL_FRAME]int16 // M history + current frame
}
func (st *postFilterState) reset() {
*st = postFilterState{}
}
// postFilter post-filters one 160-sample synthesis frame in place. az4 holds the
// four per-subframe LP coefficient sets (4*cMP1). Mirrors Post_Filter.
func (st *postFilterState) postFilter(mode int, syn []int16, az4 []int16) {
var ap3, ap4 [cMP1]int16
var h [cL_H]int16
synWork := st.synthBuf[cM:] // syn_work[i] == synthBuf[M+i]
copy(synWork[:cL_FRAME], syn[:cL_FRAME])
azOff := 0
for iSubfr := 0; iSubfr < cL_FRAME; iSubfr += cL_SUBFR {
az := az4[azOff:]
if mode == dMR122 || mode == dMR102 {
weightAi(az, gamma3MR122[:], ap3[:])
weightAi(az, gamma4MR122[:], ap4[:])
} else {
weightAi(az, gamma3[:], ap3[:])
weightAi(az, gamma4[:], ap4[:])
}
// Residual through A(z/γ3); syn_work[iSubfr] == synthBuf[M+iSubfr].
residu(ap3[:], st.synthBuf[:], cM+iSubfr, cL_SUBFR, st.res2[:])
// Truncated impulse response of A(z/γ3)/A(z/γ4).
copy(h[:cMP1], ap3[:cMP1])
for i := cMP1; i < cL_H; i++ {
h[i] = 0
}
synFilt(ap4[:], h[:], h[:], cL_H, h[cMP1:], false)
// Tilt compensation coefficient from the impulse-response autocorr.
var lTmp int32
for i := cL_H - 1; i >= 0; i-- {
t2 := int32(h[i]) * int32(h[i])
if t2 == 0x40000000 {
break // reference sets overflow and stops without adding
}
lTmp = L_add(lTmp, t2<<1)
}
temp1 := int16(lTmp >> 16)
lTmp = 0
for i := cL_H - 2; i >= 0; i-- {
t2 := int32(h[i]) * int32(h[i+1])
if t2 == 0x40000000 {
break
}
lTmp = L_add(lTmp, t2<<1)
}
temp2 := int16(lTmp >> 16)
if temp2 <= 0 {
temp2 = 0
} else {
lt := (int32(temp2) * cMU) >> 15
if lt&0x00010000 != 0 {
lt |= int32(-0x10000)
}
temp2 = int16(lt)
temp2 = div_s(temp2, temp1)
}
preemphasis(&st.preemph, st.res2[:], temp2, cL_SUBFR)
synFilt(ap4[:], st.res2[:], syn[iSubfr:], cL_SUBFR, st.memSynPst[:], true)
st.agc.agc(synWork[iSubfr:], syn[iSubfr:], cAGC_FAC, cL_SUBFR)
azOff += cMP1
}
// Carry the last M samples as history for the next frame.
copy(st.synthBuf[:cM], synWork[cL_FRAME-cM:cL_FRAME])
}