1. 基于VAD的静音段检测与丢弃

基于VAD的静音段检测与丢弃 学习卡片

任务说明

在嵌入式语音前端处理中,静音段占据通话时长比例较高,若对静音帧继续做回声消除或降噪,会浪费算力并可能引入噪声。本案例实现一个轻量级VAD(语音活动检测)模块,基于短时能量与过零率双门限判决,对16kHz单声道PCM输入流逐帧检测,并将静音帧丢弃,仅输出语音帧。

算法设计

每帧长度20ms(320个采样点)。计算帧内短时能量E与过零率ZCR,判决规则如下:

  • 若E > E_high 或 (E > E_low 且 ZCR < ZCR_max),判为语音帧;
  • 否则判为静音帧并丢弃。

其中E_high=5000,E_low=1000,ZCR_max=0.3(归一化过零率)。该门限适用于16bit PCM、幅值范围[-32768,32767]的近距离语音场景。

代码实现

// vad_drop.c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>

#define FRAME_SIZE 320      // 20ms @16kHz
#define E_HIGH 5000.0f
#define E_LOW  1000.0f
#define ZCR_MAX 0.3f

// 计算帧能量(均方和)
static float frame_energy(const int16_t *pcm, int n) {
    float sum = 0.0f;
    for (int i = 0; i < n; i++) {
        sum += (float)pcm[i] * pcm[i];
    }
    return sum / n;
}

// 计算归一化过零率(每样本平均过零次数)
static float frame_zcr(const int16_t *pcm, int n) {
    int crossings = 0;
    for (int i = 1; i < n; i++) {
        if ((pcm[i-1] >= 0 && pcm[i] < 0) || (pcm[i-1] < 0 && pcm[i] >= 0))
            crossings++;
    }
    return (float)crossings / n;
}

// VAD判决:返回1=语音帧,0=静音帧
static int vad_decision(const int16_t *frame) {
    float energy = frame_energy(frame, FRAME_SIZE);
    float zcr = frame_zcr(frame, FRAME_SIZE);
    if (energy > E_HIGH) return 1;
    if (energy > E_LOW && zcr < ZCR_MAX) return 1;
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s input.pcm output.pcm\n", argv[0]);
        return 1;
    }

    FILE *fin = fopen(argv[1], "rb");
    FILE *fout = fopen(argv[2], "wb");
    if (!fin || !fout) {
        fprintf(stderr, "File open failed\n");
        return 1;
    }

    int16_t frame[FRAME_SIZE];
    size_t read_cnt;
    int total_frames = 0, speech_frames = 0;

    while ((read_cnt = fread(frame, sizeof(int16_t), FRAME_SIZE, fin)) == FRAME_SIZE) {
        total_frames++;
        if (vad_decision(frame)) {
            fwrite(frame, sizeof(int16_t), FRAME_SIZE, fout);
            speech_frames++;
        }
    }

    printf("Total frames: %d, Speech frames: %d, Dropped: %d\n",
           total_frames, speech_frames, total_frames - speech_frames);

    fclose(fin);
    fclose(fout);
    return 0;
}

编译与运行

gcc -o vad_drop vad_drop.c -lm
./vad_drop input.pcm output.pcm

验证方法

准备一段包含语音与静音交替的16kHz、16bit单声道PCM文件(如录制“说话-停顿-说话”约5秒)。运行程序后观察终端输出:总帧数应等于PCM总时长除以20ms;语音帧数应明显小于总帧数。将输出PCM文件用音频工具(如Audacity)以16kHz/16bit/mono导入,可听到静音段被去除,语音段完整连续。判定标准:输出文件时长比输入短,且语音内容无丢失、无额外噪声插入。

验证观测点

输入PCM流 语音帧 静音帧 语音帧 静音帧 静音帧 语音帧 VAD双门限判决 丢弃静音帧 保留语音帧 输出PCM流 语音帧1 语音帧2 语音帧3

上图展示了处理流程:输入PCM流按帧进入VAD判决模块,静音帧(红色虚线路径)被丢弃,语音帧(绿色实线路径)写入输出文件。终端打印的统计信息可直接验证丢弃效果。