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"
posted @ 2023-02-16 00:31  裤子多多  阅读(613)  评论(0编辑  收藏  举报