C++で実装。ただし、音声データは符号付き16ビットPCMとし、ひとまず計算にはfloat型を用いて処理の重さも考慮しないものとします。
ノイズ抑制 (Noise Suppression)
- 一般的には、窓関数→FFT→ノイズ推定→減算処理→IFFT→重ね合わせ
- しかしやってみるとなかなかうまくいかない。逆にミュージカルノイズ(ピョロピョロ音)が出る。
- なるべくホワイトノイズを抑えて人の声を通すことだけを考える。
- バンドパスフィルタ(HPF+LPF) + 簡単なノイズゲート くらいでよいのではないか。
- 人の声の主成分は300〜3400 Hz くらいなので 200〜4000 Hz くらいを通すようにする。
→ NoiseSuppressor.h · GitHub : NoiseSuppressorクラスを実装。
発話検出 (Voice Activity Detection)
- 簡易的な方法としては振幅とゼロクロス数を用いる。
- 振幅の平均レベルが一定以上 かつ ゼロクロスが毎秒400~8000 (200〜4000 Hz) なら発話と判定。
→ SimpleVAD.h · GitHub : SimpleVADクラスを実装。
実験
- とりあえずPC上で実験
- サンプリング周波数16000Hz、符号付き16ビットPCM、モノラルのWAVファイルを使用する。
- 10msec (=160サンプル=320バイト) ずつ処理する。
- ノイズ抑制した音声データをファイルに出力する。
- 10msecごとの平均振幅、ゼロクロス数、発話判定をテキストファイルに出力する。
#include <stdio.h> #include <stdint.h> #include <string.h> #include "NoiseSuppressor.h" #include "SimpleVAD.h" int main(void) { uint8_t buf[320]; FILE* fp = fopen("input.wav", "rb"); if (!fp) { perror("fopen 1"); return -1; } FILE* ofp = fopen("output.wav", "wb"); if (!fp) { perror("fopen 2"); return -2; } FILE* log = fopen("log.txt", "w"); if (!fp) { perror("fopen 3"); return -3; } // 先頭 44 バイトをコピー (WAV ヘッダ) size_t n = fread(buf, 1, 44, fp); if (n < 44) { fprintf(stderr, "source file too small\n"); fclose(fp); fclose(ofp); return -4; } fwrite(buf, 1, 44, ofp); NoiseSuppressor ns(16000.0f); SimpleVAD vad(16000, 1); while (1) { size_t n = fread(buf, 1, 320, fp); if (n < 320) { // 最後の端数のデータはゼロパディング memset(buf, 0x00, n); fwrite(buf, 1, n, ofp); printf("end of file!"); break; } else { // ノイズ抑制処理 ns.process((int16_t*)buf, (int16_t*)buf, 160); // 発話検出処理 bool ret = vad.process((int16_t*)buf, 160); fwrite(buf, 1, 320, ofp); fprintf(log, "%d, %d, %d\n", vad.amp, vad.zcr, vad.isSpeech ? 100 : 0); } } fclose(ofp); fclose(fp); fclose(log); return 0; }