g723源码详细分析(-)

本文详细分析了G723编码器中信号处理和LPC技术的关键步骤,包括高通滤波、信号归一化、LPC系数计算及其递推算法证明。通过理解这些技术,读者能更好地掌握语音编码原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

完成了g723源代码的分析,现作一些整理

1 信号高通滤波 Rem_Dc:

 

这个函数做高通滤波用的.将低频噪声滤除

滤波器的系统函数为

H(z)=(1-z^(-1)) / (1 - (127/128)*z^(-1))

将 单位圆上的值代入(即:cos(a) + sin(a)*i)

可以看出这个函数在π达到峰值,在 0 和 2π处是谷值,明显是高通滤波

 

迭代公式如下

y[n] = x[n] - x[n-1] + (127/128) * y[n-1]

为了避免出现除法运算,所有的值都扩大了

 

x[n] - x[n - 1] 即为itu代码中的fir部分

(127/128) * y[n-1] 即iir部分

 

我们来看看滤波代码:

            /* Do the Fir and scale by 2 */

            Acc0 = L_mult( Dpnt[i], (Word16) 0x4000 ) ; //lsc 乘 128 * 128 * 2

            Acc0 = L_mac ( Acc0, CodStat.HpfZdl, (Word16) 0xc000 ) ; //lsc 乘-128 * 128 * 2 + acc0   0xc000是 -128 * 128的补码

            CodStat.HpfZdl = Dpnt[i] ; //lsc 完成滤波器零点部分运算 acc0 = 256 * 128 * x[n] - 256 * 128 * x[n-1] 

 

            /* Do the Iir part */

            Acc1 = L_mls( CodStat.HpfPdl, (Word16) 0x7f00 ) ; //lsc y[n-1] * (127 * 256 / 128 * 256)  y[n - 1]在第一轮计算中已经和x[n]处于一个数量级了

            Acc0 = L_add( Acc0, Acc1 ) ;  //lsc 完成零极点和,完成了整个滤波运算, 显然,这里的运算结果扩大了 256 * 128倍

            CodStat.HpfPdl = Acc0 ;   //lsc 保存y[n] 至寄存器,形成下一步运算的y[n-1]

            Dpnt[i] = round(Acc0) ;   //lsc 取出运算结果的高16位

 

最终的结果是 输入信号缩小了一半

 

从另一个分支也可以看出

       Dpnt[i] = shr( Dpnt[i], (Word16) 1 ) ;

如果不做高通滤波,就直接把信号缩小一半

 

 

补充说明 

L_mult 计算两个相乘,并将结扩大2倍

L_mac(var1, var2, var3) 令后两个参数var2, var3相乘,并扩大两倍,再与第一个参数var1相加,

为了使加法运算在同一个数量级 var1必须预先扩大2倍

L_mls(var1,var2) 完成var1 var2的乘法,运算是这么进行的,先计算var1低16与 var2的乘法,再计算var1高16与var2乘法结果

对低16的计算结果做一些舍弃,最终的结果是缩小至真实乘法值的2^(-15)

round 取出一个数的高16位

 

 

 

 

2 计算lpc系数 Comp_Lpc

 

程序会保留之前帧的120个样点,与当前帧的240个样点,组合成一个新数组,lpc系统是针对这个数组进行计算的

lpc系数分四组运算,每组取180个样点,组与组之间存在着120个样点的交叠

 

ShAcf_sf[k] = Vec_Norm( Vect, (Word16) LpcFrame ) ;

这段代码对信号进行归一化处理,即找出绝对值最大的输入信号,将其归一化,即计算该输入信号移到"最大"值所需要

左移次数n,然后将180个样点进行左移n-3,完成了归一化,该函数同时返回 n-3,(实际上是保留了"3空位",可能是为了防上后继计算出现溢出)

 

可以看到itu使用了海明窗对信号进行截取,

窗函数的作用:截取信号,并尽量不影响信号的频域特性,所以窗函数的频域特性,应该是主瓣窄,并且旁瓣低,由于

同时满足两者有冲突,比如频域上是个冲激(这是最理想的,不会改变信号的任何频域特性),但在时域则为无穷个点,这种窗显然没有任何实际意义

于是数学家们设计了一些窗函数,分别适用于某种特定应用场合,海明窗是其中的一个

 

itu中的海明窗是放大了2^15的,对应的乘法则缩小2^(-15),所以加窗过程没有数值做任何放大

