WebRTC之VAD算法

VAD(Voice Activity Detection)算法的作用是檢測語音辰晕,在遠(yuǎn)場語音交互場景中挺狰,VAD面臨著兩個(gè)難題:

  1. 可以成功檢測到最低能量的語音(靈敏度)。
  2. 如何在多噪環(huán)境下成功檢測(漏檢率和虛檢率)。
    漏檢反應(yīng)的是原本是語音但是沒有檢測出來归苍,而虛檢率反應(yīng)的是不是語音信號而被檢測成語音信號的概率宠默。相對而言漏檢是不可接受的麸恍,而虛檢可以通過后端的ASR和NLP算法進(jìn)一步過濾,但是虛檢會(huì)帶來系統(tǒng)資源利用率上升光稼,隨之系統(tǒng)的功耗和發(fā)熱會(huì)進(jìn)一步增加或南,而這會(huì)上升為可移動(dòng)和隨聲攜帶設(shè)備的一個(gè)難題。
    本文基于WebRTC的AEC算法艾君,WebRTC的VAD模型采用了高斯模型采够,這一模型應(yīng)用極其廣泛。

高斯分布

高斯分布又稱為正態(tài)分布(Normal distribution/Gaussian distribution)冰垄。
若隨機(jī)變量X服從一個(gè)數(shù)學(xué)期望為μ蹬癌,標(biāo)準(zhǔn)差為σ^2的高斯分布,則:
X~N(μ虹茶,σ^2)
其概率密度函數(shù)為:
f(x)=1/(√2π σ) e(-〖(x-u)〗2/(2σ^2 ))
高斯在webRTC中的使用:
f(x_k |Z逝薪,r_k)=1/√2π e^(-(x_k-u_z )2/(2σ2 ))
x_k是選取的特征向量,webRTC中指x_k是六個(gè)子帶的能量蝴罪,r_k是均值u_z和方差σ的參數(shù)結(jié)合董济,這兩個(gè)參數(shù)決定了高斯分布的概率。Z=0情況是計(jì)算噪聲的概率要门,Z=1是計(jì)算是語音的概率虏肾。

WebRTC算法流程

1.設(shè)置VAD激進(jìn)模式

共四種模式,用數(shù)字0~3來區(qū)分欢搜,激進(jìn)程度與數(shù)值大小正相關(guān)封豪。
0: Normal,1:low Bitrate炒瘟, 2:Aggressive吹埠;3:Very Aggressive
這些激進(jìn)模式是和以下參數(shù)是息息相關(guān)的。

<comman_audio/vad/vad_core.c>
// Mode 0, Quality.
static const int16_t kOverHangMax1Q[3] = { 8, 4, 3 };
static const int16_t kOverHangMax2Q[3] = { 14, 7, 5 };
static const int16_t kLocalThresholdQ[3] = { 24, 21, 24 };
static const int16_t kGlobalThresholdQ[3] = { 57, 48, 57 };
// Mode 1, Low bitrate.
static const int16_t kOverHangMax1LBR[3] = { 8, 4, 3 };
static const int16_t kOverHangMax2LBR[3] = { 14, 7, 5 };
static const int16_t kLocalThresholdLBR[3] = { 37, 32, 37 };
static const int16_t kGlobalThresholdLBR[3] = { 100, 80, 100 };
// Mode 2, Aggressive.
static const int16_t kOverHangMax1AGG[3] = { 6, 3, 2 };
static const int16_t kOverHangMax2AGG[3] = { 9, 5, 3 };
static const int16_t kLocalThresholdAGG[3] = { 82, 78, 82 };
static const int16_t kGlobalThresholdAGG[3] = { 285, 260, 285 };
// Mode 3, Very aggressive.
static const int16_t kOverHangMax1VAG[3] = { 6, 3, 2 };
static const int16_t kOverHangMax2VAG[3] = { 9, 5, 3 };
static const int16_t kLocalThresholdVAG[3] = { 94, 94, 94 };
static const int16_t kGlobalThresholdVAG[3] = { 1100, 1050, 1100 };

