音频的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;
}

浙公网安备 33010602011771号