加窗代码如下

        /* Apply the Hamming window */ //对信号加一个海明窗

        for ( i = 0 ; i < LpcFrame ; i ++ )

            Vect[i] = mult_r(Vect[i], HammingWindowTable[i]) ;

 

然后计算零时自相关,即能量,根据柯西定理,也可以知道,零时自相关是所有自相关里绝对值最大的

代码如下

        /* Compute the zeroth-order coefficient (energy) */ //lsc 零时自相关,即能量

        Acc1 = (Word32) 0 ;

        for ( i = 0 ; i < LpcFrame ; i ++ ) {

            Acc0 = L_mult( Vect[i], Vect[i] ) ;

            Acc0 = L_shr( Acc0, (Word16) 1 ) ;

            Acc1 = L_add( Acc1, Acc0 ) ;

        }

可以看出,计算结果没有做任何放大,能量还会加1/1024噪声能量

 

然后将能量归一化

        /* Normalize the energy */  //acc1,能量归一化,即左移至顶,需要多少次乘2

        Exp = norm_l( Acc1 ) ;

        Acc1 = L_shl( Acc1, Exp ) ;//能量归一化

取出归一化后能量的高16位

        curAcf[0] = round( Acc1 ) ; //取出能量的高16位

如果能量的高16位为零(即能量为零),所有的自相关都认为是零

 

计算10个自相关系数,用于莱文森-德宾递推公式使用,代码片段如下:

            for ( i = 1 ; i <= LpcOrder ; i ++ ) {

                Acc1 = (Word32) 0 ;

                for ( j = i ; j < LpcFrame ; j ++ ) {

                    Acc0 = L_mult( Vect[j], Vect[j-i] ) ;

                    Acc0 = L_shr( Acc0, (Word16) 1 ) ;

                    Acc1 = L_add( Acc1, Acc0 ) ;//lsc 计算自相关,没有任何放大

                }

                Acc0 = L_shl( Acc1, Exp ) ;/* lsc 已经归一化 */

                Acc0 = L_mls( Acc0, BinomialWindowTable[i-1] ) ;/* 一个二项式窗函数 为何要加??? --- 加窗后的值应该没有做改变的 */

                curAcf[i] = round(Acc0) ;//lsc 取出高16位

            }

            /* Save Acf scaling factor */

 

            ShAcf_sf[k] = add(Exp, shl(ShAcf_sf[k], 1));

            //lsc归一化的时候,本身移动了ShAcf_sf[k],而自相关为归一化后的信号相乘,意味着

            //得到的结果本身已经扩了2^(ShAcf_sf[k]*2),归一化又移动了Exp,自然要更新相应的ShAcf_sf[k] = ShAcf_sf[k] * 2 + Exp

 

 

 

Durbin --- 莱文森-德宾递推算法,求出10个lpc系数

呼叫时传入的参数

        Durbin( &UnqLpc[k*LpcOrder], &curAcf[1], curAcf[0], &Pk2 );

第一个参数不解释了,将记录Durbin函数计算得到的10个lpc系数

&curAcf[1] 为自相关系数组,用于莱文森-德宾递推,curAcf[0]为零时自相关,即能量,做为

递推时分母的第一个因子,Durbin函数的返回值,为残差信号的能量 &Pk2记录部分相关系数k(该参数似乎是在生成舒适噪音时才使用的)

 

 

莱文森-德宾递推公式证明:

 

首先从lpc系数的求解开始说吧

 

我们假定 s[n]是输入的语音信号

s`[n]是10阶预测信号

s`[n] = a10 * s[n - 10] + a9 * s[n - 9] + ... + a1 * s[n - 1]

 

我们取s`[n] 与 s[n] 方差最小值

 

(s[n] - s`[n])^2   ^2表示平方

然后对每个 a[i]求偏导,自然就得到了一个10元一次方程组,表示

 

10

Σa[i] * R(|k-i|) = R(k)  k = 1,2,...,10  R(k)表示输入信号的自相关 ---  方程组1

i=1

 

即莱文森-德宾递推公式就是一种适合于计算机实现的解这个10元一次方程组的算法

 

 

下面提到内积和正交的概念

A(z) B(z)分别表示前向预测与后向预测的逆滤波器的系统函数

则它们关于输入s[n]的内积这么定义

 10 11           

 Σ Σ a[i]*b[k] R(|i-k|)   R(n)同样表示输入信号s[n]的自相关函数

i=1 k=1

 

记作 <A(z),B(z)>