它們在計(jì)算高斯模型概率時(shí)用到。

2幀長設(shè)置

A) 共有三種幀長可以用到缘琅,分別是80/10ms粘都,160/20ms,240/30ms胯杭。
B) 其它采樣率的48k驯杜,32k,24k做个,16k會(huì)重采樣到8k來計(jì)算VAD鸽心。
之所以選擇上述三種幀長度,是因?yàn)檎Z音信號是短時(shí)平穩(wěn)信號居暖,其在10ms30ms之間可看成平穩(wěn)信號顽频,高斯馬爾科夫等比較的信號處理方法基于的前提是信號是平穩(wěn)的,在10ms30ms太闺,平穩(wěn)信號處理方法是可以使用的糯景。

3 高斯模型中特征向量選取

在WebRTC的VAD算法中用到了聚類的思想,只有兩個(gè)類省骂,一個(gè)類是語音蟀淮,一個(gè)類是噪聲,對每幀信號都求其是語音和噪聲的概率钞澳,根據(jù)概率進(jìn)行聚類怠惶,當(dāng)然為了避免一幀帶來的誤差也有一個(gè)統(tǒng)計(jì)量判決在算法里,那么問題來了轧粟,選擇什么樣的特征作為高斯分布的輸入呢?這關(guān)系到聚類結(jié)果的準(zhǔn)確性策治,也即VAD性能,毋庸置疑兰吟,既然VAD目的是區(qū)分噪聲和語音通惫,那么噪聲信號和語音信號這兩種信號它們的什么特征相差最大呢?選擇特征相差比較大自然能得到比較好的區(qū)分度。
眾所周知混蔼,信號的處理分類主要有時(shí)域履腋,頻域和空域,從空域上看惭嚣,webRTC的VAD是基于單麥克的遵湖,噪聲和語音沒有空間區(qū)分度的概念,在多麥克風(fēng)場景料按,確實(shí)基于多麥克風(fēng)的VAD算法奄侠,從時(shí)域上看卓箫,而者都是時(shí)變信號载矿,且短時(shí)信號變化率比較小,所以推算來推算去只有頻域的區(qū)分度可能是比較好的。

image.png

汽車噪聲頻譜

image.png

粉紅噪聲頻譜

image.png

白噪聲頻譜

image.png

語音聲譜

從以上四個(gè)圖中闷盔,可以看到從頻譜來看噪聲和語音弯洗,它們的頻譜差異還是比較大,且以一個(gè)個(gè)波峰和波谷的形式呈現(xiàn)逢勾。
WebRTC正式基于這一假設(shè)牡整,將頻譜分成了6個(gè)子帶。它們是:
80Hz250Hz溺拱,250Hz500Hz,500Hz1K,1K2K,2K3K,3K4K逃贝。
可以看到以1KHz為分界,向下500HZ迫摔,250Hz以及170HZ三個(gè)段沐扳,向上也有三個(gè)段,每個(gè)段是1KHz句占,這一頻段涵蓋了語音中絕大部分的信號能量沪摄,且能量越大的子帶的區(qū)分度越細(xì)致。
我國交流電標(biāo)準(zhǔn)是220V~50Hz纱烘,電源50Hz的干擾會(huì)混入麥克風(fēng)采集到的數(shù)據(jù)中且物理震動(dòng)也會(huì)帶來影響杨拐,所以取了80Hz以上的信號。

高通濾波器設(shè)計(jì)

