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

  

posted @ 2025-03-22 15:40  飞翔天空energy  阅读(676)  评论(0)    收藏  举报