如果内积为零,则被称之为正交

 

这里可以立即得出这么一个结论

 

<A(z), z^(-l)>一定为零,这个参见方程组1 

也就是说A(z)与z^(-l) l=1,2,...10正交时,A(z)是该阶次下最优估计的逆滤波器

 

开始递推,首先从零开始,零阶时,啥都没有

最优的逆滤波器自然就是

A0(z)=1

B0(z)=z^-1

 

构造出递推公式

A[i](z) = A[i-1](z) + ki * B[i - 1](z)

B[i](z) = z^-1{ B[i - 1](z) + ki * A[i-1](z) }          ----- 等式2

 

其中 ki = -{ <A[i-1](z),B[i - 1](z)> } / { B[i - 1](z), B[i - 1](z) }  ---- 等式1

 

现在只需要证 A[i](z) 与 z^-i正交即可 (当然B[i](z)也要与z^-i,两个证明的过程差不多)

将A[i](z) = A[i-1](z) + ki * B[i - 1](z)代入 <A[i](z), z^-i>

我们立该得到

<A[i-i](z), z^-i> + ki<B[i - 1](z), z^-i> = 0;

求出满足这个条件的ki就是了

<A[i-i](z), z^-i> 实际上与 <A[i-i](z), B[i-1](z)>是相等的,为什么呢?

因为A[i-1](z)是最优估计,那它一定与 z^-l (l=1,2,...,i-i)正交,而 B[i-i](z)的最高阶系数是1...

同理<B[i - 1](z), z^-i> 与<B[i-i](z), B[i-1](z)>也是相等的...

自然ki就是等式1的那种形式,

 

莱文森-德宾递推公式至此证明完毕

 

ki递推公式化简

                      i-1

分子可以化简为 R[i] + Σ a(m-1)[n] * R(|l-i|) 这个可以用<A[i-i](z), z^-i> 直接推导出

                      l=1

 

分母可以化简为 (1-k(l)^2)*(1-k(l-1)^2) ... (1-k(1)^2)*R(0)

这是由于 <z^-1 * F(z), z^-1 * G(z)> = <F(z), G(z)> = <F(1/z), G(1/z)> 由内积的定理可以直接证出

用 <B[i](z), B[i](z)>  等式2进行代换,再进行相应的合并同类项处理,就可以得到化简后的分子

 

递推过程中,相应的更新每一轮的自相关系数:

a(i+1)(j) = ai(j) + k ai(m-j) 

这个是从 A(i+1)(z) = Ai(z) + k Bi(z),推出的

我们可以证明 Bi(z) 的系数 与 Ai(z)的系数时"相反"的, 即把 Ai(z)的系数逆序排序其实就是Bi(z)的系数

 

 

Durbin函数代码如下:

 

Word16  Durbin( Word16 *Lpc, Word16 *Corr, Word16 Err, Word16 *Pk2 )

