音視訊開發之旅(六) -----Android整合webrtc降噪和增益模組, ns_core函數簡析

2020-08-08 11:49:35

1.前言

再上一章主要介紹了音訊檔的相關操作,在錄音的過程當中,由於android機型不同的型號,即使採樣率設定成44100k,有一定的外接音或者一些噪音等印象,配音出來的結果並不是很好,肯能存在’滋滋聲’或者一些聲音失真。所以這一章我們將深入操作下音訊的原始數據(即PCM檔案),需要做一些音訊降噪增益的處理,使使用者的配音體驗能做的更好。這篇文章主要先大致介紹下webrtc。在第三章會講下如何在Android裡具體去使用降噪和增益模組,剩餘的篇章會介紹下實現的原理,有興趣的小夥伴們可以慢慢讀下去。

有想直接看程式碼的朋友可以直接走下面 下麪這個鏈接
webrtc實現程式碼直通車

在此,列舉對於音訊的降噪處理相對成熟的三方庫

  • Speex

    Speex 是一套主要針對語音的開源免費,無專利保護的應用集合,Speex專案旨在通過免費提供昂貴專有語音編解碼器的替代方案來降低語音應用程式的進入壁壘。它的主要功能有同一位流中的窄頻(8 kHz),寬頻(16 kHz)和超寬頻(32 kHz)壓縮 ,強度立體聲編碼,丟包隱藏,可變位元率操作(VBR),語音活動檢測(VAD),不連續傳輸(DTX),定點埠,回聲消除器,噪聲抑制。開發Speex的Xiph.org基金會已經宣佈廢棄Speex,建議改用Opus取代。

  • WebRtc

    它支援視訊,語音,和同齡人之間發送通用數據,允許開發者建立強大的語音和視訊通訊解決方案。該技術適用於所有現代瀏覽器,以及對所有主要平臺的本地客戶。背後的WebRTC的技術實現爲一個開放的網路標準,可作爲所有主流瀏覽器常規的JavaScript API。對於本地用戶端,如Android和iOS應用中,庫可提供相同的功能。webRtc庫裡有一個音訊降噪的模組,即 audio_process,所處位置在 /webrtc/modules/audio_processing

  • rnnoise

    根據噪聲的不同大部分處理是針對平穩噪聲以及瞬時噪聲來做。RNNoise 降噪演算法則是根據純語音以及噪聲通過 GRU 訓練來做。包含特徵點提取、預料等核心部分。

傳統降噪演算法大部分是估計噪聲 + 維納濾波,噪聲估計的準確性是整個演算法效果的核心。

RNNoise 的優點主要是一個演算法通過訓練可以解決所有噪聲場景以及可以優化傳統噪聲估計的時延和收斂問題。
RNNoise 的缺點是深度學習演算法落地問題。因爲相對大部分傳統演算法,RNNoise 訓練要得到一個很好的效果,由於特徵點個數、隱藏單元的個數以及神經網路層數的增加,導致模型增大,執行效率。

閱讀了網上一些資料,對比過來,webrtc的降噪模組比較適合我這邊的業務使用,所以這篇文章主要講webrtc關鍵函數,需要呼叫webrct降噪模組的呼叫和降噪過後的音訊數據對比。之後補上webrtc增益模組(主要函數是 analog_agc.c)。下面 下麪的程式碼裡已經整合了相應的降噪模組

webrtc實現程式碼直通車

2. webrtc介紹

聲音處理針對音訊數據進行處理,包括回聲消除(AEC)、AECM(AEC Mobile)、自動增益(AGC)、降噪(NS)、靜音檢測(VAD)處理等功能,用來提升聲音品質。
webrtc相關程式碼我這邊使用的聲網提供的映象代理,跟着文件走一遍就行了
聲網提供的webrtc原始碼下載和編譯
學而思提供的webrtc原始碼下載和編譯
google webrtc source

整個過程是比較久的, 我這邊下載了一個多小時
在这里插入图片描述

2.1 webrtc 簡介