// High pass filtering, with a cut-off frequency at 80 Hz, if the |data_in| is
// sampled at 500 Hz.
//
// - data_in      [i]   : Input audio data sampled at 500 Hz.
// - data_length  [i]   : Length of input and output data.
// - filter_state [i/o] : State of the filter.
// - data_out     [o]   : Output audio data in the frequency interval
//                        80 - 250 Hz.
static void HighPassFilter(const int16_t* data_in, size_t data_length,
                           int16_t* filter_state, int16_t* data_out) {
  size_t i;
  const int16_t* in_ptr = data_in;
  int16_t* out_ptr = data_out;
  int32_t tmp32 = 0;

  // The sum of the absolute values of the impulse response:
  // The zero/pole-filter has a max amplification of a single sample of: 1.4546
  // Impulse response: 0.4047 -0.6179 -0.0266  0.1993  0.1035  -0.0194
  // The all-zero section has a max amplification of a single sample of: 1.6189
  // Impulse response: 0.4047 -0.8094  0.4047  0       0        0
  // The all-pole section has a max amplification of a single sample of: 1.9931
  // Impulse response: 1.0000  0.4734 -0.1189 -0.2187 -0.0627   0.04532

  for (i = 0; i < data_length; i++) {
    // All-zero section (filter coefficients in Q14).
    tmp32 = kHpZeroCoefs[0] * *in_ptr;
    tmp32 += kHpZeroCoefs[1] * filter_state[0];
    tmp32 += kHpZeroCoefs[2] * filter_state[1];
    filter_state[1] = filter_state[0];
    filter_state[0] = *in_ptr++;

    // All-pole section (filter coefficients in Q14).
    tmp32 -= kHpPoleCoefs[1] * filter_state[2];
    tmp32 -= kHpPoleCoefs[2] * filter_state[3];
    filter_state[3] = filter_state[2];
    filter_state[2] = (int16_t) (tmp32 >> 14);
    *out_ptr++ = filter_state[2];
  }
}

新版的定義在 common_audio\vad\vad_filterbank.c

// High pass filtering, with a cut-off frequency at 80 Hz, if the |data_in| is
// sampled at 500 Hz.
//
// - data_in      [i]   : Input audio data sampled at 500 Hz.
// - data_length  [i]   : Length of input and output data.
// - filter_state [i/o] : State of the filter.
// - data_out     [o]   : Output audio data in the frequency interval
//                        80 - 250 Hz.
static void HighPassFilter(const int16_t* data_in, int data_length,
                           int16_t* filter_state, int16_t* data_out) {
  int i;
  const int16_t* in_ptr = data_in;
  int16_t* out_ptr = data_out;
  int32_t tmp32 = 0;


  // The sum of the absolute values of the impulse response:
  // The zero/pole-filter has a max amplification of a single sample of: 1.4546
  // Impulse response: 0.4047 -0.6179 -0.0266  0.1993  0.1035  -0.0194
  // The all-zero section has a max amplification of a single sample of: 1.6189
  // Impulse response: 0.4047 -0.8094  0.4047  0       0        0
  // The all-pole section has a max amplification of a single sample of: 1.9931
  // Impulse response: 1.0000  0.4734 -0.1189 -0.2187 -0.0627   0.04532

  for (i = 0; i < data_length; i++) {
    // All-zero section (filter coefficients in Q14).
    tmp32 = WEBRTC_SPL_MUL_16_16(kHpZeroCoefs[0], *in_ptr);
    tmp32 += WEBRTC_SPL_MUL_16_16(kHpZeroCoefs[1], filter_state[0]);
    tmp32 += WEBRTC_SPL_MUL_16_16(kHpZeroCoefs[2], filter_state[1]);
    filter_state[1] = filter_state[0];
    filter_state[0] = *in_ptr++;

    // All-pole section (filter coefficients in Q14).
    tmp32 -= WEBRTC_SPL_MUL_16_16(kHpPoleCoefs[1], filter_state[2]);
    tmp32 -= WEBRTC_SPL_MUL_16_16(kHpPoleCoefs[2], filter_state[3]);
    filter_state[3] = filter_state[2];
    filter_state[2] = (int16_t) (tmp32 >> 14);
    *out_ptr++ = filter_state[2];
  }
}

對應(yīng)的調(diào)用入口在modules\audio_processing\VoiceDetectionImpl.cc 中擂啥,通過WebRtcVad_Process(common_audio\vad\Webrtc_vad.c int WebRtcVad_Process(VadInst* handle, int fs, const int16_t* audio_frame,
int frame_length)) 最終會(huì)調(diào)用到vad檢測哄陶;

