g723源码详细分析-15-静音检测
现在来分析静音检测
语音通话,基本上是一方听,一方说,采用静音检测可以起到节省一半带宽的作用
网络上有很多静音检测的代码,基本的思路,都是构造一个自适应的能量探测试,
低于阀值时,就认为出现静音
g723的思路基本与此相同
Comp_Vad 这个函数负责静音栽决,看代码吧,
这里笔者只分析算法,不再纠结定点数运算引起的数值缩放的问题了
g723的静音检测,检测了当前帧的180个样点(后60样点由于没有在当前帧里处理)
首先看到ScfTab这个静态数组
因为门限值的计算涉及到log,采用了类似711里的算法,用一系列折线段来近似指数函数
底为0.89 参考门限值公式10^(-0.05)约等于0.89
static Word16 ScfTab[11] = {//lsc 这里是一系列折线的斜率,用于计算近似的指数函数 注意:因为采用的是归一化的位移作自变量,所以这个表是递增的,把0.89的指数函数的第一象限"反"过来看(这里显得很绕,笔者也纠结了很久)
9170 ,
9170 ,
9170 ,
9170 ,//lsc 0.277 * 32768 = 9093 这个点基本吻合 0.277 为 0.89^11
10289 ,//lsc 0.313995361328125=10,
11544 ,//lsc 0.352294921875=9?
12953 ,//lsc 0.395=8?
14533 ,//lsc 0.4435=0.89^7 ?
16306 ,//lsc 6 0.49761962890625=0.89^6
18296 ,//lsc 5 0.558349609375=0.89^5
20529 ,//lsc 0.626=0.89^4
} ;
这个数组里的值为0.89^(n)扩大32768倍, n取值范围(4~11)
从四个子帧里找出基音周基最小的一个,存入Minp
/* Find Minimum pitch period */ //lsc VadStat.Polp 是从Line.Olp中取来的,每一帧保存两个(因为基音周期是每120采样计算一次)
Minp = PitchMax ;
for ( i = 0 ; i < 4 ; i ++ ) {
if ( Minp > VadStat.Polp[i] )
Minp = VadStat.Polp[i] ;
}
判断当前帧是否处理浊音段,依据为,如果所有子帧的基音周期都约为Minp的整数倍,
即认为当前帧是浊音段,代码片段
//lsc itu的中文翻译有误,当tm2=4时,应认为是浊音,见itu的英文版,是voice
Tm2 = 0 ;
for ( i = 0 ; i < 4 ; i ++ ) {
Tm1 = Minp ;
for ( j = 0 ; j < 8 ; j ++ ) {
Tm0 = sub( Tm1, VadStat.Polp[i] ) ;
Tm0 = abs_s( Tm0 ) ;
if ( Tm0 <= 3 )//lsc 如果在倍数附近,差值不大于3,为浊音加分,都在倍数附近,就是浊音了
Tm2 ++ ;
Tm1 = add( Tm1, Minp ) ;//lsc 用减法和加法,循环8次,代替除法
}
}
尾响处理,如果是浊音,之后的6个帧会被认为非静音,这个处理是避免元音段被不正确地截了
代码片段
/* Update adaptation enable counter if not periodic and not sine */
if ( (Tm2 == 4) || (CodStat.SinDet < 0) )//lsc 浊音,要添加尾响
VadStat.Aen += 2 ;
else
VadStat.Aen -- ;
/* Clip it */
if ( VadStat.Aen > 6 )//lsc 尾响限制为6帧
VadStat.Aen = 6 ;
if ( VadStat.Aen < 0 )
VadStat.Aen = 0 ;
与网络上流行的算法不同的是,g723做了滤波,对静音的判断是基于激励的能量
逆向滤波代码片段如下:
//lsc 逆向滤波
/* Inverse filter the data */
Acc1 = 0L ;
for ( i = SubFrLen ; i < Frame ; i ++ ) {
Acc0 = L_mult( Dpnt[i], 0x2000 ) ;
for ( j = 0 ; j < LpcOrder ; j ++ )
Acc0 = L_msu( Acc0, Dpnt[i-j-1], VadStat.NLpc[j] ) ;
Tm0 = round ( Acc0 ) ;
Acc1 = L_mac( Acc1, Tm0, Tm0 ) ;//lsc 计算出能量
}
其中的VadStat.NLpc这个数组,是在计算舒适背景音时,形成的一个平均滤波器,笔者将
在介绍舒适背景音时详细介绍这个lpc系数的生成,这里,只需要知道它是一个滤波器,
可以得到残差信号即可
噪声能量估值
/* Scale the rezidual energy */
Acc1 = L_mls( Acc1, (Word16) 2913 ) ;//lsc 这可能是除11.22(32768/2913=11.24),噪声能量估值 2913 * 11.22 = 32684(32767?) 10^1.05 = 11.220184543019634355910389464779
这里要注意,观察itu g723文档的vad章节的 A-5公式,它把10^1.05这个因子挪过来了,而把80这个因子移给了thrd的计算,绕得很
对噪声能量做个限制
/* Clip noise level in any case */ //lsc 如果噪声能量太大,更新噪声的能量,加前一帧噪声能量估值的3/4,再取1/4,形成当前噪声能量
if ( VadStat.Nlev > VadStat.Penr ) {
Acc0 = L_sub( VadStat.Penr, L_shr( VadStat.Penr, 2 ) ) ;
VadStat.Nlev = L_add( Acc0, L_shr( VadStat.Nlev, 2 ) ) ;
}
根据当前帧是元音还是辅音,做一个适当放大处理,盖因辅音段,在g723的编码模型里,就认为是由一个随机信号激励形成的
/* Update the noise level, if adaptation is enabled */
if ( !VadStat.Aen ) {//lsc 如果是清音
VadStat.Nlev = L_add( VadStat.Nlev, L_shr( VadStat.Nlev, 5 ) ) ;
}
/* Decay Nlev by small amount */
else {//lsc 如果是浊音,或者处于尾响阶段
VadStat.Nlev = L_sub( VadStat.Nlev, L_shr( VadStat.Nlev,11 ) ) ;
}
近一步限制噪声能量的取值范围 最小128 最大16383
/* Update previous energy */
VadStat.Penr = Acc1 ;
/* CLip Noise Level */
if ( VadStat.Nlev < 0x00000080L )
VadStat.Nlev = 0x00000080L ;
if ( VadStat.Nlev > 0x0001ffffL )
VadStat.Nlev = 0x0001ffffL ;
接来计算门限系数,这里涉及到用折线段来模拟指数函数,
知道是这么处理的就可以了.
最后将噪声能量估值与门限系数相乘,然后与激励能量比较,来判断是否为静音帧
代码片段如下:
/* Compute the threshold */ //lsc 这里计算门限,本质而言,还是一个能量探测器
Acc0 = L_shl( VadStat.Nlev, 13 ) ;
Tm0 = norm_l( Acc0 ) ;
Acc0 = L_shl( Acc0, Tm0 ) ;
Acc0 &= 0x3f000000L ;//lsc 笔者认为,itu可能把10这个因子塞在这里了,把10拆成(10^0.005)^20,塞进指数里,做了缩放,纠结的运算啊
Acc0 <<= 1 ;//lsc
Tm1 = extract_h( Acc0 ) ;
Acc0 = L_deposit_h( ScfTab[Tm0] ) ;
Acc0 = L_mac( Acc0, Tm1, ScfTab[Tm0-1] ) ;//lsc 这两行计算斜率,插值
Acc0 = L_msu( Acc0, Tm1, ScfTab[Tm0] ) ;
Tm1 = extract_h( Acc0 ) ;
Tm0 = extract_l( L_shr( VadStat.Nlev, 2 ) ) ;
Acc0 = L_mult( Tm0, Tm1 ) ;
Acc0 >>= 11 ;//lsc 这里隐含着扩大8位, 15+1-2=14 而只右移11位,则扩大了8倍,这样基本算是找到了文档的1/80分之一的因子了
/* Compare with the threshold */ //lsc 0表示为静音 笔者认为文档中的描述有误,应该是thr*nlev与能量比较,至少从代码看,是这样,纠结啊
if ( Acc0 > Acc1 )
VadState = 0 ;
尾响处理,如果是元音,后面的6帧认为非静音,比较简单,笔者就不列出代码了
总结:
g723的能量检测仍然基于能量检测的
林绍川
2012.1.5 于杭州
语音通话,基本上是一方听,一方说,采用静音检测可以起到节省一半带宽的作用
网络上有很多静音检测的代码,基本的思路,都是构造一个自适应的能量探测试,
低于阀值时,就认为出现静音
g723的思路基本与此相同
Comp_Vad 这个函数负责静音栽决,看代码吧,
这里笔者只分析算法,不再纠结定点数运算引起的数值缩放的问题了
g723的静音检测,检测了当前帧的180个样点(后60样点由于没有在当前帧里处理)
首先看到ScfTab这个静态数组
因为门限值的计算涉及到log,采用了类似711里的算法,用一系列折线段来近似指数函数
底为0.89 参考门限值公式10^(-0.05)约等于0.89
static Word16 ScfTab[11] = {//lsc 这里是一系列折线的斜率,用于计算近似的指数函数 注意:因为采用的是归一化的位移作自变量,所以这个表是递增的,把0.89的指数函数的第一象限"反"过来看(这里显得很绕,笔者也纠结了很久)
9170 ,
9170 ,
9170 ,
9170 ,//lsc 0.277 * 32768 = 9093 这个点基本吻合 0.277 为 0.89^11
10289 ,//lsc 0.313995361328125=10,
11544 ,//lsc 0.352294921875=9?
12953 ,//lsc 0.395=8?
14533 ,//lsc 0.4435=0.89^7 ?
16306 ,//lsc 6 0.49761962890625=0.89^6
18296 ,//lsc 5 0.558349609375=0.89^5
20529 ,//lsc 0.626=0.89^4
} ;
这个数组里的值为0.89^(n)扩大32768倍, n取值范围(4~11)
从四个子帧里找出基音周基最小的一个,存入Minp
/* Find Minimum pitch period */ //lsc VadStat.Polp 是从Line.Olp中取来的,每一帧保存两个(因为基音周期是每120采样计算一次)
Minp = PitchMax ;
for ( i = 0 ; i < 4 ; i ++ ) {
if ( Minp > VadStat.Polp[i] )
Minp = VadStat.Polp[i] ;
}
判断当前帧是否处理浊音段,依据为,如果所有子帧的基音周期都约为Minp的整数倍,
即认为当前帧是浊音段,代码片段
//lsc itu的中文翻译有误,当tm2=4时,应认为是浊音,见itu的英文版,是voice
Tm2 = 0 ;
for ( i = 0 ; i < 4 ; i ++ ) {
Tm1 = Minp ;
for ( j = 0 ; j < 8 ; j ++ ) {
Tm0 = sub( Tm1, VadStat.Polp[i] ) ;
Tm0 = abs_s( Tm0 ) ;
if ( Tm0 <= 3 )//lsc 如果在倍数附近,差值不大于3,为浊音加分,都在倍数附近,就是浊音了
Tm2 ++ ;
Tm1 = add( Tm1, Minp ) ;//lsc 用减法和加法,循环8次,代替除法
}
}
尾响处理,如果是浊音,之后的6个帧会被认为非静音,这个处理是避免元音段被不正确地截了
代码片段
/* Update adaptation enable counter if not periodic and not sine */
if ( (Tm2 == 4) || (CodStat.SinDet < 0) )//lsc 浊音,要添加尾响
VadStat.Aen += 2 ;
else
VadStat.Aen -- ;
/* Clip it */
if ( VadStat.Aen > 6 )//lsc 尾响限制为6帧
VadStat.Aen = 6 ;
if ( VadStat.Aen < 0 )
VadStat.Aen = 0 ;
与网络上流行的算法不同的是,g723做了滤波,对静音的判断是基于激励的能量
逆向滤波代码片段如下:
//lsc 逆向滤波
/* Inverse filter the data */
Acc1 = 0L ;
for ( i = SubFrLen ; i < Frame ; i ++ ) {
Acc0 = L_mult( Dpnt[i], 0x2000 ) ;
for ( j = 0 ; j < LpcOrder ; j ++ )
Acc0 = L_msu( Acc0, Dpnt[i-j-1], VadStat.NLpc[j] ) ;
Tm0 = round ( Acc0 ) ;
Acc1 = L_mac( Acc1, Tm0, Tm0 ) ;//lsc 计算出能量
}
其中的VadStat.NLpc这个数组,是在计算舒适背景音时,形成的一个平均滤波器,笔者将
在介绍舒适背景音时详细介绍这个lpc系数的生成,这里,只需要知道它是一个滤波器,
可以得到残差信号即可
噪声能量估值
/* Scale the rezidual energy */
Acc1 = L_mls( Acc1, (Word16) 2913 ) ;//lsc 这可能是除11.22(32768/2913=11.24),噪声能量估值 2913 * 11.22 = 32684(32767?) 10^1.05 = 11.220184543019634355910389464779
这里要注意,观察itu g723文档的vad章节的 A-5公式,它把10^1.05这个因子挪过来了,而把80这个因子移给了thrd的计算,绕得很
对噪声能量做个限制
/* Clip noise level in any case */ //lsc 如果噪声能量太大,更新噪声的能量,加前一帧噪声能量估值的3/4,再取1/4,形成当前噪声能量
if ( VadStat.Nlev > VadStat.Penr ) {
Acc0 = L_sub( VadStat.Penr, L_shr( VadStat.Penr, 2 ) ) ;
VadStat.Nlev = L_add( Acc0, L_shr( VadStat.Nlev, 2 ) ) ;
}
根据当前帧是元音还是辅音,做一个适当放大处理,盖因辅音段,在g723的编码模型里,就认为是由一个随机信号激励形成的
/* Update the noise level, if adaptation is enabled */
if ( !VadStat.Aen ) {//lsc 如果是清音
VadStat.Nlev = L_add( VadStat.Nlev, L_shr( VadStat.Nlev, 5 ) ) ;
}
/* Decay Nlev by small amount */
else {//lsc 如果是浊音,或者处于尾响阶段
VadStat.Nlev = L_sub( VadStat.Nlev, L_shr( VadStat.Nlev,11 ) ) ;
}
近一步限制噪声能量的取值范围 最小128 最大16383
/* Update previous energy */
VadStat.Penr = Acc1 ;
/* CLip Noise Level */
if ( VadStat.Nlev < 0x00000080L )
VadStat.Nlev = 0x00000080L ;
if ( VadStat.Nlev > 0x0001ffffL )
VadStat.Nlev = 0x0001ffffL ;
接来计算门限系数,这里涉及到用折线段来模拟指数函数,
知道是这么处理的就可以了.
最后将噪声能量估值与门限系数相乘,然后与激励能量比较,来判断是否为静音帧
代码片段如下:
/* Compute the threshold */ //lsc 这里计算门限,本质而言,还是一个能量探测器
Acc0 = L_shl( VadStat.Nlev, 13 ) ;
Tm0 = norm_l( Acc0 ) ;
Acc0 = L_shl( Acc0, Tm0 ) ;
Acc0 &= 0x3f000000L ;//lsc 笔者认为,itu可能把10这个因子塞在这里了,把10拆成(10^0.005)^20,塞进指数里,做了缩放,纠结的运算啊
Acc0 <<= 1 ;//lsc
Tm1 = extract_h( Acc0 ) ;
Acc0 = L_deposit_h( ScfTab[Tm0] ) ;
Acc0 = L_mac( Acc0, Tm1, ScfTab[Tm0-1] ) ;//lsc 这两行计算斜率,插值
Acc0 = L_msu( Acc0, Tm1, ScfTab[Tm0] ) ;
Tm1 = extract_h( Acc0 ) ;
Tm0 = extract_l( L_shr( VadStat.Nlev, 2 ) ) ;
Acc0 = L_mult( Tm0, Tm1 ) ;
Acc0 >>= 11 ;//lsc 这里隐含着扩大8位, 15+1-2=14 而只右移11位,则扩大了8倍,这样基本算是找到了文档的1/80分之一的因子了
/* Compare with the threshold */ //lsc 0表示为静音 笔者认为文档中的描述有误,应该是thr*nlev与能量比较,至少从代码看,是这样,纠结啊
if ( Acc0 > Acc1 )
VadState = 0 ;
尾响处理,如果是元音,后面的6帧认为非静音,比较简单,笔者就不列出代码了
总结:
g723的能量检测仍然基于能量检测的
林绍川
2012.1.5 于杭州