webrtc 官方架構圖介紹

藉助WebRTC,您可以在基於開放標準的應用程式中新增實時通訊功能。它支援在同級之間發送視訊,語音和通用數據,從而使開發人員能夠構建功能強大的語音和視訊通訊解決方案。該技術可在所有現代瀏覽器以及所有主要平臺的本機用戶端上使用。說白了,就是具備極強的音視訊功能。

2.2 webrtc 框架

這是webrtc的整體程式碼目錄

整個webrtc程式碼非常大,因爲它也涵蓋了一些成熟的第三方庫,使他的功能更加完善。包括ffmpeg、sqlite等。

接下來看一下官方給出的框架圖

如果我們的專案暫時不做p2p連線等功能,可以先重點看下 VoiceEngine 和 VideoEngine

VoiceEngine

  • iSAC / iLBC / Opus
    iSAC:用於VoIP和流音訊的寬頻和超寬頻音訊編解碼器。iSAC使用16 kHz或32 kHz採樣頻率,具有12到52 kbps的自適應可變位元率。

iLBC:用於VoIP和流音訊的窄頻語音編解碼器。使用8 kHz採樣頻率,其20ms幀的位元率爲15.2 kbps,30ms幀的位元率爲13.33 kbps。由IETF RFC 3951和3952定義。

Opus:支援從6 kbit / s到510 kbit / s的恆定和可變位元率編碼,從2.5 ms到60 ms的幀大小以及從8 kHz(帶4 kHz頻寬)到48 kHz(帶20 kHz頻寬)的各種採樣率,可以複製人類聽覺系統的整個聽力範圍)。由IETF RFC 6176定義。NetEQ for Voice

一種動態抖動緩衝區和錯誤隱藏演算法,用於隱藏網路抖動和數據包丟失的負面影響。在保持最高語音品質的同時,儘可能降低延遲。

  • 回聲消除器(AEC)
    聲學回聲消除器是基於軟體的信號處理元件,可實時消除因播放的聲音進入有源麥克風而產生的聲學回聲。

  • 降噪(NR)
    降噪元件是一個基於軟體的信號處理元件,可消除通常與VoIP相關的某些型別的背景噪聲。(嘶嘶聲,風扇噪音等)

VideoEngine

VideoEngine是一個框架視訊媒體鏈,用於從攝像機到網路以及從網路到螢幕的視訊。

  • VP8
    WebM專案的視訊編解碼器。它設計用於低延遲,因此非常適合RTC。

  • 視訊抖動緩衝器
    視訊動態抖動緩衝器。幫助掩蓋抖動和丟包對整體視訊品質的影響。

  • 影象增強
    例如,從網路攝像頭捕獲的影象中消除視訊噪聲。

從整體的框架來看,webrtc程式碼容量有非常大,但是各個引擎之間是相互獨立的。所以我們可以單獨把音訊模組相應的功能抽取出來,在本文中抽出了ns(降噪)和 agc (增益)

3 webrtc 降噪模組和音訊增益模組使用

3.1 降噪模組使用流程

是在不想看的話,可以直接參考程式碼
webrtc實現程式碼直通車

看下下面 下麪的時序圖

在这里插入图片描述

//ns初始化
//fs == 8000 || fs == 16000 || fs == 32000 || fs == 48000
 nsxId = WebRtcNsUtils.WebRtcNsx_Create();
int nsxInit = WebRtcNsUtils.WebRtcNsx_Init(nsxId, 8000); //0代表成功
int nexSetPolicy = WebRtcNsUtils.nsxSetPolicy(nsxId, 2);
WebRtcNsUtils.WebRtcNsx_Process(nsxId, inputData, num_bands, nsProcessData);

呼叫的流程不難,沒有特別複雜的操作。先create,init,(setConfig可要可不要),process,最後一個free釋放資源

  • 這邊要注意一點 不同版本的webrtc支援的採樣率是不同的,以及自身音訊檔是否符合降噪的採樣率,否則容易出錯。
