現在來分析靜音檢測
語音通話,基本上是一方聽,一方說,采用靜音檢測可以起到節省一半帶寬的作用
網絡上有很多靜音檢測的代碼,基本的思路,都是構造一個自适應的能量探測試,
低于閥值時,就認為出現靜音
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
//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個幀會被認為非靜音,這個處理是避免元音段被不正确地截了
代碼片段
if ( (Tm2 == 4) || (CodStat.SinDet < 0) )//lsc 濁音,要添加尾響
VadStat.Aen += 2 ;
else
VadStat.Aen -- ;
if ( VadStat.Aen > 6 )//lsc 尾響限制為6幀
VadStat.Aen = 6 ;
if ( VadStat.Aen < 0 )
VadStat.Aen = 0 ;
與網絡上流行的算法不同的是,g723做了濾波,對靜音的判斷是基于激勵的能量
逆向濾波代碼片段如下:
//lsc 逆向濾波
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系數的生成,這裡,隻需要知道它是一個濾波器,
可以得到殘差信号即可
噪聲能量估值
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的計算,繞得很
對噪聲能量做個限制
//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的編碼模型裡,就認為是由一個随機信号激勵形成的
if ( !VadStat.Aen ) {//lsc 如果是清音
VadStat.Nlev = L_add( VadStat.Nlev, L_shr( VadStat.Nlev, 5 ) ) ;
}
else {//lsc 如果是濁音,或者處于尾響階段
VadStat.Nlev = L_sub( VadStat.Nlev, L_shr( VadStat.Nlev,11 ) ) ;
}
近一步限制噪聲能量的取值範圍 最小128 最大16383
VadStat.Penr = Acc1 ;
if ( VadStat.Nlev < 0x00000080L )
VadStat.Nlev = 0x00000080L ;
if ( VadStat.Nlev > 0x0001ffffL )
VadStat.Nlev = 0x0001ffffL ;
接來計算門限系數,這裡涉及到用折線段來模拟指數函數,
知道是這麼處理的就可以了.
最後将噪聲能量估值與門限系數相乘,然後與激勵能量比較,來判斷是否為靜音幀
代碼片段如下:
//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分之一的因子了
//lsc 0表示為靜音 筆者認為文檔中的描述有誤,應該是thr*nlev與能量比較,至少從代碼看,是這樣,糾結啊
if ( Acc0 > Acc1 )
VadState = 0 ;
尾響處理,如果是元音,後面的6幀認為非靜音,比較簡單,筆者就不列出代碼了
總結:
g723的能量檢測仍然基于能量檢測的
林紹川
2012.1.5 于杭州