357 lines
13 KiB
Go
357 lines
13 KiB
Go
package amrnb
|
|
|
|
// Main encoder driver, ported from opencore-amrnb cod_amr.cpp (cod_amr) and the
|
|
// per-frame wrapper sp_enc.cpp (Speech_Encode_Frame). It ties together the LP
|
|
// analysis (lpc), LSP quantization (lsp), perceptual weighting + open-loop pitch
|
|
// (pre_big/ol_ltp), and the four-subframe analysis-by-synthesis loop
|
|
// (subframePreProc -> clLtp -> cbsearch -> gainQuant -> subframePostProc),
|
|
// producing the parameter array consumed by prm2bits/packFrame.
|
|
//
|
|
// Scope: the active-speech path for all eight modes — MR515/MR59/MR67/MR74/MR102
|
|
// via Qua_gain, MR122 via q_gain_code, MR475 via the joint two-subframe VQ
|
|
// (mr475Loop), and MR795 via the gain-adapted quantizer. MR102 uses the weighted
|
|
// open-loop pitch (pitchOlWgh). DTX/VAD is always-active. All eight modes are
|
|
// bit-exact with the opencore-amrnb reference encoder.
|
|
|
|
// buffer offsets within oldSpeech (length cL_TOTAL).
|
|
const (
|
|
cSpeechOff = cL_TOTAL - cL_FRAME - cL_NEXT // 120: present frame start
|
|
cPWindowOff = cL_TOTAL - cL_WINDOW // 80: LPC analysis window
|
|
cPWin122Off = cPWindowOff - cL_NEXT // 40: EFR (MR122) window, no lookahead
|
|
cNewSpchOff = cL_TOTAL - cL_FRAME // 160: new speech
|
|
)
|
|
|
|
// coderState holds the persistent encoder analysis state across frames,
|
|
// mirroring cod_amrState plus the Pre_Process state.
|
|
type coderState struct {
|
|
oldSpeech [cL_TOTAL]int16 // [history | present | lookahead]
|
|
oldWsp [cPIT_MAX + cL_FRAME]int16 // weighted speech with pitch history
|
|
oldExc [cExcOff + cL_FRAME]int16 // excitation with pitch+interp history
|
|
|
|
pre preProcessState
|
|
lspS lspEncState
|
|
pred gcPredState
|
|
predUnq gcPredState // "unquantized" gain predictor (MR475 only)
|
|
adapt gainAdaptState // gain adaptor (MR795 only)
|
|
pit pitchFrState
|
|
pitWght pitchOLWghtState // weighted open-loop pitch (MR102 only)
|
|
lev levinsonState
|
|
|
|
oldLags [5]int16 // closed-loop lag history (MR102 weighted OLP)
|
|
olGainFlg [2]int16 // per-half-frame open-loop gain flag (MR102)
|
|
prmBuf [64]int16 // reused parameter array (max params = 57, MR122)
|
|
|
|
memSyn [cM]int16
|
|
memW0 [cM]int16
|
|
memErr [cM]int16
|
|
memW [cM]int16 // weighting-filter memory for pre_big
|
|
sharp int16
|
|
|
|
// MR475 joint-pair quantizer state carried from the even to the odd subframe
|
|
sf0ExpGcode0, sf0FracGcode0 int16
|
|
sf0FracCoeff, sf0ExpCoeff [5]int16
|
|
sf0ExpTargetEn, sf0FracTargetEn int16
|
|
}
|
|
|
|
func (st *coderState) reset() {
|
|
*st = coderState{}
|
|
st.lspS.reset()
|
|
st.pred.reset()
|
|
st.pit.reset()
|
|
st.pitWght.reset()
|
|
for i := range st.oldLags {
|
|
st.oldLags[i] = 40
|
|
}
|
|
st.sharp = 0 // SHARPMIN
|
|
}
|
|
|
|
// lpcAnalysis runs the windowed autocorrelation + Levinson chain, filling the
|
|
// per-window LP filters in az: az[3*MP1] (A_new) always, and az[MP1] (A_mid) for
|
|
// MR122. Mirrors lpc.
|
|
func (st *coderState) lpcAnalysis(mode int, az []int16) {
|
|
var rh, rl [cMP1]int16
|
|
var rc [4]int16
|
|
if mode == dMR122 {
|
|
autocorr(st.oldSpeech[cPWin122Off:], cM, rh[:], rl[:], window_160_80)
|
|
lagWindow(cM, rh[:], rl[:])
|
|
st.lev.levinson(rh[:], rl[:], az[cMP1:], rc[:])
|
|
|
|
autocorr(st.oldSpeech[cPWin122Off:], cM, rh[:], rl[:], window_232_8)
|
|
lagWindow(cM, rh[:], rl[:])
|
|
st.lev.levinson(rh[:], rl[:], az[3*cMP1:], rc[:])
|
|
return
|
|
}
|
|
autocorr(st.oldSpeech[cPWindowOff:], cM, rh[:], rl[:], window_200_40)
|
|
lagWindow(cM, rh[:], rl[:])
|
|
st.lev.levinson(rh[:], rl[:], az[3*cMP1:], rc[:])
|
|
}
|
|
|
|
// preBig computes the perceptually weighted speech wsp for the two subframes of
|
|
// one half-frame (frameOffset 0 or L_FRAME_BY2), updating the weighting-filter
|
|
// memory memW. Mirrors pre_big.
|
|
func (st *coderState) preBig(mode, frameOffset int, az []int16) {
|
|
g1 := gamma1[:]
|
|
if mode > dMR795 { // MR102, MR122
|
|
g1 = gamma1_12k2[:]
|
|
}
|
|
aOffset := 0
|
|
if frameOffset > 0 {
|
|
aOffset = cMP1 * 2
|
|
}
|
|
fo := frameOffset
|
|
for i := 0; i < 2; i++ {
|
|
var ap1, ap2 [cMP1]int16
|
|
weightAi(az[aOffset:], g1, ap1[:])
|
|
weightAi(az[aOffset:], gamma2[:], ap2[:])
|
|
residu(ap1[:], st.oldSpeech[:], cSpeechOff+fo, cL_SUBFR, st.oldWsp[cPIT_MAX+fo:])
|
|
w := st.oldWsp[cPIT_MAX+fo : cPIT_MAX+fo+cL_SUBFR]
|
|
synFilt(ap2[:], w, w, cL_SUBFR, st.memW[:], true)
|
|
aOffset += cMP1
|
|
fo += cL_SUBFR
|
|
}
|
|
}
|
|
|
|
// gainQuant quantizes the subframe gains for the non-MR475 modes, writing the
|
|
// quantization index/indices into prm, returning the quantized gains and the
|
|
// number of indices written, and advancing the predictor. res/exc are the LP
|
|
// residual and adaptive-codebook excitation (used only by MR795). Mirrors the
|
|
// non-MR475 branch of gainQuant.
|
|
func (st *coderState) gainQuant(mode int, r clLtpResult, xn, xn2, y1, y2, code, res, exc, prm []int16) (gainPit, gainCode int16, nIdx int) {
|
|
gp := st.pred.gcPred(mode, code)
|
|
var qe122, qe int16
|
|
switch {
|
|
case mode == dMR122:
|
|
gainPit = r.gainPit // already quantized in cl_ltp
|
|
gainCode = gCode(xn2, y2)
|
|
var idx int16
|
|
idx, qe122, qe = qGainCode(mode, gp.expGcode0, gp.fracGcode0, &gainCode)
|
|
prm[0] = idx
|
|
nIdx = 1
|
|
case mode == dMR795:
|
|
fc, ec, cgFrac, cgExp := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
|
gainPit = r.gainPit
|
|
var gpIdx, gcIdx int16
|
|
gainCode, qe122, qe, gpIdx, gcIdx = st.mr795GainQuant(res, exc, code, fc[:], ec[:],
|
|
gp.expEn, gp.fracEn, gp.expGcode0, gp.fracGcode0, cgFrac, cgExp, r.gpLimit, &gainPit)
|
|
prm[0], prm[1] = gpIdx, gcIdx
|
|
nIdx = 2
|
|
default:
|
|
fc, ec, _, _ := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
|
var idx int16
|
|
idx, gainPit, gainCode, qe122, qe = quaGain(mode, gp.expGcode0, gp.fracGcode0, fc[:], ec[:], r.gpLimit)
|
|
prm[0] = idx
|
|
nIdx = 1
|
|
}
|
|
st.pred.update(qe122, qe)
|
|
return
|
|
}
|
|
|
|
// codAmr encodes one pre-processed 160-sample frame, returning the parameter
|
|
// array (LSF indices followed by the per-subframe pitch/codebook/gain indices,
|
|
// in prm2bits order). Mirrors cod_amr for the supported modes.
|
|
func (st *coderState) codAmr(mode int, newSpeech []int16) ([]int16, error) {
|
|
copy(st.oldSpeech[cNewSpchOff:], newSpeech[:cL_FRAME])
|
|
|
|
az := make([]int16, cNB_SUBFR*cMP1)
|
|
azQ := make([]int16, cNB_SUBFR*cMP1)
|
|
st.lpcAnalysis(mode, az)
|
|
|
|
prm := st.prmBuf[:len(bitno[Mode(mode)])] // reused; caller serializes it before the next frame
|
|
ap := st.lspS.lsp(mode, az, azQ, prm)
|
|
|
|
st.preBig(mode, 0, az)
|
|
st.preBig(mode, cL_FRAME_BY2, az)
|
|
var Top [2]int16
|
|
if mode == dMR475 || mode == dMR515 {
|
|
Top[0] = pitchOl(mode, st.oldWsp[:], cPIT_MAX, cPIT_MIN, cPIT_MAX, cL_FRAME)
|
|
Top[1] = Top[0]
|
|
} else {
|
|
Top[0] = st.olPitch(mode, 0)
|
|
Top[1] = st.olPitch(mode, cL_FRAME_BY2)
|
|
}
|
|
|
|
synth := make([]int16, cL_FRAME)
|
|
if mode == dMR475 {
|
|
st.mr475Loop(az, azQ, Top[:], prm, ap, synth)
|
|
copy(st.oldExc[:cExcOff], st.oldExc[cL_FRAME:cL_FRAME+cExcOff])
|
|
copy(st.oldWsp[:cPIT_MAX], st.oldWsp[cL_FRAME:cL_FRAME+cPIT_MAX])
|
|
copy(st.oldSpeech[:cL_TOTAL-cL_FRAME], st.oldSpeech[cL_FRAME:cL_TOTAL])
|
|
return prm, nil
|
|
}
|
|
for subfrNr := 0; subfrNr < cNB_SUBFR; subfrNr++ {
|
|
iSubfr := subfrNr * cL_SUBFR
|
|
aSub := az[subfrNr*cMP1:]
|
|
aqSub := azQ[subfrNr*cMP1:]
|
|
excBase := cExcOff + iSubfr
|
|
|
|
xn := make([]int16, cL_SUBFR)
|
|
res := make([]int16, cL_SUBFR)
|
|
errSig := make([]int16, cL_SUBFR)
|
|
h1 := make([]int16, cL_SUBFR)
|
|
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
|
st.memErr[:], st.memW0[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
|
|
|
res2 := make([]int16, cL_SUBFR)
|
|
copy(res2, res)
|
|
|
|
xn2 := make([]int16, cL_SUBFR)
|
|
y1 := make([]int16, cL_SUBFR)
|
|
r := st.pit.clLtp(mode, iSubfr, Top[:], h1, st.oldExc[:], excBase, res2, xn, xn2, y1)
|
|
// MR102: feed the first/last closed-loop lag back into the OLP history
|
|
if subfrNr == 0 && st.olGainFlg[0] > 0 {
|
|
st.oldLags[1] = r.T0
|
|
} else if subfrNr == 3 && st.olGainFlg[1] > 0 {
|
|
st.oldLags[0] = r.T0
|
|
}
|
|
prm[ap] = r.pitchIndex
|
|
ap++
|
|
if r.hasGainPitIndex {
|
|
prm[ap] = r.gainPitIndex
|
|
ap++
|
|
}
|
|
|
|
code := make([]int16, cL_SUBFR)
|
|
y2 := make([]int16, cL_SUBFR)
|
|
ncb := cbsearch(xn2, h1, r.T0, st.sharp, r.gainPit, res2, code, y2, mode, subfrNr, prm[ap:])
|
|
ap += ncb
|
|
|
|
gainPit, gainCode, ng := st.gainQuant(mode, r, xn, xn2, y1, y2, code, res, st.oldExc[excBase:], prm[ap:])
|
|
ap += ng
|
|
|
|
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, gainPit, gainCode,
|
|
aqSub, synth, xn, code, y1, y2, st.memSyn[:], st.memErr[:], st.memW0[:],
|
|
st.oldExc[:], cExcOff, &st.sharp)
|
|
}
|
|
|
|
// slide the analysis buffers left by one frame
|
|
copy(st.oldExc[:cExcOff], st.oldExc[cL_FRAME:cL_FRAME+cExcOff])
|
|
copy(st.oldWsp[:cPIT_MAX], st.oldWsp[cL_FRAME:cL_FRAME+cPIT_MAX])
|
|
copy(st.oldSpeech[:cL_TOTAL-cL_FRAME], st.oldSpeech[cL_FRAME:cL_TOTAL])
|
|
|
|
return prm, nil
|
|
}
|
|
|
|
// mr475Loop runs the four-subframe analysis-by-synthesis loop for MR475, whose
|
|
// gains are quantized two subframes at a time. Each even subframe is processed
|
|
// tentatively (storing its energy terms and an unquantized synthesis), and at
|
|
// the following odd subframe the joint VQ chooses one index for the pair; both
|
|
// subframes' final syntheses are then redone with the reconstructed gains.
|
|
// Mirrors the MR475 branch of cod_amr.
|
|
func (st *coderState) mr475Loop(az, azQ, Top, prm []int16, ap int, synth []int16) {
|
|
const mode = dMR475
|
|
var memSynSave, memW0Save, memErrSave [cM]int16
|
|
var h1Sf0, xnSf0, y2Sf0, codeSf0 [cL_SUBFR]int16
|
|
var sharpSave, gainPitSf0, gainCodeSf0, T0Sf0, T0fracSf0 int16
|
|
var iSubfrSf0, gainIdxPos int
|
|
|
|
for subfrNr := 0; subfrNr < cNB_SUBFR; subfrNr++ {
|
|
iSubfr := subfrNr * cL_SUBFR
|
|
evenSubfr := (subfrNr & 1) == 0
|
|
aSub := az[subfrNr*cMP1:]
|
|
aqSub := azQ[subfrNr*cMP1:]
|
|
excBase := cExcOff + iSubfr
|
|
|
|
if evenSubfr {
|
|
memSynSave = st.memSyn
|
|
memW0Save = st.memW0
|
|
memErrSave = st.memErr
|
|
sharpSave = st.sharp
|
|
}
|
|
|
|
// Both subframes of the pair build their target from memW0Save so the
|
|
// even subframe's tentative synthesis carries into the odd one.
|
|
xn := make([]int16, cL_SUBFR)
|
|
res := make([]int16, cL_SUBFR)
|
|
errSig := make([]int16, cL_SUBFR)
|
|
h1 := make([]int16, cL_SUBFR)
|
|
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
|
st.memErr[:], memW0Save[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
|
if evenSubfr {
|
|
copy(h1Sf0[:], h1)
|
|
}
|
|
res2 := make([]int16, cL_SUBFR)
|
|
copy(res2, res)
|
|
|
|
xn2 := make([]int16, cL_SUBFR)
|
|
y1 := make([]int16, cL_SUBFR)
|
|
r := st.pit.clLtp(mode, iSubfr, Top, h1, st.oldExc[:], excBase, res2, xn, xn2, y1)
|
|
prm[ap] = r.pitchIndex
|
|
ap++
|
|
|
|
code := make([]int16, cL_SUBFR)
|
|
y2 := make([]int16, cL_SUBFR)
|
|
ap += cbsearch(xn2, h1, r.T0, st.sharp, r.gainPit, res2, code, y2, mode, subfrNr, prm[ap:])
|
|
|
|
if evenSubfr {
|
|
gainIdxPos = ap // reserve the joint gain index slot
|
|
ap++
|
|
|
|
st.predUnq = st.pred // gc_pred_copy
|
|
gpu := st.predUnq.gcPred(mode, code)
|
|
st.sf0ExpGcode0, st.sf0FracGcode0 = gpu.expGcode0, gpu.fracGcode0
|
|
fc, ec, cgFrac, cgExp := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
|
st.sf0FracCoeff, st.sf0ExpCoeff = fc, ec
|
|
st.sf0ExpTargetEn, st.sf0FracTargetEn = calcTargetEnergy(xn)
|
|
st.predUnq.mr475UpdateUnqPred(st.sf0ExpGcode0, st.sf0FracGcode0, cgExp, cgFrac)
|
|
|
|
// tentative synthesis with the unquantized optimum gains
|
|
gainCode := shl(cgFrac, cgExp+1)
|
|
iSubfrSf0 = iSubfr
|
|
copy(xnSf0[:], xn)
|
|
copy(y2Sf0[:], y2)
|
|
copy(codeSf0[:], code)
|
|
T0Sf0, T0fracSf0 = r.T0, r.T0frac
|
|
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, r.gainPit, gainCode,
|
|
aqSub, synth, xn, code, y1, y2, memSynSave[:], st.memErr[:], memW0Save[:],
|
|
st.oldExc[:], cExcOff, &st.sharp)
|
|
st.sharp = sharpSave
|
|
continue
|
|
}
|
|
|
|
// odd subframe: joint gain quantization for the pair
|
|
gpu := st.predUnq.gcPred(mode, code)
|
|
fc, ec, _, _ := calcFiltEnergies(mode, xn, xn2, y1, y2, r.gCoeff[:])
|
|
tEnExp, tEnFrac := calcTargetEnergy(xn)
|
|
idx, sf0gp, sf0gc, sf1gp, sf1gc := st.mr475GainQuant(code, gpu.expGcode0, gpu.fracGcode0,
|
|
ec[:], fc[:], tEnExp, tEnFrac, r.gpLimit)
|
|
prm[gainIdxPos] = idx
|
|
gainPitSf0, gainCodeSf0 = sf0gp, sf0gc
|
|
|
|
// redo the even subframe's final synthesis with the quantized gains
|
|
st.memErr = memErrSave
|
|
predLt(st.oldExc[:], cExcOff+iSubfrSf0, T0Sf0, T0fracSf0, cL_SUBFR, 1)
|
|
convolve(st.oldExc[cExcOff+iSubfrSf0:], h1Sf0[:], y1, cL_SUBFR)
|
|
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfrSf0, gainPitSf0, gainCodeSf0,
|
|
azQ[(subfrNr-1)*cMP1:], synth, xnSf0[:], codeSf0[:], y1, y2Sf0[:],
|
|
st.memSyn[:], st.memErr[:], st.memW0[:], st.oldExc[:], cExcOff, &sharpSave)
|
|
|
|
// redo the odd subframe's analysis + synthesis with the final memories
|
|
subframePreProc(mode, aSub, aqSub, st.oldSpeech[:], cSpeechOff+iSubfr,
|
|
st.memErr[:], st.memW0[:], st.oldExc[excBase:], h1, xn, res, errSig)
|
|
predLt(st.oldExc[:], excBase, r.T0, r.T0frac, cL_SUBFR, 1)
|
|
convolve(st.oldExc[excBase:], h1, y1, cL_SUBFR)
|
|
subframePostProc(st.oldSpeech[:], cSpeechOff, mode, iSubfr, sf1gp, sf1gc,
|
|
aqSub, synth, xn, code, y1, y2, st.memSyn[:], st.memErr[:], st.memW0[:],
|
|
st.oldExc[:], cExcOff, &st.sharp)
|
|
}
|
|
}
|
|
|
|
// olPitch runs the open-loop pitch over the weighted-speech buffer for the
|
|
// half-frame at frameOffset.
|
|
func (st *coderState) olPitch(mode, frameOffset int) int16 {
|
|
idx := 0
|
|
if frameOffset != 0 {
|
|
idx = 1
|
|
}
|
|
if mode == dMR102 {
|
|
// weighted open-loop pitch with median/gain-flag adaptation
|
|
return st.pitWght.pitchOlWgh(st.oldWsp[:], cPIT_MAX+frameOffset, cPIT_MIN, cPIT_MAX,
|
|
cL_FRAME_BY2, st.oldLags[:], st.olGainFlg[:], idx)
|
|
}
|
|
pitMin := int16(cPIT_MIN)
|
|
if mode == dMR122 {
|
|
pitMin = cPIT_MIN_MR122
|
|
}
|
|
return pitchOl(mode, st.oldWsp[:], cPIT_MAX+frameOffset, int(pitMin), cPIT_MAX, cL_FRAME_BY2)
|
|
}
|