{

    int   i,j   ;

 

    Word16   Temp[LpcOrder] ;

    Word16   Pk ;

 

    Word32   Acc0,Acc1,Acc2 ;

 

 /*

  * Initialize the LPC vector

  */

    for ( i = 0 ; i < LpcOrder ; i ++ )

        Lpc[i] = (Word16) 0 ;

 

 /*

  * Do the recursion.  At the ith step, the algorithm computes the

  * (i+1)th - order MMSE linear prediction filter.

  */

    for ( i = 0 ; i < LpcOrder ; i ++ ) {

 

/*

 * Compute the partial correlation (parcor) coefficient

 */

 

        /* Start parcor computation */

        Acc0 = L_deposit_h( Corr[i] ) ;/* 将值扩至32位,对应递推中的R(m)值 */

        Acc0 = L_shr( Acc0, (Word16) 2 ) ;/* 右移2位,缩小4倍,因为L_msu扩了2 ??? */

        for ( j = 0 ; j < i ; j ++ )

            Acc0 = L_msu( Acc0, Lpc[j], Corr[i-j-1] ) ;/* 即ai * R(|i-m|),acc0就是k的分子 */

        Acc0 = L_shl( Acc0, (Word16) 2 ) ;

 

        /* Save sign */

        Acc1 = Acc0 ;

        Acc0 = L_abs( Acc0 ) ;

 

        /* Finish parcor computation */

        Acc2 = L_deposit_h( Err ) ;

        if ( Acc0 >= Acc2 ) {

            *Pk2 = 32767;

            break ;

        }

 

        Pk = div_l( Acc0, Err ) ;/* lsc k的分子除分母 得出k pk就是k */

 

        if ( Acc1 >= 0 )

            Pk = negate(Pk) ;//lsc 负负得正,正负得负

 

 /*

  * Sine detector

  */

        if ( i == 1 ) *Pk2 = Pk;

 

 /*

  * Compute the ith LPC coefficient

  */

        Acc0 = L_deposit_h( negate(Pk) ) ;

        Acc0 = L_shr( Acc0, (Word16) 2 ) ;

        Lpc[i] = round( Acc0 ) ;/* a[m]=k */

 

 /*

  * Update the prediction error

  */ //lsc 这段一比较绕,为了节省计算量itu使用了一些技巧,读者需要注意

        Acc1 = L_mls( Acc1, Pk ) ;//lsc (-分子原值) * k 得出的是 -k^2 * Err   k * Err = 分子

        Acc1 = L_add( Acc1, Acc2 ) ;//lsc Err(Acc2) + ???

        Err = round( Acc1 ) ;/* lsc 更新了k分母 */

 

 

 /*

  * Compute the remaining LPC coefficients

  */

        for ( j = 0 ; j < i ; j ++ )

            Temp[j] = Lpc[j] ;

 

        for ( j = 0 ; j < i ; j ++ ) { /* lsc 更新lpc系数 anew(i) = ai + ka(m-i), 这个是由A(i+1)(z) = Ai(z) + kBi(z) */

            Acc0 = L_deposit_h( Lpc[j] ) ;

            Acc0 = L_mac( Acc0, Pk, Temp[i-j-1] ) ;

            Lpc[j] = round( Acc0 ) ;

        }

    }

 

    return Err ;//lsc 返回的是最后一个分母,即残差信号的能量,这个返回值被用于计算cng时的增益估计

}

 

 

ok 到这里已完成了10 lpc系数计算了,

由于一个lpc系数的误差,会影响信号的每个频段(这一点由A(z)的形式很容易想出)

毕竟 A(z) = P1 * Z^(-10) + P2 * z^(-9) + ....

这个系统函数不是以因式分解形式给出的,必然有些问题

我们必须把它转化为因式分解的形式,然后对其求根,而每个根的误差只会影响某频域的能量,这样

就适合进行矢量量化了

 

于是构造了lsf系数,它实际是A(z)的变形,并且能把所有的根者限定的单位圆上,这样求lsf系数就可以

简单地沿着单位圆进行暴力搜索.(解一元10次方程是很复杂的,所以itu将求根过程简化了)

 

至此,已经分析完了g723的整个lpc求解过程,

笔者将在后继章节中讲述 lpc转成lsf的过程,

 

完成了声道系数求解,剩下的就是激励编码了,这些笔者均会在后继章节中进行详细分析

 

 

未完待继...

 

版权木有,笔者不对本文转载产生的任何后果负责(如耳麦炸裂,显示器花屏等)

 

                                                                 林绍川

                                                                 2011-04-15 于杭州

