Files
Cyrene_v2/internal/capability/transcode/amrnb/dsp_decoder.go
T
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

243 lines
7.3 KiB
Go

package amrnb
// AMR-NB decoder frame driver, ported from opencore-amrnb dec_amr.cpp
// (Decoder_amr) and sp_dec.cpp (GSMFrameDecode). It orchestrates the
// already-ported building blocks for one 20 ms frame: LSF dequantization and
// LSP interpolation, then four subframes of pitch-lag decode, adaptive
// codebook, fixed codebook with pitch sharpening, gain decode, excitation
// assembly and LP synthesis; finally the formant post-filter and output
// high-pass.
//
// This implements the active-speech (good-frame) path. Error concealment
// (bad-frame / DTX), background-noise adaptation, the gain-averaging
// (Cb_gain_average) and adaptive phase dispersion are not yet ported, so the
// excitation is synthesized directly from the total excitation (equivalent to
// phase dispersion disabled) and the code gain is used unmixed.
const (
cSHARPMAX = 13017 // max pitch sharpening
cExcOff = cPIT_MAX + cL_INTERPOL // 154: excitation history length
cPIT_MIN_MR122 = 18
)
// decoderStateDSP holds the persistent decoder synthesis state across frames.
type decoderStateDSP struct {
oldExc [cL_SUBFR + cPIT_MAX + cL_INTERPOL]int16 // excitation w/ history
lspOld [cM]int16 // previous-frame LSPs
memSyn [cM]int16 // synthesis filter memory
sharp int16 // pitch sharpening (prev gain_pit)
oldT0 int16 // previous integer pitch lag
lsf dPlsfState // LSF predictor state
pred gcPredState // gain predictor state
pst postFilterState // formant post-filter state
post postProcessState // output HP filter state
}
func (st *decoderStateDSP) reset() {
for i := range st.oldExc {
st.oldExc[i] = 0
}
copy(st.lspOld[:], lsp_init_data[:cM])
for i := range st.memSyn {
st.memSyn[i] = 0
}
st.sharp = 0
st.oldT0 = 40
st.lsf.reset()
st.pred.reset()
st.pst.reset()
st.post = postProcessState{}
}
// sharpenCode applies pitch sharpening to the innovation: code[i] += pitSharp ·
// code[i-T0] for i in [T0, L_SUBFR).
func sharpenCode(code []int16, T0 int, pitSharp int16) {
for i := T0; i < cL_SUBFR; i++ {
code[i] = add(code[i], mult(code[i-T0], pitSharp))
}
}
// decodeAmr decodes one frame's parameters into 160 raw synthesis samples and
// returns the four per-subframe LP coefficient sets for the post-filter.
func (st *decoderStateDSP) decodeAmr(mode int, params []int16, synth []int16) []int16 {
var lspMid, lspNew [cM]int16
at := make([]int16, cNB_SUBFR*cMP1)
pi := 0
if mode == dMR122 {
st.lsf.dPlsf5(0, params[pi:pi+5], lspMid[:], lspNew[:])
pi += 5
intLpc1and3(st.lspOld[:], lspMid[:], lspNew[:], at)
} else {
st.lsf.dPlsf3(mode, 0, params[pi:pi+3], lspNew[:])
pi += 3
intLpc1to3(st.lspOld[:], lspNew[:], at)
}
copy(st.lspOld[:], lspNew[:])
var code [cL_SUBFR]int16
exc := st.oldExc[cExcOff:] // current-subframe excitation window (40 samples)
azOff := 0
subfrNr := -1
evenSubfr := int16(0)
var indexMR475 int16 // carried across the MR475 subframe pair
for iSubfr := 0; iSubfr < cL_FRAME; iSubfr += cL_SUBFR {
subfrNr++
evenSubfr = 1 - evenSubfr
az := at[azOff:]
pitFlag := int16(iSubfr)
if iSubfr == cL_FRAME_BY2 && mode != dMR475 && mode != dMR515 {
pitFlag = 0
}
// ---- pitch lag + adaptive codebook ----
index := params[pi]
pi++
var T0, T0frac int16
if mode != dMR122 {
flag4 := int16(0)
if mode == dMR475 || mode == dMR515 || mode == dMR59 || mode == dMR67 {
flag4 = 1
}
deltaLow, deltaRange := int16(5), int16(9)
if mode == dMR795 {
deltaLow, deltaRange = 10, 19
}
t0min := sub(st.oldT0, deltaLow)
if t0min < cPIT_MIN {
t0min = cPIT_MIN
}
t0max := add(t0min, deltaRange)
if t0max > cPIT_MAX {
t0max = cPIT_MAX
t0min = sub(t0max, deltaRange)
}
T0, T0frac = decLag3(index, t0min, t0max, pitFlag, st.oldT0, flag4)
predLt(st.oldExc[:], cExcOff, T0, T0frac, cL_SUBFR, 1)
} else {
T0, T0frac = decLag6(index, cPIT_MIN_MR122, cPIT_MAX, pitFlag, st.oldT0)
predLt(st.oldExc[:], cExcOff, T0, T0frac, cL_SUBFR, 0)
}
// ---- fixed codebook + pitch sharpening ----
var gainPit, gainCode int16
switch mode {
case dMR475, dMR515:
posIdx := params[pi]
pi++
signs := params[pi]
pi++
decode2i40_9bits(int16(subfrNr), signs, posIdx, code[:])
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
case dMR59:
posIdx := params[pi]
pi++
signs := params[pi]
pi++
decode2i40_11bits(signs, posIdx, code[:])
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
case dMR67:
posIdx := params[pi]
pi++
signs := params[pi]
pi++
decode3i40_14bits(signs, posIdx, code[:])
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
case dMR74, dMR795:
posIdx := params[pi]
pi++
signs := params[pi]
pi++
decode4i40_17bits(signs, posIdx, code[:])
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
case dMR102:
dec8i40_31bits(params[pi:pi+7], code[:])
pi += 7
sharpenCode(code[:], int(T0), shl(st.sharp, 1))
case dMR122:
gpIdx := params[pi]
pi++
gainPit = dGainPitch(mode, gpIdx)
dec10i40_35bits(params[pi:pi+10], code[:])
pi += 10
sharpenCode(code[:], int(T0), shl(gainPit, 1))
}
// ---- gain decode ----
switch mode {
case dMR475:
if evenSubfr != 0 {
indexMR475 = params[pi]
pi++
}
gainPit, gainCode = st.pred.decGain(mode, indexMR475, code[:], evenSubfr)
case dMR515, dMR59, dMR67, dMR74, dMR102:
idx := params[pi]
pi++
gainPit, gainCode = st.pred.decGain(mode, idx, code[:], evenSubfr)
case dMR795:
gpIdx := params[pi]
pi++
gainPit = dGainPitch(mode, gpIdx)
gcIdx := params[pi]
pi++
gainCode = st.pred.dGainCode(mode, gcIdx, code[:])
case dMR122:
gcIdx := params[pi]
pi++
gainCode = st.pred.dGainCode(mode, gcIdx, code[:])
}
// store pitch sharpening for next subframe (not in MR475 even subframe)
if mode != dMR475 || evenSubfr == 0 {
st.sharp = gainPit
if st.sharp > cSHARPMAX {
st.sharp = cSHARPMAX
}
}
// ---- total excitation: gain_pit·adaptive + gain_code·code ----
var pitchFac, tmpShift int16
if mode <= dMR102 {
pitchFac = gainPit
tmpShift = 1
} else {
pitchFac = shr(gainPit, 1)
tmpShift = 2
}
for i := 0; i < cL_SUBFR; i++ {
L := L_mult(exc[i], pitchFac)
L = L_mac(L, code[i], gainCode)
L = L_shl(L, tmpShift)
exc[i] = round_(L)
}
// ---- LP synthesis ----
synFilt(az, exc[:cL_SUBFR], synth[iSubfr:iSubfr+cL_SUBFR], cL_SUBFR, st.memSyn[:], false)
copy(st.memSyn[:], synth[iSubfr+cL_SUBFR-cM:iSubfr+cL_SUBFR])
// slide the excitation buffer left by one subframe
copy(st.oldExc[:cExcOff], st.oldExc[cL_SUBFR:cL_SUBFR+cExcOff])
st.oldT0 = T0
azOff += cMP1
}
return at
}
// decodeSpeechFrame decodes one good speech frame end-to-end into 160 PCM
// samples: parameter decode, post-filter, output high-pass, and the 13-bit
// output truncation. Mirrors GSMFrameDecode for the speech path.
func (st *decoderStateDSP) decodeSpeechFrame(mode int, params []int16, synth []int16) {
az := st.decodeAmr(mode, params, synth)
st.pst.postFilter(mode, synth, az)
st.post.process(synth, cL_FRAME)
for i := 0; i < cL_FRAME; i++ {
synth[i] &^= 7 // truncate to 13 bits
}
}