int WebRtcNs_InitCore(NoiseSuppressionC *self, uint32_t fs) {
    int i;
    // Check for valid pointer.
    if (self == NULL) {
        return -1;
    }

    // Initialization of struct.
    if (fs == 8000 || fs == 16000 || fs == 32000 || fs == 48000) {
        self->fs = fs;
    } else {
        return -1;
    }
    …………
    }

這裏看一下Process程式碼,最終呼叫的是ns_core.c 的WebRtcNs_ProcessCore,在下一章我們會介紹ns_core.c檔案主要邏輯

void WebRtcNs_Process(NsHandle *NS_inst,
                      const float *const *spframe,
                      size_t num_bands,
                      float *const *outframe) {
    WebRtcNs_ProcessCore((NoiseSuppressionC *) NS_inst, spframe, num_bands,
                         outframe);
}

3.1 webrtc 使用結果

一下是用Audacity來分析音訊數據,這邊只測試了 8000採樣率的 pcm檔案

在这里插入图片描述

通過降噪後,原本的音訊一些小雜音會被抹去。

4 webrtc 音訊降噪函數解析

webrtc 降噪的關鍵函數在 ns_core.c,噪聲頻譜可以使用如語音/噪聲似然函數進行估計。將接收到的每幀信號和頻率分量分類爲噪聲或語音。這邊會先從原理和程式碼角度來解析。

4.1 演算法相關原理

y(t) = x(t)+n(t) --(1)

上式中x和n分別表示語音和噪聲,而y表示麥克風採集到的信號。 從上圖可以看出語音和噪聲是加性且不相關的關係,所以這裏的中心思想就變成了從Y中估計噪聲D,然後抑制n(t)以得到語音。所以對噪聲的估計準確性是至關重要的,我們知道聲音是正態分佈的圖,有如下演算法可以參考,

  1. 基於VAD檢測的噪聲估計,VAD對Y進行檢測,如果檢測沒有語音,則認爲噪聲,這是對噪聲的一種估計方法。

2.基於全域性幅度譜最小原理,該估計認爲幅度譜最小的情況必然對應沒有語音的時候。

3.還有基於矩陣奇異值分解原理估計噪聲的

webRTC沒有採用上述的方法,而是對似然比(VAD檢測時就用了該方法)函數進行改進,將多個語音/噪聲分類特徵合併到一個模型中形成一個多特徵綜合概率密度函數,對輸入的每幀頻譜進行分析。其可以有效抑制風扇/辦公裝置等噪聲。

其抑制過程如下:

對接收到的每一幀帶噪語音信號,以對該幀的初始噪聲估計爲前提,定義語音概率函數,測量每一幀帶噪信號的分類特徵,使用測量出來的分類特徵,計算每一幀基於多特徵的語音概率,在對計算出的語音概率進行動態因子(信號分類特徵和閾值參數)加權,根據計算出的每幀基於特徵的語音概率,修改多幀中每一幀的語音概率函數,以及使用修改後每幀語音概率函數,更新每幀中的初始噪聲(連續多幀中每一幀的分位數噪聲)估計。

4.2 set_feature_extraction_parameters

設定了特徵提取使用到的參數,當前WebRTC噪聲抑制演算法使用了LRT特徵/頻譜平坦度和頻譜差異度三個指標,沒有使用頻譜熵和頻譜方差這兩個特徵。

這裏簡單介紹下概念

  • LRT特徵

似然比檢驗是基於 最大似然估計(ML),進一步引入某個假設,來證明新引入假設是否成立的方法。首先,假設原來的條件已經令似然函數取得最大值,新引入的假設不會超過這個最大似然函數值,但如果約束條件有效,有約束的最大值應當接近無約束的最大值,兩者的比值接近於1。否則遠遠小於1。

頻譜平坦度也叫做維納熵,即各個頻率分量的幾何平均數與算術平均數的比值, 網上有定義,下面 下麪直接出公式了。

在这里插入图片描述

