g723源码详细分析-7-lsp反量化与插值
Lsp_Inq Lsp_Int lsp反量化与插值
这两个函数完成的功能是将量化的lsp系数反量化
由于参与量化的lsp系数是4个子帧中的最后一帧,
另外3个子帧的lsp系数是由之前的帧的lsp系数(PrevLsp)
与当前的lsp经过加权比例混合得到的.
这4组lsp被转化成为4组lpc系数后,构成一个滤波器,利用此滤波器
得到残差信号,这样就可以对残差信号进行自适应编码与固定码本编码了
先来看Lsp_Inq,
这个函数对量化后的lsp进行反量化
首先是一个判断
传入的参数Crc(这个是由于上层应用程序传入的)值为1时,认为是出现了丢包,
编码不存在丢包这一说,所以此处认为是不丢包的
然后将lspid拆开,每8个bit代表一个量化的下标索引,然后根据数组下标索引
从码本表里查相应的值,代码片段如下:
/*
* Inverse quantize the 10th-order LSP vector. Each band is done
* separately.
*/ //查表反量化
for ( i = LspQntBands-1; i >= 0 ; i -- ) {
/*
* Get the VQ table entry corresponding to the transmitted index
*/
Tmp = (Word16) ( LspId & (Word32) 0x000000ff ) ;
LspId >>= 8 ;
LspQntPnt = BandQntTable[i] ;
for ( j = 0 ; j < BandInfoTable[i][1] ; j ++ )
Lsp[BandInfoTable[i][0] + j] =
LspQntPnt[Tmp*BandInfoTable[i][1] + j] ;
}
回忆一下编码过程,采用的时类似差值方式来量化的,
所以从码本表里得到的值,要加上直流分量以及
过程与量化相反
首先得到之前被去除直分量的lsp系数
/*
* Subtract the DC component from the previous frame's quantized
* vector
*/ //减去直流分量
for ( j = 0 ; j < LpcOrder ; j ++ )
PrevLsp[j] = sub(PrevLsp[j], LspDcTable[j] ) ;
然后与码本里的值相加
/*
* Generate the prediction vector using a fixed first-order
* predictor based on the previous frame's (DC-free) quantized
* vector
*/ //形成预测向量
for ( j = 0 ; j < LpcOrder ; j ++ ) {
Tmp = mult_r( PrevLsp[j], Lprd ) ;
Lsp[j] = add( Lsp[j], Tmp ) ;
}
再加上直流分量
/*
* Add the DC component back to the previous quantized vector,
* which is needed in later routines
*/ //因为是用减去直流分量的lsp向量量化的,所以加回来
for ( j = 0 ; j < LpcOrder ; j ++ ) {
PrevLsp[j] = add( PrevLsp[j], LspDcTable[j] ) ;
Lsp[j] = add( Lsp[j], LspDcTable[j] ) ;
}
可以看到,这样就"恢复"了lsp,即反量化了,当然反量化值与原值是略有不同的
接下来进行lsp系数稳定性检测调整,
其实就是保证每个lsp系数分得足够开,如果两个lsp系数太过靠紧,就调整它们的值,让它
们离得开一些
从itu的文档,我们看出两个lsp之间距离不得小于31.25hz,
即8000/31.25=256,换算到量化的间隔(2pi被量化成512等于)
也就是量化后的lsp量必须相差2,注意到量化的lsp是扩大2^7倍的,
2即 0x100
如果相邻lsp系数之间的差值小于2,就取平均值,然后各减一
与itu文档对应的就是取平均,各减31.25/2hz
代码比较简单,如下:
/* Check the first frequency */
if ( Lsp[0] < (Word16) 0x180 )//lsc 这个是3,最小值限制
Lsp[0] = (Word16) 0x180 ;
/* Check the last frequency */
if ( Lsp[LpcOrder-1] > (Word16) 0x7e00 )//lsc 这个是252,最大值限制
Lsp[LpcOrder-1] = (Word16) 0x7e00 ;
/* Perform the modification */
for ( j = 1 ; j < LpcOrder ; j ++ ) {
Tmp = add( Scon, Lsp[j-1] ) ;//lsc 在编码时 Scon实际上就是2 对应的就是31.25hz, 8000/31.25=256
Tmp = sub( Tmp, Lsp[j] ) ;
if ( Tmp > (Word16) 0 ) {//lsc相互距离小于31.25hz,取平均,然后一个加1,一个减1(也就是31.25/2)
Tmp = shr( Tmp, (Word16) 1 ) ;
Lsp[j-1] = sub( Lsp[j-1], Tmp ) ;
Lsp[j] = add( Lsp[j], Tmp ) ;
}
}
然后检测:
for ( j = 1 ; j < LpcOrder ; j ++ ) {//lsc 检查是否满足距离都大于等于31.25hz即2
Tmp = add( Lsp[j-1], Scon ) ;
Tmp = sub( Tmp, (Word16) 4 ) ;
Tmp = sub( Tmp, Lsp[j] ) ;
if ( Tmp > (Word16) 0 )
Test = True ;
}
如果循环了10次仍然不满足每个lsp的值间隔大于2,就取之前的lsp值
Lsp_Int lsp插值
将当前解码出来的lsp与之前帧的lsp系数进行一定比例的混合,得到4个子帧的lsp系数
因为语音数据的相关性,itu采用了这种作法来减少传输的数据量
这个函数比较简单,对着itu的文档自然一清二楚,笔者不再详细说明了
这里稍解释一些LsptoA这个函数
顾名思义,它的功能就是完成lsp转成lpc系数,真正构造用于计算残差的滤波器的还是lpc系数
每完成一组lsp插值,就计算出相应的lpc系数
算法比较直观,由于采用的是定点数运算,所以存在很多数值放大缩小的情况,显得比较绕
首先,LsptoA做的是查cos表,得到扩大2^15倍的cos值,因为此时已经得到角度的量化值了,2pi为量化成512等分(15:7)为整数部分(6:0)为小数部分
代码片段如下:
for ( i = 0 ; i < LpcOrder ; i ++ ) {
/*
* Do the table lookup using bits [15:7] of the LSP frequency
*/
j = (int) shr( Lsp[i], (Word16) 7 ) ;// lsc lsp的低7位是小数点之后的,剩余的高位才直接从cos表里面查
Acc0 = L_deposit_h( CosineTable[j] ) ;//扩大2^16倍,总共2^30
/*
* Do the linear interpolations using bits [6:0] of the LSP
* frequency
*/ //lsc 用直线段代替曲线,量化小数点后面的值,并与整数部分的cos值相加
Tmp = sub(CosineTable[j+1], CosineTable[j] ) ;
Acc0 = L_mac( Acc0, Tmp, add( shl( (Word16)(Lsp[i] & 0x007f) ,
(Word16)8 ), (Word16) 0x0080 ) ) ;
//lsc shl 8,由于小数部分本身是放大2^7, 8+7 + 14(tmp的放大倍数) + 1(L_mac) = 30,这样就与Acc0是一个数量级了
Acc0 = L_shl( Acc0, (Word16) 1 ) ;
Lsp[i] = negate( round( Acc0 ) ) ;//lsc 因为- 2cos(w_i),有个负号,所以这里直接取反 最终是扩大了2^15
}
得到cos值之后,就可以根据根的性质进行多项式乘法得到lpc系数了,
P_i(z) = 1 - 2cos(w_i) z^{-1} + z^{-2}这个方程的根 为 cos(w_i)+sin(w_i)j cos(w_i)-jsin(w_i),所以P(z)可以分解为5个这样的二次多项式
执行多项式相乘,即可得到对应的系数
笔者这里简要地描述一下多项式乘法的过程
1 c 1 c = 2cos(w_i)
1 b 1 b = 2cos(w_i+1)
根据多项式乘法则有系数如下
z^0 z^1 z^2 z^3 z^4
1 b 1
c bc c
1 b 1
从这里,可以看出系数存在对称性
当然由于对称性itu最代p[0]是最高次(完全可以这么假设),这在之后的计算要加以注意,否则就会无法理解代码,
根有10个,由于对称性,只要做5次循环,求出前6个系数,后面的5个系数由对称性可以直接推出来
先计算p[0] p[1] p[2]然后,再做3次循环,迭代更新,求出前6个系数,每次循环会多求出一个系数,
并在之后的循环中更新所有之前计算出来的值
通过这样一个实例,可以更好地理解以下的代码,因为系数总是对称的,只需要计算"前一半"就可以了(有奇数个项)
1 2c 1
p[0] p[1] p[2] p[1] p[0]
执行多项式乘法后,则如下
p[0] p[1] p[2] p[1] p[0]
2c*p[0] 2c*p[1] 2c*p[2] 2c*p[1] 2c*p[0]
p[0] p[1] p[2] p[1] p[0]
从这里我们看到 新增加的 p[3] = p[1] + 2c*p[2] + p[1]
而p[2]要更新为 p[2] += (2c*p[1] + p[0])
p[1]更新为 p[1] += (2c*p[0])
p[0]不变
在计算的过程中,每循环一次,缩小1/2(总共三次,所以缩小了1/8),这样做一方面也能防止溢出,所以我们看到循环过程中,p[0]也在不断地被更新,
因为我们看到,是<相乘迭代>的过程,每一环缩小1/2,最终就会缩小1/8
相应的代码片段:
求p[0] p[1] p[2]
P[0] = (Word32) 0x10000000L ;//lsc这个表示1 2^28
P[1] = L_mult( Lsp[0], (Word16) 0x2000 ) ;//lsc lsp[0]是放大2^15,0x2000即2 2^12 总共2^28,因为有个乘2因子
P[1] = L_mac( P[1], Lsp[2], (Word16) 0x2000 ) ;//两个一次项系相加,得到最最终的一次项系数
P[2] = L_mult( Lsp[0], Lsp[2] ) ;//这里是算bc了
P[2] = L_shr( P[2], (Word16) 1 ) ;//正好放大了4倍(L_mult贡献了乘2)
P[2] = L_add( P[2], (Word32) 0x20000000L ) ;//这里做+2 即 2+bc,这里得到的值,是原值的2^28倍
//lsc 这段代码含义同上,不多做解释了
Q[0] = (Word32) 0x10000000L ;
Q[1] = L_mult( Lsp[1], (Word16) 0x2000 ) ;
Q[1] = L_mac( Q[1], Lsp[3], (Word16) 0x2000 ) ;
Q[2] = L_mult( Lsp[1], Lsp[3] ) ;
Q[2] = L_shr( Q[2], (Word16) 1 ) ;
Q[2] = L_add( Q[2], (Word32) 0x20000000L ) ;
循环求之后的3个系数
for ( i = 2 ; i < LpcOrder/2 ; i ++ ) {
/* Compute coefficient (i+1) */
Acc0 = P[i] ;//lsc 这个值已经在之前的循环迭代中缩放到合适的值
Acc0 = L_mls( Acc0, Lsp[2*i+0] ) ;//lsc 2c*p[2],因为缩小1/2, L_mls能把结果值缩小2^-15
Acc0 = L_add( Acc0, P[i-1] ) ;//lsc p[1] + c*p[2]
P[i+1] = Acc0 ;//lsc 是当前循环迭代的 1/2(这么说有点费解,但笔者也找不到更适合的词了,不是真实值的1/2,在当前循环缩放因子的基础上再缩1/2)
//q的计算同上
Acc1 = Q[i] ;
Acc1 = L_mls( Acc1, Lsp[2*i+1] ) ;
Acc1 = L_add( Acc1, Q[i-1] ) ;
Q[i+1] = Acc1 ;
/* Compute coefficients i, i-1, ..., 2 */
for ( j = i ; j >= 2 ; j -- ) {
Acc0 = P[j-1] ;
Acc0 = L_mls( Acc0, Lsp[2*i+0] ) ;//lsc Lsp[2*i+0]是需要乘2的,但要缩小1/2,所以就不用乘了
Acc0 = L_add( Acc0, L_shr(P[j], (Word16) 1 ) ) ;//p[2]缩小1/2
Acc0 = L_add( Acc0, L_shr(P[j-2], (Word16) 1 ) ) ;//p[0]缩小1/2
P[j] = Acc0 ;//lsc 最终是缩小了1/2
//q的计算同上
Acc1 = Q[j-1] ;
Acc1 = L_mls( Acc1, Lsp[2*i+1] ) ;
Acc1 = L_add( Acc1, L_shr(Q[j], (Word16) 1 ) ) ;
Acc1 = L_add( Acc1, L_shr(Q[j-2], (Word16) 1 ) ) ;
Q[j] = Acc1 ;
}
/* Compute coefficients 1, 0 */
P[0] = L_shr( P[0], (Word16) 1 ) ;//这里更新p[0],每次缩小1/2,
Q[0] = L_shr( Q[0], (Word16) 1 ) ;
Acc0 = L_deposit_h( Lsp[2*i+0] ) ;//这里的lsp被扩大了2^30
Acc0 = L_shr( Acc0, (Word16) i ) ;//这里为了把Lsp[2*i + 0]调至与p[0]相同的数量级,因为p[0]本质是1, 也就是2^(30-i)
Acc0 = L_add( Acc0, P[1] ) ;//数据量相同的情况下才能相加,即 p[1] += (c*p[0])
Acc0 = L_shr( Acc0, (Word16) 1 ) ;//缩小至1/2
P[1] = Acc0 ;
Acc1 = L_deposit_h( Lsp[2*i+1] ) ;
Acc1 = L_shr( Acc1, (Word16) i ) ;
Acc1 = L_add( Acc1, Q[1] ) ;
Acc1 = L_shr( Acc1, (Word16) 1 ) ;
Q[1] = Acc1 ;
}
计算到这一步,笔者推断出p q的系数是扩大2^25的
根据A(z) = 1/2 {P(z) (1+ z^{-1}) + Q(z) (1 - z^{-1})}
求出lpc系数
//lsc 注意itu认为 p[0]是最高次(因为对称性,也可以认为是最低次,但itu假设成最高次也完全合理)
//lsc 清楚这点才能顺畅地理解以下的代码(笔者初看时把顺序弄反了,结果怎么看都不对)
for ( i = 0 ; i < LpcOrder/2 ; i ++ ) {
Acc0 = P[i] ;
Acc0 = L_add( Acc0, P[i+1] ) ;//lsc 到这里完成计算P(z) (1 + z^{-1})
Acc0 = L_sub( Acc0, Q[i] ) ;//lsc 这里是计算Q(z)
Acc0 = L_add( Acc0, Q[i+1] ) ;//lsc 这里是计算Q(z) * z^(-1)
Acc0 = L_shl( Acc0, (Word16) 3 ) ;//这里可以看出扩了2^4倍,因为还有一个1/2因子没有乘进去
Lsp[i] = negate( round( Acc0 ) ) ;//29-16=13
//对称性,同时计算别外一半,因为对称性,这里的加减顺序自然是相反的
Acc1 = P[i] ;
Acc1 = L_add( Acc1, P[i+1] ) ;
Acc1 = L_add( Acc1, Q[i] ) ;
Acc1 = L_sub( Acc1, Q[i+1] ) ;
Acc1 = L_shl( Acc1, (Word16) 3 ) ;
Lsp[LpcOrder-1-i] = negate( round( Acc1 ) ) ;
}
//lsc 到这一步,笔者推断出是扩大了2^13倍
//从之后的计算冲激响应也可以看出来,确实是扩大了2^13,因为我们看到冲激响应的脉冲输入是0x04000000L(它代表1,值为2^26),正好是13的两倍
//考虑到信号其中有冲激响应的乘法,这就正好对上了
到此为止,完成了lsp到lpc的转换,看上去很多,但其实都是根据公式平铺直叙,弄清楚放大倍数,代码就迎刃而解了
接下来的工作就是根据lpc构造冲激响应,进行自适应激励码本与固定码本搜索了
笔者将在后继的章节继续分析
林绍川
2011.6.23 于杭州