后置滤波处理
后置滤波处理,大致有以下几个步骤
step 1: 长时预测
这段代码的主要目的是利用语音的长时相关来加强当前的语音信号
做法是在基音延迟附近搜索最佳基音延迟,利用历史解码出来的激励信号
对当前的激励信号做一个加权
step 2:共振峰感知加权,这个不用说了,加强共振峰处的能量
step 3:倾斜被偿,修正因为共振峰感知加权引入的频谱倾斜
step 4:增益处理
长时预测的代码比较繁琐,独立了出来分析
进入Post函数,前面的解码阶段,已经得到了重构的语音信号与lpc系数
以及基音周期
所以可以立刻再得到残差信号
/* Compute weighted LPC coefficients */
Weight_Az(coeff, GAMMA1_PST, M, apond1);
Weight_Az(coeff, GAMMA2_PST, M, apond2);
/* Compute A(gamma2) residual */
Residu(apond2, signal_ptr, res2_ptr, L_SUBFR);//lsc 通过感知加权的分母部分子系统,就可以得到残差信号
得到了残差信号,注意res2_ptr这个指针的位置,前面有一段是用来保存之前帧的残差的,用来作当前帧的最佳基音延迟依据
pst_ltp
后置长时预测滤波器
先做归一化处理,
然后是搜索最佳基音延迟
函数声明:
static void search_del(
Word16 t0, /* input : pitch delay given by coder */
Word16 *ptr_sig_in, /* input : input signal (with delay line) */
Word16 *ltpdel, /* output: delay = *ltpdel - *phase / f_up */
Word16 *phase, /* output: phase */
Word16 *num_gltp, /* output: 16 bits numerator of LTP gain */
Word16 *den_gltp, /* output: 16 bits denominator of LTP gain */
Word16 *sh_num_gltp, /* output: justification for num_gltp */
Word16 *sh_den_gltp, /* output: justification for den_gltp */
Word16 *y_up, /* output: LT delayed signal if fract. delay */
Word16 *off_yup /* output: offset in y_up */
)
{//lsc 输出 长时预测增益 (num_gltp den_gltp sh_num_gltp sh_den_gltp:增益的分子,分母,以及归一化的位置位数)
//lsc ltpdel:最佳延迟 phase:最佳分数延迟 y_up:升抽样序列 off_yup:最佳延迟对应的升抽样在y_up里的偏移,注意最佳延迟是分两截的,前后各8个,off_yup=0:表示取前8个中的对应位置,反之则后8个(拗口的表述,但是这个意思)
//lsc 真是纠结的一系列参数啊...
参数的解释大致如上
这个函数的描述的过程大概是这样:
先搜索整数基音延迟
也就是在解码出来的基音周期附近搜索(大约搜索3个位置)
然后在最佳整数延迟附近再搜索分数延迟(这里又要处理升抽样,不如g723直接n阶加权来得干脆)
搜索的依据,就是自相关最大的那个
笔者对升抽样的过程作一个简要的注释说明,
整个过程中比较简单,显得比较晦涩的是升抽样滤波器的冲激响应数组:tab_hup_s,它就是sinc函数的抽样
这个数组中每个样点的顺序是经过调整的.笔者会在代码注释中说明,这个调整是如何进行的,
代码片段如下:
/* initialization used only to suppress Microsoft Visual C++ warnings */
i_max = (Word16)0;
for(i=0; i<3; i++) {
L_acc = 0L;
for(n=0; n<L_SUBFR; n++) {
L_acc = L_mac( L_acc, ptr_sig_in[n] , ptr_sig_past[n]);//lsc 计算自相关
}
if(L_acc < 0) {
L_acc = 0L;
}
L_temp =L_sub(L_acc ,L_num_int);//lsc 在基音周基附近,寻找自相关最大的那个基音延迟
if(L_temp > 0L) {
L_num_int = L_acc;
i_max = (Word16)i;
}
ptr_sig_past--;
}
if(L_num_int == 0) {
*num_gltp = 0;
*den_gltp = 1;
*ltpdel = 0;
*phase = 0;
return;
}
/* Compute den for i_max */
lambda = add(lambda, (Word16)i_max);//lsc 得到最佳基音延迟
ptr_sig_past = ptr_sig_in - lambda;
L_acc = 0L;
for(i=0; i<L_SUBFR; i++) {
temp = *ptr_sig_past++;
L_acc = L_mac( L_acc, temp, temp);//lsc 该基音延迟对应的能量
}
if(L_acc == 0L) {
*num_gltp = 0;
*den_gltp = 1;
*ltpdel = 0;
*phase = 0;
return;
}
L_den_int = L_acc;//lsc 保存最佳整数基音延迟的能量
/***********************************/
/* Select best phase around lambda */
/***********************************/
/* Compute y_up & denominators */
/*******************************/
ptr_y_up = y_up;
L_den_max = L_den_int;
ptr_L_den0 = L_den0;
ptr_L_den1 = L_den1;
ptr_h = tab_hup_s;//lsc 指向插值序列数组,是一个排列顺序经过调整的矩形滤波器的冲激响应
temp = sub(lambda, LH_UP_SM1);
ptr_sig_past0 = ptr_sig_in - temp;//lsc ptr_sig_past0位于最佳基音延迟后的一个位置
/*
//lsc 这个表格的数据组织顺序是经过调整的,笔者按每4格一跳,重新排列,发现这其实是一个sinc函数的抽样.
Word16 tab_hup_s[SIZ_TAB_HUP_S] = {
-188, 2873, 31650, -1597, -484, 7041, 28469, -2147, -933, 12266,
23705, -1992, -1492, 18050, 18050, -1492, -1992, 23705, 12266, -933,
-2147, 28469, 7041, -484, -1597, 31650, 2873, -188 };
重新组织如下:
-188 -484 -933 -1492 -1992 -2147 -1597 (从-188开始,每跳4格,抽取,就可以得到这个序列)
2873 7041 12266 18050 23705 28469 31650 (从2873,开始,每跳4格抽取,以此类推,得到后面两行)
31650 28469 23705 18050 12266 7041 2873
-1597 -2147 -1992 -1492 -933 -484 -188
画一下这个序列,就可以知道,它实际上是一个sinc函数的抽样,
调整tab_hup_s之后,对照着读下面的循环代码,理解起来就方便了,下面的代码原理是与Pred_lt_3一样的,就是在计算升抽样
把子帧的40个样点,升抽样成320个样点,320个样点按分数延迟,组织在一个数组里头y_up
y_up里每连续的40个值,就表示一组分数延迟的采样
之后的计算就是照本宣科了,比较相关性,就可以得到最佳分数延迟
*/
/* Loop on phase */
for(phi=1; phi<F_UP_PST; phi++) {//lsc 计算最佳分数延迟
/* Compute y_up for lambda+1 - phi/F_UP_PST */
/* and lambda - phi/F_UP_PST */
ptr_sig_past = ptr_sig_past0;
for(n = 0; n<=L_SUBFR; n++) {//lsc 这里处理插值,照理应循环40次,每次循环计算一个点(做一次残差与插值序列卷积),这里做了优化,减少了计算量
ptr1 = ptr_sig_past++;
L_acc = 0L;
for(i=0; i<LH2_S; i++) {
L_acc = L_mac(L_acc, ptr_h[i], ptr1[-i]);//lsc 计算卷积,ptr_h指向每8格一跳的插 按当前两点,过去两点(插值滤波器是非因果的)
}
ptr_y_up[n] = round(L_acc);//lsc 这里得到了残差的插值,总共320
}
/* compute den0 (lambda+1) and den1 (lambda) */
/* part common to den0 and den1 */
L_acc = 0L;
for(n=1; n<L_SUBFR; n++) {
L_acc = L_mac(L_acc, ptr_y_up[n] ,ptr_y_up[n]);//lsc 算出中间39个取样的总能量
}
L_temp0 = L_acc; /* saved for den1 */
/* den0 */
L_acc = L_mac(L_acc, ptr_y_up[0] ,ptr_y_up[0]);//lsc 加前面一个点,就是 lambda - phi/F_UP_PST 的能量
*ptr_L_den0 = L_acc;
/* den1 */
L_acc = L_mac(L_temp0, ptr_y_up[L_SUBFR] ,ptr_y_up[L_SUBFR]);//lsc 加后面一个点,就是 lambda+1 - phi/F_UP_PST 的能量
*ptr_L_den1 = L_acc;
if(sub(abs_s(ptr_y_up[0]),abs_s(ptr_y_up[L_SUBFR])) >0) {//lsc L_den_max 这段代码,哪个序列能量大,记录哪个,为了后继的计算归一化
L_temp =L_sub(*ptr_L_den0 ,L_den_max );
if(L_temp> 0L) {
L_den_max = *ptr_L_den0;
}
}
else {
L_temp =L_sub(*ptr_L_den1 ,L_den_max );
if(L_temp> 0L) {
L_den_max = *ptr_L_den1;
}
}
ptr_L_den0++;
ptr_L_den1++;
ptr_y_up += L_SUBFRP1;
ptr_h += LH2_S;
}
if(L_den_max == 0) {
*num_gltp = 0;
*den_gltp = 1;
*ltpdel = 0;
*phase = 0;
return;
}
经过这一过程的处理,就得到了升抽样,
接来说就是对各个备选的升抽样序列进行相关性的大小比较
取相关性最大的那个
这些跟基音周期搜索是极为相似的,过程也比较简单,代码不多说明了
结束了search_del.
接下来的过程也就简单了,按照search_del输出参数,对残差信号进行相应的滤波(或者说是加权)
have fun!
林绍川
2012.6.5于杭州