int VoiceDetectionImpl::ProcessCaptureAudio(AudioBuffer* audio) {
  if (!is_component_enabled()) {
    return apm_->kNoError;
  }

  if (using_external_vad_) {
    using_external_vad_ = false;
    return apm_->kNoError;
  }
  assert(audio->samples_per_split_channel() <= 160);

  // TODO(ajm): concatenate data in frame buffer here.

  int vad_ret = WebRtcVad_Process(static_cast<Handle*>(handle(0)),
                                  apm_->proc_split_sample_rate_hz(),
                                  audio->mixed_low_pass_data(),
                                  frame_size_samples_);

github地址:https://github.com/starmier/webrtc-1/blob/master/webrtc/modules/audio_processing/audio_processing_impl.cc

感興趣的小伙伴可以去研讀一下??;

WebRTC在設(shè)計(jì)該濾波器上還是很有技巧的啤它,技巧有二:

  1. 定點(diǎn)數(shù)計(jì)算奕筐,指兩個(gè)方面,一是濾波系數(shù)量化变骡,而是計(jì)算過程的定點(diǎn)化离赫,高斯模型計(jì)算也使用了這一技巧。
  2. 舍入技巧塌碌,減少運(yùn)算量渊胸。
    下面就來看看,這些技巧是如何使用的台妆,首先根據(jù)代碼的注釋可以看出翎猛,
    全零點(diǎn)和全極點(diǎn)脈沖響應(yīng)的實(shí)際上是浮點(diǎn)數(shù),它們脈沖響應(yīng)分別是:
    0.4047 -0.8094 0.4047 0 0 0
    1.0000 0.4734 -0.1189 -0.2187 -0.0627 0.04532
    所以可見應(yīng)該是六階方程接剩,但是超過3階后切厘,零點(diǎn)全零,極點(diǎn)數(shù)值較小懊缺,這時(shí)適當(dāng)增大第三個(gè)數(shù)值疫稿,達(dá)到減少計(jì)算次數(shù)的目的。

量化是按照2的十四次方進(jìn)行定點(diǎn)化。這是因?yàn)樽畈钋闆r下遗座,零極點(diǎn)的放大倍數(shù)不超過兩倍舀凛,所以16位數(shù)可以表示的下來。其零極點(diǎn)繪圖如下:

image.png

對這兩個(gè)圖的解釋就忽略了途蒋,能夠看懂上述代碼和兩張圖的意義猛遍,就可以更改濾波器的特性了,對不要相位信息的号坡,采用IIR比FIR達(dá)到相同的增益平坦度需要的階數(shù)要少懊烤。關(guān)于高通濾波器的設(shè)計(jì)還有疑問的可以留言共同交流。頻響如下:

image.png

WebRtcVad_CalculateFeatures函數(shù)計(jì)算每個(gè)子帶的能量宽堆。能量結(jié)果存放在features數(shù)組里奸晴,然后調(diào)用GmmProbability計(jì)算概率。

int WebRtcVad_CalcVad8khz(VadInstT* inst, const int16_t* speech_frame,
                          size_t frame_length) {
    int16_t feature_vector[kNumChannels], total_power;

    // Get power in the bands
    total_power = WebRtcVad_CalculateFeatures(inst, speech_frame, frame_length,
                                              feature_vector);

    // Make a VAD
    inst->vad = GmmProbability(inst, feature_vector, total_power, frame_length);

    return inst->vad;
}

計(jì)算流程

高斯模型有兩個(gè)參數(shù)H0和H1日麸,它們分表示的是噪聲和語音寄啼,判決測試使用LRT(likelihood ratio test)。分為全局和局部兩種情況代箭。

image.png

a)高斯概率計(jì)算采用的高斯公式如下:

image.png

c)對數(shù)似然比墩划,分為全局和局部,全局是六個(gè)子帶之加權(quán)之和嗡综,而局部是指每一個(gè)子帶則是局部乙帮,所以語音判決會(huì)先判斷子帶,子帶判斷沒有時(shí)會(huì)判斷全局极景,只要有一方過了察净,就算有語音,公式表達(dá)如下:

image.png

后記:

和判決準(zhǔn)則相關(guān)的參數(shù)在vad_core.c文件盼樟,他們是:

