webrtc中的Noise Suppresso(NS噪音抑制)和Voice Activity Detector(VAD语音检测)
最近做了一张语音采集板卡的小项目,语音采集卡(stm32单片机+wm8960音频芯片+w5500网口芯片),接上麦克风采集到语音后转发到上位机,接收语音数据转发到服务器,并接收服务器其他板子发过来的语音来播放。
东西不复杂,因为去年底疫情刚解除的关系,硬件制板的时间比较紧,工程板打样出来,调出声音来后就交客户了。
这几天客户抱怨说麦克风背噪太大,影响通话质量,又想接手咪,要按下按键才发送语音。
买了个对讲机的手咪,接上实测了下,发现会有很大的背景噪音。拆开一看,PTT通话按键跟喇叭扬声器的负极是共地,有很严重的干扰,并且没按PTT按键时麦克风负极是悬空状态。导致麦克风前级放大模块的AGC自动增益把背景噪音又放大了一级,于是没说话时喇叭就一直嗡嗡嗡的响,实在是莫法用。
板子需要加个IO口,读手咪的PTT按键操作,手咪的扬声器和麦克风也要改硬件,还要找个低噪音的麦克风前级放大模块,这是搞麻烦了。
于是想了个弯路实现解决方案,上位机收到语音数据后,对数据做降噪处理,再利用语音检测算法去检测说话,检测到人声后才向服务器转发语音数据。学习手机拍照,硬件不够,算法来补。
很多年前剥离过webrtc里面的一个爆音平滑算法代码用于自己工程,效果不错,说干就干。
webrtc的官网没FQ,访问不了(google的东西,大家都懂的),在电脑上翻到一份17年download的代码,花了两天,顺利剥离调试成功。实测效果还不错,没人声时不转发语音数据,自动就静音了,背噪也抑制了大半。只是语音检测还是会偶尔失误,附近打电话引起的电流干扰声之类会引起检测判断失败。
翻了下百度,发现学而思搞了个webrtc的国内镜像站,顺利download了份最新版的webrtc。看了下代码,发现VAD做了蛮大的改动,以前的VAD挪到AGC(自动增益)里面,重新做了个AGC2,用的RNNiose神经网络学习特征值算法搞了个新版的VAD。于是又花了一天,把最新版的噪音抑制和语音检测剥离了出来,把两个不同的语音检测模块实现都加上,一起判断。实测效果基本能解决客户问题。
这里开放修改后的源码。因上位机系统环境是Win32 x64,用的VS2019,就没去弄NEON和MIPS的支持了。有这方面的需要,或者是还需要回音消除和爆音抑制的代码,可以站内短信我或者发邮件,我有时间能酌情帮忙搞搞。
代码有点多,1百多个文件,博客园不能传文件资源,遂打包传csdn上了。
下载地址:https://download.csdn.net/download/qq_31143187/87453682
需要5个积分。没积分的同志也可以直接站内短信或者发邮件管我要代码,看到了会回复。
下面附上测试工程抽出来的调用代码样例,没有做能运行的演示程序,仅供参考调用流程。
NoiseSuppressor是噪音抑制,VoiceActivityDetector是语音检测,VoiceActivityDetectorWrapper我改成了VoiceActivityDetector2,文件目录也有一些改动,重新整理了下,不影响使用。
#include "Common.h"
#include "NoiseSuppressor.h"
#include "VoiceActivityDetector2.h"
#include "VoiceActivityDetector.h"
#define LDTTL_WAVE_PACKET_LENGTH (1472)
Udp* m_pUdpAudio = nullptr;
XAudio2Play m_voicePlay;
VUTableWidget* m_pVUTableWidget = nullptr;
StdThread_t m_dataThread;
std::unique_ptr<webrtc::NoiseSuppressor> m_NoiseSuppressor;
std::unique_ptr<webrtc::VoiceActivityDetector2> m_VoiceActivityDetector2;
std::unique_ptr<webrtc::VoiceActivityDetector> m_VoiceActivityDetector;
uint32_t m_10msSampleBytes;
int main(int argc, char* argv[])
{
WAVEFORMATEX wf;
ZeroMemory(&wf, sizeof(WAVEFORMATEX));
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = 2;
wf.nSamplesPerSec = 16000;
wf.wBitsPerSample = 16;
wf.nBlockAlign = wf.nChannels * (wf.wBitsPerSample / 8);
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
wf.cbSize = 0;
// 播放缓存1秒,按UDP_PACKET_LENGTH长度分帧
m_voicePlay.Init();
m_voicePlay.Open(&wf, wf.nAvgBytesPerSec / UDP_PACKET_LENGTH, UDP_PACKET_LENGTH);
// 噪音抑制处理和语音检测只支持10ms数据长度检测
m_10msSampleBytes = wf.nSamplesPerSec / 1000 * 10 * wf.nBlockAlign;
webrtc::NsConfig cfg;
cfg.target_level = webrtc::NsConfig::SuppressionLevel::k12dB; // 设置噪音抑制级别为普通水平
m_NoiseSuppressor = std::make_unique<webrtc::NoiseSuppressor>(cfg, 16000, 2);
m_VoiceActivityDetector2 = std::make_unique<webrtc::VoiceActivityDetector2>(webrtc::GetAvailableCpuFeatures(), 16000);
m_VoiceActivityDetector = std::make_unique<webrtc::VoiceActivityDetector>();
m_pUdpAudio = new Udp();
if (!m_pUdpAudio->Bind("0.0.0.0", dataPort + 1))
{
SAFE_DELETE(m_pUdpAudio);
return 0;
}
m_pUdpAudio->SetBlockMode(FALSE);
m_dataThread.is_run = true;
m_dataThread.p_thread = new std::thread(std::bind(&Thread, this));
while (1)
{
Sleep(10);
}
m_dataThread.is_run = false;
m_dataThread.Close();
SAFE_DELETE(m_pUdpAudio);
return 0;
}
void Thread()
{
uint8_t voiceData[UDP_PACKET_LENGTH];
sockaddr_in from_addr;
Buffer_t captureCache;
Buffer_t voiceCache;
constexpr float kVoiceThreshold_kDefault = 0.02f;
constexpr float kVoiceThreshold_kRnnVad = 0.7f;
int sampleCount = m_10msSampleBytes / sizeof(int16_t) / 2;
webrtc::AudioBuffer audio_buffer(16000, 2, 16000, 2, 16000, 2);
std::vector<int16_t> monoChannel;
monoChannel.resize(sampleCount);
uint64_t voiceDaleyTime = 0;
while (m_dataThread.is_run)
{
if (m_pUdpAudio->Select(SELECT_MODE_READ, 10) != SELECT_STATE_READY)
continue;
while (m_dataThread.is_run)
{
int ret = m_pUdpAudio->Read(data, UDP_PACKET_LENGTH, &from_addr);
if (ret <= 0)
break;
if (ret != LDTTL_WAVE_PACKET_LENGTH)
continue;
bool hasVoice = false;
// 放入缓存,按10ms长度进行拆包处理
captureCache.AppendData(voiceData, UDP_PACKET_LENGTH);
while (captureCache.m_nDataSize >= m_10msSampleBytes)
{
audio_buffer.CopyFrom((int16_t*)captureCache.m_pData, StreamConfig(16000, 2));
// 是否启用降噪处理
if (ui.checkBox_UseNS->checkState() == Qt::Checked)
{
m_NoiseSuppressor->Analyze(audio_buffer);
m_NoiseSuppressor->Process(&audio_buffer);
}
audio_buffer.CopyTo(StreamConfig(16000, 2), (int16_t*)captureCache.m_pData);
// vad语音检测需要单声道数据
webrtc::DownmixInterleavedToMono<int16_t>((int16_t*)captureCache.m_pData,
sampleCount,
2,
monoChannel.data());
// vad语音检测
m_VoiceActivityDetector->ProcessChunk(monoChannel.data(), sampleCount, 16000);
float probabilityDefault = m_VoiceActivityDetector->last_voice_probability();
// rnn(RNNoise) vad语音检测
float probabilityRnnVad = m_VoiceActivityDetector2->Analyze(webrtc::AudioFrameView<const float>(audio_buffer.channels(),
audio_buffer.num_channels(),
audio_buffer.num_frames()));
//printf("Voice Probability: %f %f\n", probabilityDefault, speechProbabilityRnnVad);
// 同时使用两个检测器的阈值进行判决,务必注意两个检测器的判断阈值不一样
if (probabilityDefault >= kVoiceThreshold_kDefault || probabilityRnnVad >= kVoiceThreshold_kRnnVad) {
voiceDaleyTime = std_get_time_now_by_ms();
hasVoice = true;
}
// 放入缓存等待重新组包发送
voiceCache.AppendData(captureCache.m_pData, m_10msSampleBytes);
captureCache.m_pData += m_10msSampleBytes;
captureCache.m_nDataSize -= m_10msSampleBytes;
}
// no voice判决延迟200ms
if (!hasVoice)
{
if (GetTimeInterval(std_get_time_now_by_ms(), hasVoice) < 200) {
hasVoice = true;
}
}
if (!hasVoice) {
// 没有检测到语音,清除数据
voiceCache.ClearData();
continue;
}
// 检测到语音,数据包才予以播放和转发
while (voiceCache.m_nDataSize >= LDTTL_WAVE_PACKET_LENGTH)
{
// 发送语音数据包
SendData(voiceData, LDTTL_WAVE_PACKET_LENGTH, true, &from_addr);
// 播放语音数据
if (ui.checkBox_PlayAudio->checkState() == Qt::Checked)
{
m_voicePlay.Play(voiceData, LDTTL_WAVE_PACKET_LENGTH);
}
// 绘制音频VU音量表
int8_t voiceVolumeLevel = Voice_ComputeLevel((int16_t*)voiceData, LDTTL_WAVE_PACKET_LENGTH / 2);
voiceVolumeLevel = voiceVolumeLevel * 100 / 9; // pos rang = (0 - 100)
m_pVUTableWidget->SetPos(voiceVolumeLevel);
m_VUTableDrawTime = timeGetTime();
voiceCache.m_pData += LDTTL_WAVE_PACKET_LENGTH;
voiceCache.m_nDataSize -= LDTTL_WAVE_PACKET_LENGTH;
}
}
}
}
2023/03/07更新
151行判断延时应该修改为
if (GetTimeInterval(std_get_time_now_by_ms(), voiceDaleyTime) < 200) {
头文件还需要包含
#include "audio_util.h"
#include "audio_buffer.h"