ITU-T G.729 Annex B ANSI-C Source Code Version 1.4 Last modified: November, 2000 */ TITLE ----- Fixed-point description of Recommendation G.729 with ANNEX B Coding of Speech at 8 kbit/s using Conjugate-Structure Algebraic-Code-Excited Linear-Prediction (CS-ACELP) with Voice Activity Decision(VAD), Discontinuous Transmission(DTX), and Comfort Noise Generation(CNG). SOFTWARE AND INTELLECTUAL PROPERTY ---------------------------------- This software package is provided as part of ITU-T Recommendation G.729B. Copyright (c) 1996, AT&T, France Telecom, Lucent Technologies, NTT, Rockwell International, Universite de Sherbrooke. All rights reserved. The copy of the source C code, version 1.4, is given under Copyright of the authors, only for the purpose of establishing the specification of a codec. VERSION ------- This is version 1.4. ---------------------------------------------------------------------------- Differences between Version 1.2 and Version 1.1 : In version 1.2, tab_dtx.h, tab_dtx.c, vad.c and calcexc.c were updated as per Corrigendum to annex B of G.729 published in COM 16-R20 of june 1997. ---------------------------------------------------------------------------- Differences between Version 1.3 and Version 1.2 : file : DEC_LD8K.C ***************** Version 1.2 lines 147 to 149 : ---------------------------------------------------------------------------- if(bfi == 1) if(past_ftyp == 1) ftyp = 1; else ftyp = 0; ---------------------------------------------------------------------------- replaced in Version 1.3 by lines 147 to 149 : ---------------------------------------------------------------------------- if(bfi == 1) { if(past_ftyp == 1) ftyp = 1; else ftyp = 0; *parm = ftyp; /* modification introduced in version V1.3 */ } file : BITS.C ************* function : read_frame() ---------------------------------------------------------------------------- Version 1.2 line 221-226 : ---------------------------------------------------------------------------- /* the hardware detects frame erasures by checking if all bits are set to zero */ parm[0] = 0; /* No frame erasure */ for (i=0; i < serial[1]; i++) if (serial[i+2] == 0 ) parm[0] = 1; /* frame erased */ ---------------------------------------------------------------------------- replaced in Version 1.3 by lines 219-230 : ---------------------------------------------------------------------------- /* This part was modified for version V1.3 */ /* for speech and SID frames, the hardware detects frame erasures by checking if all bits are set to zero */ /* for untransmitted frames, the hardware detects frame erasures by testing serial[0] */ parm[0] = 0; /* No frame erasure */ if(serial[1] != 0) { for (i=0; i < serial[1]; i++) if (serial[i+2] == 0 ) parm[0] = 1; /* frame erased */ } else if(serial[0] != SYNC_WORD) parm[0] = 1; ---------------------------------------------------------------------------- Differences between Version 1.4 and Version 1.3 : In Version 1.4 the initialization of lspSid in dec_sid.c has been updated according to the Corrigendum to Annex B of G.729 published in COM 16-R 60-E. In terms of functionality, only the file dec_sid.c has changed. For compilation purposes the two makefiles were changed, and the version number was changed in coder.c and decoder.c. The changes from Version 1.3 to Version 1.4 do not affect the test vectors. ---------------------------------------------------------------------------- DESCRIPTION ----------- This package includes the files needed to build the fixed point version of the G.729 codec with VAD/DTX/CNG as described in ANNEX B. It includes also the PC executable (coder.exe and decoder.exe), a batch file (test.bat), speech (test.inp) and data files (test.bit and test.syn) to verify the execution. The binary reference files are in PC format. SIMILARITIES AND DIFFERENCES WITH G.729 --------------------------------------- Common files with G.729 ~~~~~~~~~~~~~~~~~~~~~~~ acelp_co.c basic_op.c de_acelp.c dec_gain.c dec_lag3.c dspfunc.c filter.c gainpred.c lpcfunc.c lspgetq.c oper_32b.c p_parity.c pitch.c post_pro.c pre_proc.c pred_lt3.c pwf.c qua_gain.c basic_op.h oper_32b.h typedef.h File extracted from G.729 file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ taming.c Files in G.729 but modified for ANNEX B ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bits.c cod_ld8k.c coder.c dec_ld8k.c decoder.c lpc.c lspdec.c pst.c qua_lsp.c tab_ld8k.c util.c ld8k.h tab_ld8k.h Files only in ANNEX B ~~~~~~~~~~~~~~~~~~~~~ calcexc.c dec_sid.c dtx.c qsidgain.c qsidlsf.c tab_dtx.c vad.c dtx.h octet.h sid.h tab_dtx.h vad.h COMPILATION ----------- Edit the file typedef.h to comply to your target platform For UNIX systems the following makefiles are provided coder.mak decoder.mak Edit the makefiles coder.mak and decoder.mak to set the proper options for your system. The command to compile and link all code on a UNIX system is make -f coder.mak make -f decoder.mak For other platforms, the *.mak files can be used to work out the compilation procedures. This code has been successfully compiled and run on the following platforms: Platform Operating System Compiler ----------------------------------------------------------------------------- DEC ALPHA OSF/1 DEC OSF/1 cc SGI IRIX 5.2 cc SUN SUNOS 4.1.3 gcc PC DOS 6.2 Borland 4.02 Microsoft Quick C 2.5 Microsoft Visual C++ 1.51 Watcom 10.6 PC DOS 6.21 Borland 3.1 Microsoft 8 Watcom 9.6 USAGE ----- The following files are used or generated inputfile 8 kHz sampled data file 16 bit PCM (binary) outputfile 8 kHz sampled data file 16 bit PCM (binary) bitstreamfile binary file containing bitstream The following parameter is used for the encoder dtx_option = 1 : DTX enabled 0 : DTX disabled coder inputfile bitstreamfile dtx_option decoder bitstreamfile outputfile
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值