// Spectrum Weighting
static const int16_t kSpectrumWeight[kNumChannels] = { 6, 8, 10, 12, 14, 16 };
static const int16_t kNoiseUpdateConst = 655; // Q15
static const int16_t kSpeechUpdateConst = 6554; // Q15
static const int16_t kBackEta = 154; // Q8
// Minimum difference between the two models, Q5
static const int16_t kMinimumDifference[kNumChannels] = {
    544, 544, 576, 576, 576, 576 };
// Upper limit of mean value for speech model, Q7
static const int16_t kMaximumSpeech[kNumChannels] = {
    11392, 11392, 11520, 11520, 11520, 11520 };
// Minimum value for mean value
static const int16_t kMinimumMean[kNumGaussians] = { 640, 768 };
// Upper limit of mean value for noise model, Q7
static const int16_t kMaximumNoise[kNumChannels] = {
    9216, 9088, 8960, 8832, 8704, 8576 };
// Start values for the Gaussian models, Q7
// Weights for the two Gaussians for the six channels (noise)
static const int16_t kNoiseDataWeights[kTableSize] = {
    34, 62, 72, 66, 53, 25, 94, 66, 56, 62, 75, 103 };
// Weights for the two Gaussians for the six channels (speech)
static const int16_t kSpeechDataWeights[kTableSize] = {
    48, 82, 45, 87, 50, 47, 80, 46, 83, 41, 78, 81 };
// Means for the two Gaussians for the six channels (noise)
static const int16_t kNoiseDataMeans[kTableSize] = {
    6738, 4892, 7065, 6715, 6771, 3369, 7646, 3863, 7820, 7266, 5020, 4362 };
// Means for the two Gaussians for the six channels (speech)
static const int16_t kSpeechDataMeans[kTableSize] = {
    8306, 10085, 10078, 11823, 11843, 6309, 9473, 9571, 10879, 7581, 8180, 7483
};
// Stds for the two Gaussians for the six channels (noise)
static const int16_t kNoiseDataStds[kTableSize] = {
    378, 1064, 493, 582, 688, 593, 474, 697, 475, 688, 421, 455 };
// Stds for the two Gaussians for the six channels (speech)
static const int16_t kSpeechDataStds[kTableSize] = {
    555, 505, 567, 524, 585, 1231, 509, 828, 492, 1540, 1079, 850 };

原文鏈接:https://blog.csdn.net/shichaog/article/details/52399354

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氢卡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晨缴,更是在濱河造成了極大的恐慌译秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击碗,死亡現(xiàn)場離奇詭異筑悴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稍途,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門阁吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人械拍,你說我怎么就攤上這事突勇∩涔担” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵与境,是天一觀的道長。 經(jīng)常有香客問我猖吴,道長摔刁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任海蔽,我火速辦了婚禮共屈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘党窜。我一直安慰自己拗引,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布幌衣。 她就那樣靜靜地躺著矾削,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豁护。 梳的紋絲不亂的頭發(fā)上哼凯,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機(jī)與錄音楚里,去河邊找鬼断部。 笑死,一個(gè)胖子當(dāng)著我的面吹牛班缎,可吹牛的內(nèi)容都是我干的蝴光。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼达址,長吁一口氣:“原來是場噩夢啊……” “哼蔑祟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沉唠,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤做瞪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后右冻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體装蓬,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年纱扭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牍帚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乳蛾,死狀恐怖暗赶,靈堂內(nèi)的尸體忽然破棺而出鄙币,到底是詐尸還是另有隱情,我是刑警寧澤蹂随,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布十嘿,位于F島的核電站,受9級特大地震影響岳锁,放射性物質(zhì)發(fā)生泄漏绩衷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一激率、第九天 我趴在偏房一處隱蔽的房頂上張望咳燕。 院中可真熱鬧,春花似錦乒躺、人聲如沸招盲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曹货。三九已至,卻和暖如春讳推,著一層夾襖步出監(jiān)牢的瞬間控乾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工娜遵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜕衡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓设拟,卻偏偏與公主長得像慨仿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子纳胧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容