g723源码详细分析(四) 感知加权与基音周期搜索

本文深入解析了语音信号处理中的关键技术,包括感知加权滤波器的构造、语音信号的归一化处理、以及通过自相关法搜索基音周期的过程。讨论了如何利用历史数据和当前数据构建缓冲区,并详细解释了Estim_Pitch函数的工作原理。

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

5 感知加权与基音周期

Mem_Shift

这个函数的作用是

把先前保存的120个输入信号,与当前的240信号值,整成一个360的缓冲区buf,

并把当前的最后120个输入信号存入PrevData,

取buf的第60至299样值块(也就是一帧240)来做分析.

Wght_Lpc

构造感知加权滤波器

用lpc系数来构造形式如下

10 10

(1 - Σa(i) * 0.9^i * z^-1) / (1 - 1 - Σa(i) * 0.5^i * z^-1)

i=1 i=1

Error_Wght

构240个样值送入感知加权滤波器,得到感知加权后的语音信号

其计算过程不详述了,分为iir 和 fir两部分来运算,

代码完全根据加权滤波器的公式,照本宣科

然后做一些缓冲区拼接,将先前142个历史值,与当前的240个样值,拼起来(142跟基音周期有关,见下文)

/* Construct the buffer */

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

Dpnt[i] = CodStat.PrevWgt[i] ;

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

Dpnt[PitchMax+i] = DataBuff[i] ;

Vec_Norm

把信号归一化

Estim_Pitch

接下来就是基音周期搜索了.

采用的是自相关法来搜索基音周期的.

根据柯西定量 a^2+b^2 > 2ab 这样.

我们可以知道,一个语音信号的自相关值,一定会在它的基音周期处,达到最大值

(还有一种基音周期的估算法,叫做短时平均幅度差法,与自相关法不同的是,它在基音周期处,是谷值)

妇女150-300赫兹,儿童200-300赫兹);成年男子的声带长而厚,所以说话声音就低一些(60-200赫兹) 对应为采样率为

8000时 基音周期分别为26-133 (8000/60=133 8000/300=26),

itu对基音周期的搜索为 18-142

即PitchMin PitchMax的值

以下为一些声音频率的参考资料:

人说话时基频范围大约为100Hz~300Hz

深沉的男低音发出的最低音的频率可达65.4Hz。

花腔女高音发出的最高音的频率可达1177.2Hz。

人和一些动物的发声频率范围和听觉频率范围

名称 发声频率范围Δf/Hz 听觉频率范围Δf/Hz

人 65~1 100 20~20 000

狗 450~1 800 15~50 000

猫 760~1 500 60~6 500

蝙蝠 10 000~150 000 1 000~200 000

海豚 7 000~120 000 150~150 000

知更鸟 2 000~13 000 250~20 000

鱼 40~2 000 --- 鱼能发声吗?从没听过,呵呵

回到代码中来,723将语音帧分成两截分别求基音周期,每截为120样值点

j = PitchMax ;

for ( i = 0 ; i < SubFrames/2 ; i ++ ) {

Line.Olp[i] = Estim_Pitch( Dpnt, (Word16) j ) ;

VadStat.Polp[i+2] = Line.Olp[i] ;

j += 2*SubFrLen ;

}

Estim_Pitch采用的是自相关算法,即第一个峰值点的索引,就是基音周期了

即计算

n=119 n=119

( (Σ s[n] * s[n - j])^2 ) / (Σ s[n - j] * s[n - j]) 18<=j<=142

n=0 n=0

可以看出分母即能量 分子就是自相关函数

这里为了避开昂贵的除法运算,实际代码在比较是做了变形,

我们假设搜索目标值 分子为Da, 分母为Db, 当前搜索到的最大值分子为Ma, 分母为Mb

代码要做比较时,实际是这样的 Da*Mb - Db*Ma 结果大于零,来判断相应的值大小,推导很简单

根据不等式的性质直接推出

下面来看Estim_Pitch函数的实现过程

首先就是计算能量初始值,不用每次都将分母计算一遍,只需要在循环中更新能量即可(即添头,去尾)

/* Init the energy estimate */

Pr = Start - (Word16)PitchMin + (Word16)1 ;

Acc1 = (Word32) 0 ;

for ( j = 0 ; j < 2*SubFrLen ; j ++ )

Acc1 = L_mac( Acc1, Dpnt[Pr+j], Dpnt[Pr+j] ) ;

