音频的VAD检测
最近开始进行一些语音广播、语音识别等相关的工作,逐步接触到了音频的AGC(自动增益算法)、ANS(音频降噪算法)、VAD(语音活动检测算法)等算法应用。
工作之余,将用到的音频相关这些技术进行梳理留存。
-------------------------------------------------------
在音频处理中,语音活动检测(VAD)是一项不可或缺的技术,尤其在语音通信、语音识别、语音增强等领域具有较大的应用价值。在VAD是技术实现上,WebRTC作为一个开源框架,提供了高效且可靠的解决方案。
1.WebRTC-VAD原理介绍:
一、WebRTC VAD的工作原理
WebRTC的VAD检测原理主要是基于人声的频谱范围来进行的。首先,它会把输入的频谱分成六个子带,这些子带的频率范围分别是80Hz-250Hz、250Hz-500Hz、500Hz-1K、1K-2K、2K-3K和3K-4K。然后,对于每个子带,WebRTC会计算其能量。这些能量的计算有助于理解每个子带中的语音信号强度。
接下来,WebRTC会使用高斯模型的概率密度函数对这些子带的能量进行运算,从而得出一个对数似然比函数。这个对数似然比函数可以分为全局和局部两种。全局对数似然比是六个子带能量加权之和,而局部对数似然比则是针对每个子带独立计算的。在进行语音判决时,WebRTC会先判断每个子带,如果子带判断没有语音,那么会进一步判断全局对数似然比。只要全局或任何一个子带的对数似然比超过了设定的阈值,那么就认为有语音存在。
二、WebRTC VAD的设置模式和采样率
WebRTC的VAD提供了四种不同的检测模式,分别是Normal、Low Bitrate、Aggressive和Very Aggressive。这四种模式的激进程度与数值大小相关,用户可以根据实际的使用场景在初始化的时候进行配置。例如,在需要更精确检测的场景中,可以选择Aggressive或Very Aggressive模式;而在对检测精度要求不高,但对性能要求较高的场景中,可以选择Normal或Low Bitrate模式。
此外,WebRTC的VAD还支持三种不同的帧长,分别是80/10ms、160/20ms和240/30ms。这些帧长的选择是基于语音信号的特点,因为语音信号是短时平稳信号,所以在10ms-30ms的范围内可以被看作是平稳信号。这也是许多信号处理方法,如高斯马尔可夫等,所依赖的前提条件。
对于音频信号的采样率,WebRTC支持8kHz、16kHz、32kHz和48kHz的音频。但需要注意的是,无论输入的音频采样率是多少,WebRTC都会首先将其降频到8kHz,然后再进行处理。这是因为WebRTC的VAD算法是基于8kHz的音频设计的,而且8kHz的采样率已经足够捕捉到人声的绝大部分信息。
三、WebRTC VAD的实际应用
WebRTC的VAD技术在许多实时通信应用中都有广泛的应用。例如,在VoIP(网络电话)中,VAD可以帮助系统更准确地判断何时开始和结束传输语音数据,从而节省带宽和计算资源。在语音识别中,VAD可以用于过滤掉非语音部分的音频,提高语音识别的准确率。此外,在音频编码和音频增强等领域,VAD也都发挥着重要的作用。
总结:
WebRTC的VAD技术是一种高效的语音活动检测技术,它通过分析和计算音频信号的频谱能量,以及对数似然比,能够实时地检测出音频信号中是否包含语音。同时,WebRTC的VAD还提供了多种设置模式和采样率选择,以适应不同的使用场景。通过深入理解和应用WebRTC的VAD技术,我们可以更好地实现音频信号的实时处理和分析,为实时通信、语音识别等领域的发展提供有力支持。
2.封装应用:
1、代码引入:
抽取webrtc源码中关于vad处理部分的代码,有人已经分离好了,直接git下载使用:https://gitee.com/paopaouuli/webrtc-vad
上面webrtc-vad原理部分的代码实现主要在vad和signal_pricessing目录中。使用时只需要引用webrtc_vad.h头文件即可。
int WebRtcVad_Init(VadInst* handle):vad检测句柄初始化
int WebRtcVad_set_mode(VadInst* handle, int mode):设置vad检测的激进程度模式,支持0/1/2/3四个模式。分别是Normal、Low Bitrate、Aggressive和Very Aggressive。
int WebRtcVad_Process(VadInst* handle, int fs, const int16_t* audio_frame, size_t frame_length):声音有效性检测。fs为采样率,frame_length为输入帧长度,两者之间有对应关系。以16000HZ采用率为例,单次输入frame_length设为160个int16_t长度,代表以160/16000*1000=10ms为一个声音有效性检测的最小单元。webrtc的vad检测支持10ms/20ms/30ms的最小单元设置。
void WebRtcVad_Free(VadInst* handle):释放vad检测句柄
2、二次封装:
由于webrtc的vad检测算法只是检测一个最小时间片段的音频能量来判断是否有效。语音断句功能需要自己进行业务实现,实现的方法是缓存一定长度的语音片段,对缓存中的每一个片段进行语音有效性检测。当检测到语音片段有效性的概率大于设定的阈值时,认为检测到人声说话的开始,当检测到语音片段无效性的概率大于设定的阈值时,认为检测到人声说话的结束。通过这种方式进行语言的断句,可以检测出一段实时语音流中的有效语音流,然后就可以将断句之后的结果进行语音识别等其它应用。
封装代码如下:
#pragma once #include <stdio.h> #include <string> #include <vector> #include "webrtc_vad.h" struct Frame { uint16_t buff[160]; int activate; }; class RingBuffer { public: RingBuffer(int maxSize); ~RingBuffer(); void enqueue(Frame frame); std::vector<Frame> traverse(); void clear(); private: Frame* buffer; int capacity; int front; int rear; int size; }; class bwVad { public: bwVad(); ~bwVad(); void put_audio(int16_t* audio_frame, int frameLength); private: VadInst* m_vadHandle; RingBuffer* m_ringbuffer; bool m_triggered; int m_buffSize; std::vector<Frame> m_voiceFrames; int m_no; }; #include "bwVad.h" #include <chrono> RingBuffer::RingBuffer(int maxSize) : capacity(maxSize), front(0), rear(0), size(0) { buffer = new Frame[maxSize]; } RingBuffer::~RingBuffer() { delete[] buffer; } void RingBuffer::enqueue(Frame frame) { if (capacity == 0) return; buffer[rear] = frame; rear = (rear + 1) % capacity; if (size < capacity) { size++; } else { front = (front + 1) % capacity; } } std::vector<Frame> RingBuffer::traverse() { std::vector<Frame> result; if (size == 0) return result; int current = front; for (int i = 0; i < size; i++) { result.push_back(buffer[current]); current = (current + 1) % capacity; } return result; } void RingBuffer::clear() { front = 0; rear = 0; size = 0; } bwVad::bwVad() { m_vadHandle = WebRtcVad_Create(); WebRtcVad_Init(m_vadHandle); WebRtcVad_set_mode(m_vadHandle, 3); m_buffSize = 80; m_ringbuffer = new RingBuffer(m_buffSize); m_triggered = false; m_no = 0; } void bwVad::put_audio(int16_t* audio_frame, int frameLength) { Frame frame; memcpy(frame.buff, audio_frame, 160 * sizeof(int16_t)); auto start = std::chrono::high_resolution_clock::now(); frame.activate = WebRtcVad_Process(m_vadHandle, 16000, audio_frame, 160); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double, std::milli> elapsed = end - start; printf("vad cost %f ms\n", elapsed.count()); //printf("%d", frame.activate); m_ringbuffer->enqueue(frame); if (!m_triggered) { int activateCount = 0; std::vector<Frame> buffFrames = m_ringbuffer->traverse(); for (auto f : buffFrames) { if (f.activate) { activateCount++; } } if (activateCount >= m_buffSize * 0.9) { printf("\n !!!!! begin !!!!!\n"); m_triggered = true; for (auto f : buffFrames) { m_voiceFrames.push_back(f); } m_ringbuffer->clear(); } } else { m_voiceFrames.push_back(frame); int unActivateCount = 0; std::vector<Frame> buffFrames = m_ringbuffer->traverse(); for (auto f : buffFrames) { if (!f.activate) { unActivateCount++; } } if (unActivateCount >= m_buffSize * 0.9) { printf("\n !!!!! end !!!!!\n"); m_triggered = false; std::string m_tmpFile = "out/out_" + std::to_string(m_no++) + ".pcm"; FILE* fp = fopen(m_tmpFile.c_str(), "wb"); for (auto f : m_voiceFrames) { fwrite(f.buff, sizeof(int16_t), 160, fp); } m_voiceFrames.clear(); m_ringbuffer->clear(); } } } bwVad::~bwVad() { WebRtcVad_Free(m_vadHandle); m_ringbuffer->clear(); m_ringbuffer = nullptr; } #include <iostream> #include "bwVad.h" int main(){ bwVad vad; int16_t audio_frame[160]; FILE* fp = fopen("123.pcm", "rb"); while (true) { int len = fread(audio_frame, sizeof(int16_t), 160, fp); if (len < 160) { break; } vad.put_audio(audio_frame, 160); } fclose(fp); return 0; }