1.前言本文档纯粹是个人理解,正确或错误请大家自己判别。另外,如果对此文档有改进建议,欢迎和我联系:bigstone1998@sina.com2.lpc基本原理LPC就是线性预测编码。从时域的角度,就是用历史的输出值,和当前的输入值,来判别当前的输出值。从传递函数的角度(Z变换),通常的传递函数既有零点,也有极点:∑∑=−=−−−=NiiiMjjjzazbGzH111)1()(理论上证明(我真的不知道如何证明的)如果分母的阶数够高,那么传递函数可以用全极点函数表示:)(1)(1zAGzaGzHNiii=−=∑=−通常我们都是取一小段语音来认为传递函数的参数a(i)是不变的。问题是,这样的长度N到底取多长?一方面,N越小,计算量也小,同时语音段的变化也越小。但是,如果期望用lpc的残差来估算pitch周期的话,那么就意味着N至少要是pitch周期的若干倍才行。通常取N为100~400(10KHz采样率)。对于一小段语音,可以认为传递函数的参数a(i)是不变的。这样,语音就可以用这样一些参数a(i)和输入激励来表示。至于为什么用全极点,而不用包含零点的传递函数来预测?主要是计算方便方面的原因。同时,极点可以代表语音频谱结构里面的峰值,也就是formant频率,下面有解释。其实对于鼻音,他们在传递函数上体现为零点,所以全极点类型对这种语音的代表是不完全的,尽管极点阶数高了,可以尽可能模拟零点。如上图,语音的输入激励其实就2种情况:voice是周期序列u(n),而unvoice是白噪声。那传递函数用多少阶极点比较合适呢?这就要对这个传递函数的物理意义进行理解。上图是一小段语音的原始频谱和不同极点阶数的LPC传递函数的对比图。从上面不难看到,阶数越高,那么对频谱的峰值匹配的越好。但是频谱的细节是丢失的。这点可以这样理解:¾频谱中的细节,其实是pitch的反映。而LPC只和昀近的几个输出语音相关,所以完全不能够覆盖到足以体现pitch周期的样本数量。也就是时域压缩,频域模糊。¾同时,LPC传递函数的极点,其实就是频谱中的峰值点的体现。在阶数p的选择上,和采样频率有关。人的语音基本上每KHz有2个极点(或一个复数极点)。这是vocaltract的贡献。这样,当采样频率为Fs(KHz)时候,需要Fs个pole来表示语音频谱。另外,还需要3~4个pole来表示源激励频谱和嘴巴辐射频谱,这样对于10Khz采样的语音,p值为13~14个。对于4KHz语音的分析而言,通常需要显示的共振峰至少4个,也就是说传递函数(或滤波器)的阶数至少8个。¾由于LPC传递函数没有零点,所以对频谱的波谷的模拟相对较差。理解到这来,LPC合成语音已经没有问题了。就是找出预测系数,再用激励输入,就得到输出语音了。可是,我们面对下一个问题:对于现成的语音,如何提取预测系数?好,现在抛开LPC什么的。我们还是用线性预测的思路,来考虑用历史值表示当前值:∑=−=pkkknsans1)()(~并试图找出误差昀小的a(k)的值。这种事情有些像数据模拟中的昀小二乘法之类的工作。不外乎就是计算误差,并对a(k)求导,取极值为0,来计算a(k),如下:∑=−−=−=pkkknsansnsnsne1)()()(~)()(对应的传递函数为:。于是,LPC的语音合成传递函数和误差传递函数就走到一起去了:∑=−−=pkkkzazA11)()()(zAGzH=。同时,e(n)也就成为了输入激励Gu(n)。所以A(z)也叫inversefilter。通过这个A(Z),就得到了语音合成时候的原始输入Gu(n)。这也就是对现有语音进行lpc参数计算的时候,得到残差的物理意义。通过这个残差,也得到语音合成的输入,并可以用来判别是否是voice/unvoice,以及提取pitch。当然,在依照语音来计算a(k)的时候,不同的算法,会得到不同精度的残差。自相关方法简单,但是在开始和结尾的时候的误差比较大,所以要加窗来抑制这个残差。而互相关残差比较精确,但是计算麻烦些。当然还要考虑计算方法的稳定性。昀稳定的是窗格法。在matlab中,也有lpc函数[a,g]=lpc(x,p)。其中,x是输入序列,p是阶数,a是预测系数,g是总预测误差。对于这些参数的理解,说明如下:注意如下几点:¾有效的参数是从a(2)开始。a(1)默认为1,是无用的;¾上图中的H函数,和前面我们提到的H函数是不一致的。这个H,是单纯线性预测的传递函数。而前面提到的H,是语音合成的传递函数。别搞混了。但是A函数的含义是一致的,都是inversefilter,输出的是残差。利用matlab函数,获取相关的lpc的过程量代码如下:%获取lpc参数[a,g]=lpc(x2,12);%绘制lpc的谱域图。要用合成的H,而不是上图的H。也就是1/A的Hfreqz(1,[1-a(2:end)]);%和原始输入x2的频谱对比y=x2;lx=length(y);Freqs=Fs;freq=[-Freqs/2:Freqs/lx:Freqs/2-Freqs/lx];S=fftshift(abs(fft(y)));Sdb=10*log10(S);plot(freq,Sdb);%由lpc参数,得到预测的x值。注意,a(1)被舍弃est_x=filter([0-a(2:end)],1,x);%计算原始x和预测x的差值。如果绘图的话,对于voice,可以看到和激励u(n)类似的周期性结构。e=x-est_x;%通过相关函数,可以计算pitch。[acs,lags]=xcorr(e,'coeff');plot(lags,acs);title('AutocorrelationofthePredictionError');xlabel('Lags');ylabel('NormalizedValue');grid;下图是一段实际语音的残差信号。从中可以看出一些基本的周期性:050100150200250300350400-0.04-0.03-0.02-0.0100.010.020.033.EnCode-lpc10从上面,发现了一些语言codec的东西。下载下来居然能够编译。于是想理解一下这些源码。代码量不大,但是几乎没有注释。真要命,也许我自己应该添加注释。从lpc10_encode_int的处理来看,这个代码对输入有如下假设:¾语音编码是16位编码。从对输入的归一化用32768就可以看出。16位是65536,考虑正负效果,就是32768。3.1.analys代码通读网上有很好的帮助,C代码和Matlab都有,但是matlab说的详细些,并更好理解:C代码::上面是两个例子。具体函数可以把函数名称替换就可以了。Hcrypt.c:hcryptNewSalt:看起来和加密相关。对加密这一块目前还没有概念。其实可以先不管。VOX:语音激活检测。HL_EXPintHL_APIENTRYhvdiPacketEncode/*InputParameter:*buflen:Thelengthofinputbuffer;**buffer:Theinputdatasample;*paclen:Themaxoutputencodeddatanumber;**key,*state:thecryptparamter**Outputparameter:**packet:theencodeddata;**Returnvalue:*int:theencodeddatalength;**/整个程序应该比较简单,基本功能就是把PCM语音进行压缩,然后再解压缩为PCM语音,昀后对比一下效果。每次取出2880个sample,进行处理,大概是360ms的数据。编码完成后,再立即解码,并把解码的文件保存,用于对比音质效果。对analys这个函数本身而言,它输入的是当前帧的语音数据。并且归一化到1。在analys代码中,相关的数据处理和数据结构如下:输入数据:inbuf=&(st-inbuf[0]);540个byte,共3帧数据;依照顺序存放。昀新的放在昀后面。虽然输入的speech是-1~1间的小数,但在进入这个analys程序时候,进行了放大,目的是speech可以用12个bit来表示:inbuf[720-LPC10_SAMPLES_PER_FRAME+i-180]=speech[i]*4096.f-bias;中间的数组有:¾预加重pebuf[540]。¾低通的数据lpbuf,存放696个。为啥是696个?不明白。但是每次新的数据帧来的时候,都是往前推180个数据。所以这里面其实存放了4个帧+156个多的数据。为什么lpbuf要大些?¾InverseFilteredspeech(ivbuff)。总共312个字节。可是有效数据好像只有131个字节。但每次来新数据的时候,原来131个数据都往前推180个位置。于是这样,好像昀多是存放了2帧的数据。否则,中间断开的垃圾数据就不好解释了。为什么ivbuf又要小些?因为ivbuff中放的是inversefilter后的数据。而inversefilter的目的,是为了得到残差,便于提取pitch周期。而为了这个目的,先对语音信号进行了800Hz的低通滤波,把高频成分滤掉,这样原来的采样率8K或10KHz就没有意义了,完全可以降采样率后再处理,减少计算量。ivbuf是312个,因为inversefilter后180个值,而用于ADMF算法时候,差值昀大156,而又只要计算156个点,所以312就够了。¾Onsetbuffer。总共10个。当新数据来的时候,检查原来的onset是否已经被推出去。并推到昀前面去。¾Voicedecisionbuff,voibuf[8],存放了4帧的的voice判别数据,每帧分前后两部分。¾VWin。是voice的判别窗口。物理含义还不是很明确。Analys的基本思路是:用高通和预加重后的数据,来寻找onset的位置。Onset的位置往后多加了180偏移,导致后面使用要把此偏移去掉;根据onset的位置来设置vwin的位置。Vwin是用来干嘛的?接下去是要来寻找pitch的周期。寻找pitch周期的方法是:用通过高通后的原始语音,再经过低通,把pitch外的高频成分滤掉。然后用ivfilt进行反向滤波,找出残差信号。在对残差信号进行ADMF算法,差值昀大156,昀多算156个点,只需要312个值就够了。这样找到pitch周期。注意:算法中ivfilt和ADMF算法并不是对当前帧进行处理,而是对历史上的前一帧进行处理。这是因为ivfilt有不同算法,有些算法需要后一帧的数据。虽然代码中没有用这种算法,但是兼容上考虑,就保留了这个时延。接下去是voicing。Voicing依照模式匹配算法进行。得到当前处理帧(历史上的前一帧)的UV结果。在接下去对UV判别结果进行平滑的时候,是根据当前处理帧的结果,对当前处理帧的前2帧进行平滑。昀后,在voicing内,根据当前处理帧的UV结果,对一些常数进行更新。整个过程的时序关系如下:昀新数据昀后说明一下,由于pitch的计算,引入了2帧的时延,所以analys在返回的时候,返回的3.1.1.HP100Hp100函数说明:://read.pudn.com/downloads24/sourcecode/comm/voice_compress/76468/lpc10/src/hp100.c__是上图的第0帧的数据。在http.htm和网站,都有该函数的说明:100HzHigh22121222122111111212221121nZnZnZnynZnZn