g723源码详细分析-8-计算冲激响应与振铃减法

时间:2021-12-01 09:51:41

Comp_Ir 计算冲激响应
前面已经介绍了如何将量化的lsp转成4组lpc系数,
得到量化后的lpc系数,相应的得到了逆向滤波器(这里称它为滤波器A吧).
这时要做的就是根据这些滤波器,在激励码本表里找出合适的激励源,
也就是对激励进行编码了,接下去的都是跟激励编码相关的.
比较各种语音压缩算法,在lpc分析这一阶段,基本是大同小异的,不同之处,在于对激励的编码算法

现在来看看g723的激励编码算法的总体是什么样子的.

g723的激励源由两部分组成,一部分是自适应激励,另一部分是伪随机的固定码本.
其中,自适应激励实际上是由固定码本反复迭代得到的.自适应激励源可以认为是过去解码的激励源

刚开始阶段,自适应激励自然都为零,这是激励全由固定码本激励源以一定的增益来贡献.
在第二帧开始,自然就有自适应激励源了(由第一帧的固定码本激励以一定的增益构成的)
这时就可以根据基音周期,在基期周期附近按照一定的规则搜索最佳的激励源(也就是连续的60历史解码的激励源)
将这个激励源与滤波器A的冲激相应进行卷积,得到的残余的信号,将这个信号做为目标向量,
进一步在固定码本激励源里搜索(依据均方差最小的原则)

g723还有一个高速激励的编码方式,在第二级激励编码时,采用的是多脉冲激励方式
相应的算法采用的不是最优解(计算量太大了),即先搜索出第一个最佳脉冲的位置与增益,然后再搜索第二个,
将残余的信号再进行固定码本激励搜索

总的来说,激励源的编码是两级的,分别体现的语音信号的时域相关性(自适应激励)以及随机性(固定码本激励/或者多脉冲激励)

当然,第一步我们得把滤波器的冲激响应求出来 Comp_Ir顾名思义,计算冲激响应
ok,现在进入枯燥的代码中吧

这个函数的其中的两个局部变量 FirDl IirDl,再做高通滤波时,对这两个变量的含义就不再陌生了
分别对应是有限冲激响应部分,与无限冲激响应部分 FirDl对应的是分子 IirDl对应的就是分母,读者
列一下滤波器的系统函数,即可一目了然了,比较让人烦恼的仍然是由于定点数运算,不得不引入的一些计算结果
缩放问题

首先,看到对单位冲激响应的定义,可以看出,这个冲激响应已经被扩大了2^26倍了
    Acc0 = (Word32) 0x04000000L ;//lsc 扩大了2^26

接下来对子个帧的lpc分别计算冲激响应
相应的滤波器函数是由三部分组成的

首先是A(z)
这个就是由lpc系数组成的系统函数如下:

          1
   -----------------
          10
     1 -  Σ a(j)*Z^(-j)
         j=1
         
其中a(j)就是lpc系数

然后是共振峰感知加权
          10
     1 -  Σ a(j)*(0.9*Z)^(-j)
         j=1
    ---------------------
          10
     1 -  Σ a(j)*(0.5*Z)^(-j)
         j=1

最后是谐波噪声去除滤波器

     1 - b *z^(-l)   b和l在之前已经求出了
     
让冲激相应先激励第一个,然后再激励第二个,第三个
计算A(z)的冲激响应,然后用A(z)响应做为输入,计算共振峰感知加权的,最后是谐波噪声的

代码片段如下:
        for ( j = 0 ; j < LpcOrder ; j ++ )
            Acc0 = L_mac( Acc0, QntLpc[j], FirDl[j] ) ;//lsc QntLpc是扩大2^13,而FirDl是扩大2^12倍的,乘法本身高大2,相应刚好与Acc0为同一数量级
        Acc1 = L_shl( Acc0, (Word16) 2 ) ;//lsc 这里变成了扩大了 2^28

这里的Acc1就是会被保存在FirDl数组里,因为它将作为共振峰感知加权的历史输入,
同时也是A(z)的历史输出(对A(z)来说,它实际是无限冲激响应部分)

接下计算共振峰感知加权部分

        /* FIR part */
        for ( j = 0 ; j < LpcOrder ; j ++ )
            Acc0 = L_msu( Acc0, PerLpc[j], FirDl[j] ) ;//lsc FirDl成了输入, Acc0也是
                                Acc0 = L_shl( Acc0, (Word16) 1 ) ;
        for ( j = LpcOrder-1 ; j > 0 ; j -- )//lsc 这里更新FirDl数组
            FirDl[j] = FirDl[j-1] ;
        FirDl[0] = round( Acc1 ) ; // lsc 扩大了 2^12(因为round是取出高16位,所以28-16=12)

        /* Iir part */
        for ( j = 0 ; j < LpcOrder ; j ++ )
            Acc0 = L_mac( Acc0, PerLpc[LpcOrder+j], IirDl[j] ) ;
        for ( j = LpcOrder-1 ; j > 0 ; j -- )
            IirDl[j] = IirDl[j-1] ;
        Acc0 = L_shl( Acc0, (Word16) 2 ) ;
        IirDl[0] = round( Acc0 ) ;
        Temp[PitchMax+i] = IirDl[0] ;

最后是谐波噪声去除,不详细说了,保留个延迟,然后做个减法

Sub_Ring 振铃减法,这个函数的目的是计算零输入响应
所谓的零输入响应
过去解码的激励会形成一些拖尾的输入,即无限冲激响应部分
因为我们看到 lpc的预测是这样的
y[n] = x[n] + a * y[n - 1] ... b*y[n-10],也就是说当前帧的最后几个输出,
会对下一帧产生影响,当然的编码时在将这些影响去除,也就是通过减去零输入响应来完成的
具体地说,说是保留上一帧的最后几个输出,然后对计算A(z),感知加权,谐波去噪的零输入响应
并将其扣除

先来看这两个全局变量(实际上是一个全局结构体里的两个域)
CodStat.RingFirDl
CodStat.RingIirDl
它们的含义与作用跟在计算冲激响应时是一样的
Sub_Ring函数大体和Comp_Ir相似,只是输入不是1而是0
并且相应的FirDl与IirDl是在之前帧里计算出来的(Upd_Ring完成这项工作)
在之后的循环中FirDl IirDl被相应地更新
参与计算三个滤波器与Comp_Ir相同

到此为止,可以看到下一个函数就是Find_Acbk,这个是正式开始进行激励编码相关的代码了,
笔者将在下一章分析

                                                               林绍川
                                                               2011.6.27 于杭州