直觀的說如果每個分量都相等,比值爲1;分量值相當,比值接近於1,這時認爲是白噪聲;分量差異大的話,比值接近0,一般認爲有語音產生。
在下文會更詳細的說明

  • 頻譜偏差

這個是將輸入的頻譜和估算的頻譜模板進行對比來評價語音和噪聲概率的方法。是一種譜估計方法

4.3 WebRtcNs_InitCore

self:應該初始化的範例
fs:採樣頻率 這邊注意下(不同版本對採樣頻率支援不一樣,越新的支援的採樣頻率越多)

// Initialize state.
int WebRtcNs_InitCore(NoiseSuppressionC* self, uint32_t fs) {
  int i;
  // Check for valid pointer.
  if (self == NULL) {
    return -1;
  }

  // Initialization of struct.
  if (fs == 8000 || fs == 16000 || fs == 32000 || fs == 48000) {
    self->fs = fs;
  } else {
    return -1;
  }
  self->windShift = 0;
  // We only support 10ms frames.
  if (fs == 8000) {
  //語音數據的長度,8k/10ms的數據量是80
    self->blockLen = 80; 
//分析長度,由於是在頻域分析,將長度像上取2的冪次,最小的值是128,實際上是fft的長度
    self->anaLen = 128;
    //窗函數,採用混合漢寧平頂窗函數
    self->window = kBlocks80w128;
  } else {
    self->blockLen = 160;
    self->anaLen = 256;
    self->window = kBlocks160w256;
  }
  self->magnLen = self->anaLen / 2 + 1;  // Number of frequency bins.

  // Initialize FFT work arrays.
  self->ip[0] = 0;  // Setting this triggers initialization.
  memset(self->dataBuf, 0, sizeof(float) * ANAL_BLOCKL_MAX);
  WebRtc_rdft(self->anaLen, 1, self->dataBuf, self->ip, self->wfft);

//是滑動分析窗,針對80點128的fft而言,每一次會保留前一幀的128-80=48個點的數據,而不是對80點簡單填充0變成128點做fft。
//但這會帶來合成上的問題,通常採用加窗以防止重疊帶來的突變。可以使用做fft變換一樣的窗函數。但這要求窗函數保冪對映,即重疊
//區部分視窗的平方和必須爲1.
  memset(self->analyzeBuf, 0, sizeof(float) * ANAL_BLOCKL_MAX);
//dataBuf儲存的是原始時域信號
  memset(self->dataBuf, 0, sizeof(float) * ANAL_BLOCKL_MAX);
//syntBuf是譜減法,減去噪聲後變換到時域的信號
  memset(self->syntBuf, 0, sizeof(float) * ANAL_BLOCKL_MAX);

  // For HB processing.這是高頻部分,最多有兩個band
  memset(self->dataBufHB,
         0,
         sizeof(float) * NUM_HIGH_BANDS_MAX * ANAL_BLOCKL_MAX);

  // For quantile noise estimation.
  memset(self->quantile, 0, sizeof(float) * HALF_ANAL_BLOCKL);
//3影格同步化估計,lquantile是對數分位數。density是概率密度,計算分位數用到概率密度的。
  for (i = 0; i < SIMULT * HALF_ANAL_BLOCKL; i++) {
    self->lquantile[i] = 8.f;
    self->density[i] = 0.3f;
  }

  for (i = 0; i < SIMULT; i++) {
  // counter是一個權值,代表的每一幀對分位數估計而言其所佔的比重。
    self->counter[i] =
        (int)floor((float)(END_STARTUP_LONG * (i + 1)) / (float)SIMULT);
  }

  self->updates = 0;

  // 維納濾波器初始化
  for (i = 0; i < HALF_ANAL_BLOCKL; i++) {
    self->smooth[i] = 1.f;
  }

  // 設定抑制噪聲的激進度
  self->aggrMode = 0;

  // Initialize variables for new method.
  self->priorSpeechProb = 0.5f;  // Prior prob for speech/noise.
  // Previous analyze mag spectrum.
  memset(self->magnPrevAnalyze, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // Previous process mag spectrum.
  memset(self->magnPrevProcess, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // Current noise-spectrum.
  memset(self->noise, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // Previous noise-spectrum.
  memset(self->noisePrev, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // Conservative noise spectrum estimate.
  memset(self->magnAvgPause, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // For estimation of HB in second pass.
  memset(self->speechProb, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  // Initial average magnitude spectrum.
  memset(self->initMagnEst, 0, sizeof(float) * HALF_ANAL_BLOCKL);
  for (i = 0; i < HALF_ANAL_BLOCKL; i++) {
    // Smooth LR (same as threshold).
    self->logLrtTimeAvg[i] = LRT_FEATURE_THR;
  }

//特徵量,計算噪聲用到。光譜平整度 和功能量
  // Feature quantities.
  // Spectral flatness (start on threshold).
  self->featureData[0] = SF_FEATURE_THR;
  self->featureData[1] = 0.f;  // Spectral entropy: not used in this version.
  self->featureData[2] = 0.f;  // Spectral variance: not used in this version.
  // Average LRT factor (start on threshold).
  self->featureData[3] = LRT_FEATURE_THR;
  // Spectral template diff (start on threshold).
  self->featureData[4] = SF_FEATURE_THR;
  self->featureData[5] = 0.f;  // Normalization for spectral difference.
  // Window time-average of input magnitude spectrum.
  self->featureData[6] = 0.f;

  memset(self->parametricNoise, 0, sizeof(float) * HALF_ANAL_BLOCKL);

  // Histogram quantities: used to estimate/update thresholds for features.
  memset(self->histLrt, 0, sizeof(int) * HIST_PAR_EST);
  memset(self->histSpecFlat, 0, sizeof(int) * HIST_PAR_EST);
  memset(self->histSpecDiff, 0, sizeof(int) * HIST_PAR_EST);


  self->blockInd = -1;  // Frame counter.
  // Default threshold for LRT feature.
  self->priorModelPars[0] = LRT_FEATURE_THR;
  // Threshold for spectral flatness: determined on-line.
  self->priorModelPars[1] = 0.5f;
  // sgn_map par for spectral measure: 1 for flatness measure.
  self->priorModelPars[2] = 1.f;
  // Threshold for template-difference feature: determined on-line.
  self->priorModelPars[3] = 0.5f;
  // Default weighting parameter for LRT feature.
  self->priorModelPars[4] = 1.f;
  // Default weighting parameter for spectral flatness feature.
  self->priorModelPars[5] = 0.f;
  // Default weighting parameter for spectral difference feature.
  self->priorModelPars[6] = 0.f;

  // Update flag for parameters:
  // 0 no update, 1 = update once, 2 = update every window.
  self->modelUpdatePars[0] = 2;
  self->modelUpdatePars[1] = 500;  // Window for update.
  // Counter for update of conservative noise spectrum.
  self->modelUpdatePars[2] = 0;
  // Counter if the feature thresholds are updated during the sequence.
  self->modelUpdatePars[3] = self->modelUpdatePars[1];

//白噪聲和粉紅噪聲
  self->signalEnergy = 0.0;
  self->sumMagn = 0.0;
  self->whiteNoiseLevel = 0.0;
  self->pinkNoiseNumerator = 0.0;
  self->pinkNoiseExp = 0.0;

  set_feature_extraction_parameters(self);

  // Default mode.
  WebRtcNs_set_policy_core(self, 0);

  self->initFlag = 1;
  return 0;
}

4.4 ComputeSpectralFlatness

頻譜度計算時N表示STFT後頻率點數,B代表頻率帶的數量,K是頻點指數,j是頻帶指數。每個頻帶包括大量的頻率點。就128個頻率點可分成4個頻帶(低帶,中低頻帶,中高頻帶,高頻),每個頻帶32個頻點。對於噪聲Flatness偏大且爲常數,而對於語音,計算出的數量則偏下且爲變數。這四個頻段對於語音信號差異是比較大的,對於噪聲是比較小的,根據上面的公式,如果接近於1,則是噪聲,(噪聲的幅度譜趨於平坦),二對於語音,上面的N次根是對乘積結果進行N次縮小,相比於分母部分,縮小的數量級是倍數的,所以語音的平坦度較小,是趨近於0的。


// Compute spectral flatness on input spectrum.
// |magnIn| is the magnitude spectrum.
// Spectral flatness is returned in self->featureData[0].
static void ComputeSpectralFlatness(NoiseSuppressionC* self,
                                    const float* magnIn) {
  size_t i;
  size_t shiftLP = 1;  // Option to remove first bin(s) from spectral measures.
  float avgSpectralFlatnessNum, avgSpectralFlatnessDen, spectralTmp;
 
  // Compute spectral measures.
  // For flatness.
  avgSpectralFlatnessNum = 0.0;
  avgSpectralFlatnessDen = self->sumMagn;
  for (i = 0; i < shiftLP; i++) { 
//跳過第一個頻點,即直流頻點Den是denominator(分母)的縮寫,avgSpectralFlatnessDen是上述公式分母計算用到的
    avgSpectralFlatnessDen -= magnIn[i];
  }
  // Compute log of ratio of the geometric to arithmetic mean: check for log(0) case.
  // 計算分子部分,numerator(分子),對log(0)是無窮小的值,所以計算時對這一情況特殊處理。
  for (i = shiftLP; i < self->magnLen; i++) {
    if (magnIn[i] > 0.0) {
      avgSpectralFlatnessNum += (float)log(magnIn[i]);
} else {
//TVAG是time-average的縮寫,對於能量出現異常的處理。利用前一次平坦度直接取平均返回。這裏平滑因子是0.3.
      self->featureData[0] -= SPECT_FL_TAVG * self->featureData[0];
      return;
    }
  }
  // Normalize.
  avgSpectralFlatnessDen = avgSpectralFlatnessDen / self->magnLen;
  avgSpectralFlatnessNum = avgSpectralFlatnessNum / self->magnLen;
 
  // Ratio and inverse log: check for case of log(0).
  spectralTmp = (float)exp(avgSpectralFlatnessNum) / avgSpectralFlatnessDen;
 
  // Time-avg update of spectral flatness feature.
  self->featureData[0] += SPECT_FL_TAVG * (spectralTmp - self->featureData[0]);
  // Done with flatness feature.
}

4.5 ComputeSpectralDifference

有關噪聲頻譜的另一個假設是,噪聲頻譜比語音訊譜更穩定。因此,可假設噪聲頻譜的整體形狀在任何給定階段都傾向於保持相同。這第三個特徵用於測量輸入頻譜與噪聲頻譜形狀的偏差。

計算公式如下

  // avgDiffNormMagn = var(magnIn) - cov(magnIn, magnAvgPause)^2 / var(magnAvgPause)

4.6 ComputeSnr

根據分位數噪聲估計計算前後訊雜比。
後驗訊雜比指觀測到的能量與噪聲功率相關的輸入功率相比的瞬態SNR:

根據分位數噪聲估計計算前後訊雜比。
輸入:|magn |是信號幅度譜的估計。
|noise| 噪聲譜估計的幅度。
輸出 得到前後訊雜比

static void ComputeSnr(const NoiseSuppressionC* self,
                       const float* magn,
                       const float* noise,
                       float* snrLocPrior,
                       float* snrLocPost) {
  size_t i;

  for (i = 0; i < self->magnLen; i++) {
    // Previous post SNR.
    // Previous estimate: based on previous frame with gain filter.
    float previousEstimateStsa = self->magnPrevAnalyze[i] /
        (self->noisePrev[i] + 0.0001f) * self->smooth[i];
    // Post SNR.
    snrLocPost[i] = 0.f;
    if (magn[i] > noise[i]) {
      snrLocPost[i] = magn[i] / (noise[i] + 0.0001f) - 1.f;
    }
    // DD estimate is sum of two terms: current estimate and previous estimate.
    // Directed decision update of snrPrior.
    snrLocPrior[i] =
        DD_PR_SNR * previousEstimateStsa + (1.f - DD_PR_SNR) * snrLocPost[i];
  }  // End of loop over frequencies.
}

4.7 SpeechNoiseProb

這個函數主要計算語音和噪聲概率,並將這兩者概率返回,這個函數主要是對計算公式進行推導,這裏就不做細講了

4.8 UpdateNoiseEstimate

更新噪音估算。

    probSpeech = self->speechProb[i];
    probNonSpeech = 1.f - probSpeech;
    // Temporary noise update:
    // 如果更新值小於之前的值,則將其用於語音幀。
    noiseUpdateTmp = gammaNoiseTmp * self->noisePrev[i] +
                     (1.f - gammaNoiseTmp) * (probNonSpeech * magn[i] +
                                              probSpeech * self->noisePrev[i]);

上述噪聲估計模型會對噪聲可能性較大(即語音可能性較小)的每個幀和頻率槽的噪聲進行更新。對於噪聲可能性不大的幀和頻率槽,則將對信號中上一個幀的估計作爲噪聲估計。完成噪聲估計更新後,噪聲估計和過濾流程採用維納增益濾波器以減少或消除來自輸入幀的估計噪聲量。

4.9 WebRtcNs_ProcessCore

ProcessCore是降噪的核心函數,主要的降噪程式。這邊就不貼住完整程式碼了,可以跟着流程去看程式碼

void WebRtcNs_ProcessCore(NoiseSuppressionC *self,
                          const float *const *speechFrame,
                          size_t num_bands,
                          float *const *outFrame) 
                          

1、檢查啓動是否完成和校驗相關參數,更新L波段的分析緩衝區,更新分析緩衝區的H波段(陣列型別)。

2、 計算輸入帶噪語音數據幀的能量值。(看程式碼定義的能量值)

在這種情況下, 希望避免更新統計資訊:只有0時更新特性統計資訊將導致閾值向零信號的情況移動。這反過來有這樣的效果,一旦信號被「開啓」(非零值),一切將被視爲語音,沒有噪聲抑制效果。根據不活躍信號的持續時間,系統需要相當長的時間來了解什麼是噪音,什麼是語音。

Windowing(self->window, self->analyzeBuf, self->anaLen, winData);
energy = Energy(winData, self->anaLen);

static float Energy(const float *buffer, size_t length) {
    size_t i;
    float energy = 0.f;

    for (i = 0; i < length; ++i) {
        energy += buffer[i] * buffer[i];
    }

    return energy;
}

  if (energy == 0.0) {
  
        self->signalEnergy = 0;
        return;
    }

3、FFT傅裡葉變換

4、計算維納濾波增益,通過直接判決法計算先驗訊雜比。

   // DD estimate is sum of two terms: current estimate and previous estimate.
        // Directed decision update of |snrPrior|.
        snrPrior = DD_PR_SNR * previousEstimateStsa +
                   (1.f - DD_PR_SNR) * currentEstimateStsa;
        // Gain filter.
        theFilter[i] = snrPrior / (self->overdrive + snrPrior);

5、對維納增益值根據使用者設定的降噪等級,進行下溢與上溢處理。

6、進行維納濾波,並將增益後的頻域數據(頻率降噪)通過IFFT轉爲時域數據。

7、對降噪等級進行判斷,如果等級爲0 ,則維納濾波輸出則爲最後的降噪結果。否則,繼續處理。

8、計算維納濾波後的幀數據能量,並計算與步驟2中的能量比值, gain = (float)sqrt(energy2/ (energy1 + 1.f));

9、當gain值大於0.5,計算因子1,factor1= 1.f + 1.3f * (gain - B_LIM);並判斷gain *factor1是否大於1。如果大於1,因子1則爲 factor1 = 1.f / gain;

10、當gain值小於0.5,判斷gain是否小於增益下限值(使用者設定),如果小於,則 gain = self->denoiseBound;計算因子2,factor2= 1.f - 0.3f * (B_LIM - gain);

11、結合先驗語音概率計算最終的增益因子。

    factor = self->priorSpeechProb* factor1  +  (1.f - self->priorSpeechProb) * factor2;

12、在時域,將維納濾波處理後的數據與facror相乘,得到最後的降噪結果。

13、如果對信號進行了分子帶處理,則接下來需要對高頻部分的自帶進行處理。

  if (flagHB == 1) {
        // Average speech prob from low band.
        // 平均值(4->8kHz)的頻率頻譜
        avgProbSpeechHB = 0.0;
        for (i = self->magnLen - deltaBweHB - 1; i < self->magnLen - 1; i++) {
            avgProbSpeechHB += self->speechProb[i];
        }
        //1.計算高頻部分的先驗語音概率,這是是用低頻子帶求平均得到  
        avgProbSpeechHB = avgProbSpeechHB / ((float) deltaBweHB);
       // 如果語音被處理過,例如AEC,那麼它不應該被認爲是用於高頻帶抑制目的的語音。
        sumMagnAnalyze = 0;
        sumMagnProcess = 0;
        for (i = 0; i < self->magnLen; ++i) {
            sumMagnAnalyze += self->magnPrevAnalyze[i];
            sumMagnProcess += self->magnPrevProcess[i];
        }
        RTC_DCHECK_GT(sumMagnAnalyze, 0);
          // 2.將上一步計算的結果進行縮放,乘以上一幀處理後的輸出訊雜比
        avgProbSpeechHB *= sumMagnProcess / sumMagnAnalyze;
        // Average filter gain from low band. //3.計算得到的各頻點的維納濾波增益的平均值。 avgFilterGainHB
        avgFilterGainHB = 0.0;
        for (i = self->magnLen - deltaGainHB - 1; i < self->magnLen - 1; i++) {
            avgFilterGainHB += self->smooth[i];
        }
        avgFilterGainHB = avgFilterGainHB / ((float) (deltaGainHB));
        avgProbSpeechHBTmp = 2.f * avgProbSpeechHB - 1.f;
        //4.對先驗語音概率進行對映,得到初步的增益值。gainModHB
        gainModHB = 0.5f * (1.f + (float) tanh(gainMapParHB * avgProbSpeechHBTmp));
        // Combine gain with low band gain. //5.將上兩步(gainModHB 、avgFilterGainHB)中得到的結果進行平滑,得到高頻部分最終的增益因子。
        gainTimeDomainHB = 0.5f * gainModHB + 0.5f * avgFilterGainHB;
        if (avgProbSpeechHB >= 0.5f) {
            gainTimeDomainHB = 0.25f * gainModHB + 0.75f * avgFilterGainHB;
        }
        gainTimeDomainHB = gainTimeDomainHB * decayBweHB;
        // Make sure gain is within flooring range.
        // Flooring bottom.
        if (gainTimeDomainHB < self->denoiseBound) {
            gainTimeDomainHB = self->denoiseBound;
        }
        // Flooring top.
        if (gainTimeDomainHB > 1.f) {
            gainTimeDomainHB = 1.f;
        }
        // Apply gain.
        for (i = 0; i < num_high_bands; ++i) {
            for (j = 0; j < self->blockLen; j++) {
            //6.返回結果
                outFrameHB[i][j] =
                        WEBRTC_SPL_SAT(WEBRTC_SPL_WORD16_MAX,
                                       gainTimeDomainHB * self->dataBufHB[i][j],
                                       WEBRTC_SPL_WORD16_MIN);
            }
        }
    } 

小結

webrtc的功能還是很強大的,使用起來很方便,但是整體實現還是比較複雜的,需要我們繼續去挖掘理解。AGC的部分原始碼可以直接看程式碼裡的,也可以去閱讀網上相關的解析。

參考

《實時語音處理實踐指南》

webrtc中的噪聲抑制之四:語音噪聲概率計算

音訊降噪在 58 直播中的研究與實現

webrtc官方文件