添头,去尾,达到更新能量的目的,也就是分母

/* Energy update */

Acc1 = L_msu( Acc1, Dpnt[Pr+2*SubFrLen], Dpnt[Pr+2*SubFrLen] ) ;

Acc1 = L_mac( Acc1, Dpnt[Pr], Dpnt[Pr] ) ;

计算自相关,这个也就是分子组成部分

/* Compute the cross */

Acc0 = (Word32) 0 ;

for ( j = 0 ; j < 2*SubFrLen ; j ++ )

Acc0 = L_mac( Acc0, Dpnt[Start+j], Dpnt[Pr+j] ) ;

接下来的代码比较绕,但是还是很容易理解的,

计算自相关的平方,得到了分子,并将其归一化

/* Compute Exp and mant of the cross */

Exp = norm_l( Acc0 ) ; //lsc 计算归一化分子所需要的左移位数

Acc0 = L_shl( Acc0, Exp ) ; //lsc 归一化分子

Exp = shl( Exp, (Word16) 1 ) ; //lsc 因为平方,所以指数在扩大两倍..

Ccr = round( Acc0 ) ;

Acc0 = L_mult( Ccr, Ccr ) ; //lsc 这里计算平方

Ccr = norm_l( Acc0 ) ; //lsc 再次归一化得到的结果

Acc0 = L_shl( Acc0, Ccr ) ;

Exp = add( Exp, Ccr ) ;

Ccr = extract_h( Acc0 ) ;

分母,即能量归一化

/* Do the same with energy */ //注意归一化后得到的指数的符号与原值是相反的,所以最大,相应为最小

Acc0 = Acc1 ;

Enr = norm_l( Acc0 ) ;

Acc0 = L_shl( Acc0, Enr ) ;

除法,对应分子分母的指数相减

Exp = sub( Exp, Enr ) ;

Enr = round( Acc0 ) ;

真值大于"1"的情况,所以右移一位后,又是归一化了,指数相应减少(因为是左移,这点要记住,否则你会认为指数应该加1)

if ( Ccr >= Enr ) {

Exp = sub( Exp, (Word16) 1 ) ;

Ccr = shr( Ccr, (Word16) 1 ) ;

}

接下来就是一段繁琐的比较代码,笔者分析了如下.大意就是比较大小

//lsc 指数小,说明值大,因为左移的

if ( Exp <= Mxp ) {

//lsc 绝对小,保存最大的自相关值以及相应的索引 大于1.25db的情况 即应该比最大值大1.33倍

if ( (Exp+1) < Mxp ) {//lsc 这是大4倍的情况,直接保留索引

Indx = (Word16) i ;

Mxp = Exp ;

Mcr = Ccr ;

Mnr = Enr ;

continue ;

}

if ( (Exp+1) == Mxp )//lsc 这是大两倍,需要整到同一个数量级,所在右移

Tmp = shr( Mcr, (Word16) 1 ) ;

else

Tmp = Mcr ;//lsc 指数一样,这就不用移了,可以直接相乘再相减判断大小

/* Compare with equal exponents */

Acc0 = L_mult( Ccr, Mnr ) ;

Acc0 = L_msu( Acc0, Enr, Tmp ) ;

if ( Acc0 > (Word32) 0 ) {

if ( ((Word16)i - Indx) < (Word16) PitchMin ) {//lsc 位置差别小于18,只要大,就选成

Indx = (Word16) i ;

Mxp = Exp ;

Mcr = Ccr ;

Mnr = Enr ;

}

else {//lsc 位置差别大于18,则还要考虑是否大了1.33倍,但这里似乎成了1.5倍...笔者怎么算都不符

Acc0 = L_mult( Ccr, Mnr ) ;

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

Acc0 = L_mac( Acc0, Ccr, Mnr ) ;

Acc0 = L_msu( Acc0, Enr, Tmp ) ;

if ( Acc0 > (Word32) 0 ) {

Indx = (Word16) i ;

Mxp = Exp ;

Mcr = Ccr ;

Mnr = Enr ;

}

}

}

}

最后返回得到的索引值 Indx 它就是基音周

为何要做这些?基音周期体现了语音数据的相关性,实际上后继的自适应码本,就是在基音周期的基础上进行搜索的,

通过对基音周期周围的历史5个激励源进行加成,得到自适应激励,笔者将在下一章分析这些,待续

林绍川

2011.05.16 于杭州

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值