在VOIP的音頻算法中,回音處理已經(jīng)成為一個關系通話質(zhì)量的主要問題。
回聲的產(chǎn)生在IP網(wǎng)絡主要有兩種:
1.聲學回聲
2.電路回聲
聲學回聲主要又分成以下幾種:
a ) 直接回聲:由揚聲器產(chǎn)生的聲音未經(jīng)任何反射直接進入麥克風
b ) 間接回聲: 由揚聲器發(fā)出的聲音經(jīng)過多次反射后,再進入Mic
對于第二種回聲,擁有多路徑,時變性的特點.是比較難處理的.
由于IP網(wǎng)絡下的傳輸?shù)难舆t較大,而一般情況下,對于人耳,如果聲音延遲達到了10ms以上的話,那么回聲就會越來越明顯.
一般來講,VOIP中的聲音延遲主要來自于幾個方面:
1. 編碼延遲: 一般情況下編碼算法在聲音壓縮時都會產(chǎn)生延遲,就我們采用的Speex來講,延遲大概在20ms左右
2. 處理延遲, 封裝時延, 緩沖時延等
3. 在IP網(wǎng)絡中數(shù)據(jù)的傳輸過程也會照成延時.這由當前的網(wǎng)絡狀況決定.
回聲消除的模型:
a) 建立遠端聲音模型,進行回聲估計, 從采集的值中減去估計值
b) 聲學模型
Speex manul(手冊)中文版
Speex是一套專門用于壓縮聲音的庫,由于其專門針對聲音,所以壓縮聲音的性能非常高.Speex由于其壓縮性能,及0.80版后的跨平臺的性能,所以在網(wǎng)絡聲音的傳輸中有很大的價值.但是需要注意的是speex只能對聲音進行壓縮,不支持音樂的壓縮,如果你需要音樂的壓縮你或許需要用vorbis庫.
但是的speex資料像其它大都數(shù)專用庫一樣,并沒有大多的中文資料.所以在這里我決定將里面最核心的編程技術(shù)翻譯出來.一來是練習練習自己翻譯資料的能力,二來是方便一些英語水平較差的朋友.由于本人能力有限,有些感覺有出入或難理解的地方可以去speex的官方網(wǎng)站www.speex.org找到英文原版的說明.
翻譯的一些說明:
1,對于一些專有名詞如speex,api不過行翻譯
2,對于一些新概念翻譯,以及其它的翻譯過來也許會產(chǎn)生歧義的文字,用中/英兩種方式標出:
如:對話(speech),位采集(bit_packing)
3,基本做到和英文原行對譯.及英文原文一行,中文翻譯過來也是一行,使翻譯后的文章和原文基本行行對照.
4,源代碼不翻譯,如SpeexBits bits;
5,語言中的關健字不譯,如float
6,一此不是關健字但英文通常出現(xiàn)的詞第一次以中/英文格式給出,之后按具體情況給出英文或中文,如:frame(幀),
7,對一些有自己翻譯起來感覺有歧義的地方,加斜線作標記
1,speex的介紹(略)
2特征描述
這個章節(jié)展示了speex的主要特征,以衣一些關于對話(speech)編碼的一個概念,以便
幫助我們更好的了解下一章節(jié).
取樣率(Sampling rate)
Speex主要是設計了三種不同的取樣率:8kHz,16kHz,32kHz.這些分別代表了窄寬(narrowband),
多頻率,超聲.
質(zhì)量
Speex編碼大都數(shù)時間是被一個范圍為0到10的質(zhì)量參數(shù)來控制 的.在一個比特率為常量(CBR)的操作中,質(zhì)量參數(shù)是一個整數(shù),而對于變動的比特率(VBR)參數(shù)是一個float;
復雜性(變量)
用speex,你可以將編碼設置成允許的復雜度.這由一個范圍為1到10的整數(shù)來控制完成,就像你用選項-1到-9來控制gzip和bzip2的壓縮質(zhì)量.在通常的運用中,噪聲級別的復雜度1是在1到2dB之間,比復雜度10要高,但是CPU需要復雜度10大概5倍高行復雜度1.在實踐中,最好的是設置在2到4之間,盡管更高的設定通常有用,當編碼一個非對話聲音(non-speech sounds)像DTMF語調(diào)(tones).
變波特率(VBR
Variable bit-rate (VBR) allows a codec to change its bit-rate dynamically to adapt to
變波特率(VBR)允許編碼動態(tài)地改變它的波特率以適應聲音編碼的”難度”.在speex舉例來說,
像元音(vowel)和瞬間高音(high-enenrg transients)需要個高的比特率來
取得一個不錯的質(zhì)量,
而摩擦音(fricative)可以被充分地用相對較少的字節(jié)來進行編碼.
由于上面這個原因,VBR可以調(diào)節(jié)到一個低的比特率卻達到一個同樣的質(zhì)量,或者用
某個比特率達到更好的質(zhì)量.盡管有上面這些優(yōu)點,但是VBR也有兩個主要的缺點.
首先,僅僅靠指定質(zhì)量值,這里沒有一個關于最后平均比特率的保證.(譯者注:作者大概是想說沒有什么明確的方法知道質(zhì)量值)此外,對一些即時
通信,像IP電話(VoIP)這種包含著最大的比特率的,必須把比特率設為足夠低以適應
傳輸通道.
r
平均比特率(ABR)
平均比特率通過動態(tài)地調(diào)節(jié)VBR質(zhì)量去得到一個確定的目標的比特率,從而解決了VBR中的一個問題..因為質(zhì)量/比特率被即時的調(diào)整了,整體質(zhì)量將會稍稍低于由VBR對一個
設置得和目標平均比特率非常接近的質(zhì)量數(shù)編碼得到的結(jié)果.
聲音生動性檢測(VAD)
聲音生動性檢測將會發(fā)覺音頻正在被編碼成對話,靜音,或背景噪音.VAD總在用VBR進行編碼時暗中起作用,因此選項僅僅對一個不是VBR的操作起作用.對于不是VBR的操作來說,speex察覺出一個不屬于對話的周期,然后對它用足夠的字節(jié)重新生成為背景噪音.不這叫做舒適的噪音生成(CNG).
不連續(xù)傳輸(DTX)
不連續(xù)傳輸是VAD/VBR操作的一個額外選項,當背景噪音一定時,它可以完整地傳輸.因為在基于文件的操作中,我們不能停止對文件進行寫入,所以只有5字節(jié)被這種幀所運用.(給250bps通信)
Perceptual enhancement
知覺增強
知覺增加是解碼的一部分,它在打開的時候用來減少由編碼解碼所產(chǎn)生的噪音.在大都數(shù)
情況下,知覺增強在客觀上使聲音離原始值更遠(如果用SNR),但是在最后它仍然聽起來更好(主觀上的改進)
Algorithmic delay
遲時算法
每一個聲音編碼導致了在傳輸上的延時.對于speex,這種延時等于frame的大小加上一些
數(shù)量的需要對每一幀進行的前瞻(”look-adhead”).
在窄寬操作中(8kHz),遲時是30ms,而對于多頻率(2-44Hz),遲時是34ms.這些值
不包括CPU編碼,解碼幀的時間.
用speex編程(the libspeex api)
這個章節(jié)出要講述了如何用speex api進行編程.例子的源代友你也可以在附錄B中找到
4.1 Encoding
4.1壓縮
為了用Speex壓縮對話,你首先需要引用頭文件:
#include <speex.h>
然后你需要定義一個Speex的位采集(bit-packing)結(jié)構(gòu)
SpeexBits bits;
and a Speex encoder state
以及定義一個speex編碼器狀態(tài)量
void *enc_state;
上面定義的這樣被初始化:
The two are initialized by:
speex_bits_init(&bits);
enc_state = speex_encoder_init(&speex_nb_mode);
為了支持多頻率的壓縮,speex_nb_mode將被sppex_wb_mode取代.在大都數(shù)
情況下,你需要知道你用的模式(mode)的幀(frame)的大小,你可以得到在frame_size變量里得到這值:
speex_encoder_ctl(enc_state,SPEEX_GET_FRAME_SIZE,&frame_size);
一但初始化完畢,對于每一個輸入幀:
speex_bits_reset(&bits);
speex_encode(enc_state, input_frame, &bits);
nbBytes = speex_bits_write(&bits, byte_ptr, MAX_NB_BYTES);
上面input_frame是一個指向?qū)υ?/span>(speech)幀(frame)的float指針(pointing);byte_ptr
是指向編碼幀開始寫的地方的char指針,MAX_NB_BYTES是能
寫進byte_ptr而不會造成溢出的最大數(shù).nbBytes是一個實際寫入btye_ptr的數(shù),即編碼的實際大小
在調(diào)用speex_bits_write前,可能你需要調(diào)用speex_bits_nbytes(&bits)得到需要寫入(write)的字節(jié)大小.
在你已經(jīng)編碼后,釋放所有的資源.
speex_bits_destroy(&bits);
speex_encoder_destroy(enc_state);
That’s about it for the encoder.
這就是關于編碼的方面.
Speex manul中文版三
附源代碼的翻譯:
B Sample code
B 例程源代碼
這個章節(jié)演示了一段用speex編碼,解碼對話(speech)的源代碼.
可以如下用api命令來編碼并解碼一個文件:
譯者注:這里說的api命令是指unix的用”|”進行管道寫入讀出.在windows下這樣并不能實現(xiàn).
% sampleenc in_file.sw | sampledec out_file.sw
這里這兩段代碼都沒有引用其它的頭文件,并以16 比特率(bits)進行編碼
natural endianness).
B.1 sampleenc.c
Sameleenc 用一個未加工的16比特率(bits)文章,給它編碼并產(chǎn)生一個speex 流(steam)給標準輸出.注意已壓縮的和speexenc/speexdec不和諧!
#include <speex.h>
#include <stdio.h>
/*幀的大小在這個例程中是一個固定的值,但它并不是必須這樣*/
#define FRAME_SIZE 160
int main(int argc, char **argv)
{
char *inFile;
FILE *fin;
short in[FRAME_SIZE];
float input[FRAME_SIZE];
char cbits[200];
int nbBytes;
/*保存編碼的狀態(tài)*/
void *state;
/*保存字節(jié)因此他們可以被speex常規(guī)讀寫*/
SpeexBits bits;
int i, tmp;
//新建一個新的編碼狀態(tài)在窄寬(narrowband)模式下
state = speex_encoder_init(&speex_nb_mode);
//設置質(zhì)量為8(15kbps)
tmp=8;
speex_encoder_ctl(state, SPEEX_SET_QUALITY, &tmp);
inFile = argv[1];
fin = fopen(inFile, "r");
//初始化結(jié)構(gòu)使他們保存數(shù)據(jù)
speex_bits_init(&bits);
while (1)
{
//讀入一幀16bits的聲音
fread(in, sizeof(short), FRAME_SIZE, fin);
if (feof(fin))
break;
//把16bits的值轉(zhuǎn)化為float,以便speex庫可以在上面工作
for (i=0;i<FRAME_SIZE;i++)
input[i]=in[i];
//清空這個結(jié)構(gòu)體里所有的字節(jié),以便我們可以編碼一個新的幀
speex_bits_reset(&bits);
//對幀進行編碼
speex_encode(state, input, &bits);
//把bits拷貝到一個利用寫出的char型數(shù)組
nbBytes = speex_bits_write(&bits, cbits, 200);
//首先寫出幀的大小,這是sampledec文件需要的一個值,但是你的應用程序中可能不一樣
fwrite(&nbBytes, sizeof(int), 1, stdout);
//寫出壓縮后的數(shù)組
fwrite(cbits, 1, nbBytes, stdout);
}
//釋放編碼器狀態(tài)量
speex_encoder_destroy(state);
//釋放bit_packing結(jié)構(gòu)
speex_bits_destroy(&bits);
fclose(fin);
return 0;
}
在Speex(www.speex.org)的最新版本中,開始集成了回音消除的模塊,而回音消除一直是Voip之中亟待解決的主要問題。
很多朋友和我說speex的aec模塊的效能并不好,我們先來看一下speex的aec的api調(diào)用方式。
/*
*創(chuàng)建AEC對象
*/
SpeexEchoState *echo_state = speex_echo_state_init(frame_size, filter_length);
frame_size 的取值最好是一個編碼的frame大小, 在低帶寬條件下,一般延遲20ms,而大小為160
filter_length,最好是房間內(nèi)反射時間的1/3
如: 一個房間的反射時延為300ms
那么這個filter_length就最好是100ms(這個長度又被稱為tail length).
而其中filter_length的設定是一個關鍵。
/*
*執(zhí)行AEC
*/
speex_echo_cancel(echo_state, input_frame, echo_frame, output_frame, residue);
其中:
input_frame: 就是被聲卡捕捉到的聲音
echo_frame: 是由揚聲器播放出的聲音,這個聲音是需要從 input_frame中抵消的聲音.
output_frame 是處理完以后輸出的聲音
residue是一個可選參數(shù),如果不使用可以將之設置為NULL, 也可以通過preprocessor 來控制
問題的關鍵是 處理input和echo 之間的關系,
也就是說在捕捉到的信號和播放的信號之間的延遲必須足夠的小,才可以提高效率.
writetosndcard(echo_frame, frame_size)
readfromsndcard(input_frame, frame_size)
speex_echo_cancel(echo_state, input_frame, echo_frame, output_frame, residue)
如果你想要盡可能的減小信號中的回音,那么可以將residue這個參數(shù)設置為噪音參數(shù).
我相信在大多數(shù)情況下,都是因為聲音捕捉和聲音播放之間的同步問題沒有處理好,導致的音頻質(zhì)量下降。
/*
*銷毀和復位
*/
speex_echo_state_destroy(echo_state);
speex_echo_state_reset(echo_state);
不再復述了!
說明:
據(jù)說在Speex的最新的1.2beta版本上,Speex提供了可選擇的,簡化的API,來提高echo執(zhí)行過程中的同步問題。