文章目录

  • 前言
  • 一、高斯混合模型介绍
    • 1.高斯模型举例
      • 1)定义
      • 2)举例说明
    • 2.高斯混合模型(GMM)
      • 1)定义
      • 2)举例说明
      • 3)一维曲线
  • 二、VAD高斯混合模型
    • 1.模型训练介绍
      • 1)训练方法
      • 2)训练结果
    • 2.噪声高斯模型分布
      • 1)matlab代码
      • 2)结果
    • 2.人声高斯模型分布
  • 三、VAD人声判断
    • 1.高斯分布计算
      • 1)公式
      • 2)对应代码
    • 2.人声判定策略
      • 1)判断机制
      • 2)重要阈值确认
      • 3)整体流程
        • 第一步:计算噪声/人声概率分布
        • 第二步:噪声/人声比较
        • 第三步:判断单个阈值
        • 第四步:全局判定
  • 四、模型更新
    • 1.计算条件概率
    • 2.获取噪声基线和均值
      • 1)噪声基线的作用
      • 2)确定噪声基线
      • 3)获取噪声均值
    • 3.更新噪声均值
      • 1)短时更新
      • 2)平滑修正
      • 3)边界约束
      • 4)策略意义
    • 4.更新人声均值
    • 5.更新人声方差
      • 1)更新公式
      • 2)代码解析
      • 3)边界约束
    • 6.更新噪声方差
    • 7.模型分离
      • 1)计算全局均值
      • 2)计算差值
      • 3)如果模型太近,强行拉开
      • 4)限制模型漂移范围
  • 五、hangover 机制
    • 1.策略作用
    • 2.整体流程
    • 3.对应代码
      • 1)非语音处理
      • 2)语音
  • 总结


前言

在上一篇文章中,介绍了VAD对于能量的计算原理和策略介绍。本篇文章中讲进一步介绍VAD中的人声/噪声决策,这是基于高斯混合模型,也就是GMM进行计算的。

本篇文章将会对照源码、结合图像进行介绍:

  • GMM模型介绍(可以跳过)
  • webrtc中VAD的高斯模型(包括来源以及使用)
  • 人声/噪声决策以及在线更新机制
  • hangover机制

本篇文章比较长,可以耐心观看。

|版本声明:山河君,未经博主允许,禁止转载


一、高斯混合模型介绍

如果对于高斯模型很熟悉那么可以跳过这一节,这里需要明白的是概率密度、区间概率和条件概率的区别。

1.高斯模型举例

1)定义

高斯分布(又叫正态分布)是一种常见的概率分布,它的概率密度也叫做似然函数(PDF)是:
p(x)=12πσ2exp⁡(−(x−μ)22σ2)p(x)=\frac{1}{\sqrt{2\pi\sigma^2}}\exp\Big(-\frac{(x-\mu)^2}{2\sigma^2}\Big)p(x)=2πσ21exp(2σ2(xμ)2)

  • μ\muμ为均值:分布的中心位置,数据大部分集中在它附近。
  • σ2\sigma^2σ2表示方差:分布的宽度,数值越大,图像分布越平;数值越小,分布越尖。

而使用高斯分布计算区间概率公式为:
P(a<X<b)=∫abp(x)dxP(a<X<b)=\int_a^bp(x)dxP(a<X<b)=abp(x)dx

2)举例说明

假设有一门数学考试,100 分满分,有5个学生的成绩分别是60,70,80,75,6560,70,80,75,6560,70,80,75,65

  • 均值μ\muμμ=1N∑i=1Nxi=60+70+80+75+655=70\mu=\frac{1}{N}\sum_{i=1}^{N}x_i = \frac{60+70+80+75+65}{5}=70μ=N1i=1Nxi=560+70+80+75+65=70
  • 方差σ2=1N∑i=1N(xi−μ)2=(−10)2+02+102+52+(−5)25=50\sigma^2=\frac{1}{N}\sum_{i=1}^{N}(x_i-\mu)^2=\frac{(-10)^2+0^2+10^2+5^2+(-5)^2}{5}=50σ2=N1i=1N(xiμ)2=5(10)2+02+102+52+(5)2=50

那么现在还有一个学生

  • 他的成绩为737373分的概率带入公式最终结果为:5.16%5.16\%5.16%
  • 他的成绩在[70,75][70,75][70,75]分之间的概率带入公式为:26%26\%26%

2.高斯混合模型(GMM)

1)定义

GMM 就是多个高斯分布的加权和,它的概率密度公式是:
p(x)=∑k=1Kwk⋅N(x∣uk,σk2)p(x)=\sum_{k=1}^{K}w_k \cdot \Nu(x|u_k,\sigma_k^2)p(x)=k=1KwkN(xuk,σk2)

  • kkk:高斯分布的个数
  • wkw_kwk:每个分布的权重(比例,所有权重相加 = 1)
  • uk,σku_k,\sigma_kuk,σk:第k 个高斯分布的均值和方差
  • N(x∣uk,σk2)\Nu(x|u_k,\sigma_k^2)N(xuk,σk2):第k个高斯分布的概率密度函数 (PDF)

而此时我们计算某一个值属于哪一个高斯分布的过程叫做条件概率(责任度)
P(lk∣x)=lkp(x)=wk⋅N(x∣uk,σk2)p(x)P(l_k|x)=\frac{l_k}{p(x)}=\frac{w_k \cdot \Nu(x|u_k,\sigma_k^2)}{p(x)}P(lkx)=p(x)lk=p(x)wkN(xuk,σk2)

2)举例说明

上文中是一个以成绩的例子进行计算高斯分布,但显然现实中要考虑的因素会更多:

  • 一部分学生是学霸30%30\%30%,集中在85±585 \pm 585±5分附近
  • 一部分学生是普通学生70%70\%70%,集中在65±1065\pm 1065±10分附近

那么由此可以得到:

  • 学霸群体:均值u1=85u_1 = 85u1=85,标准差σ1=5\sigma_1= 5σ1=5,权重w1=0.3w_1= 0.3w1=0.3
  • 普通学生:均值u2=65u_2 = 65u2=65,标准差σ2=10\sigma_2= 10σ2=10,权重w2=0.7w_2= 0.7w2=0.7

此时有一个学生成绩为70,那么他的条件概率分别为:

  • P(l1∣x)=l1p(x)≈0.01067(1.07%)P(l_1|x)=\frac{l_1}{p(x)} \approx 0.01067(1.07\%)P(l1x)=p(x)l10.01067(1.07%)
  • P(l2∣x)=l2p(x)=1−P(l1∣x)≈0.98933(98.93%)P(l_2|x)=\frac{l_2}{p(x)} = 1- P(l_1|x) \approx 0.98933 (98.93\%)P(l2x)=p(x)l2=1P(l1x)0.98933(98.93%)

很明显,该学生大概率属于普通学生

3)一维曲线

使用matlab画出曲线

x = linspace(30, 100, 500);% 学霸群体参数
mu1 = 85; sigma1 = 5; w1 = 0.3;
pdf1 = w1 * normpdf(x, mu1, sigma1);% 普通学生群体参数
mu2 = 65; sigma2 = 10; w2 = 0.7;
pdf2 = w2 * normpdf(x, mu2, sigma2);% 混合分布
pdf_mix = pdf1 + pdf2;% 绘制
figure;
plot(x, pdf1, 'b--', 'LineWidth', 1.5); hold on;
plot(x, pdf2, 'g--', 'LineWidth', 1.5);
plot(x, pdf_mix, 'r-', 'LineWidth', 2);
grid on;
legend('学霸群体 N(85,5^2)*0.3', ...'普通学生 N(65,10^2)*0.7', ...'混合分布 (GMM)');
title('考试成绩的高斯混合模型 (GMM)');
xlabel('成绩');
ylabel('概率密度');

在这里插入图片描述

二、VAD高斯混合模型

1.模型训练介绍

1)训练方法

webrtc关于VAD的高斯混合模型训练是一种离线训练,是通过以下几个步骤得到的:

  • 准备数据
    • 收集了大量的语音数据(各种语言、男女声、不同音量)
    • 收集了大量的噪声数据(白噪声、街道、办公室、电话线路噪声等)
  • 特征提取
    • 对每一帧音频(10ms ~ 30ms)提取简单特征:子带能量、总能量、过零率
    • 特征维度很低,不像是MFCC会有多个维度,但适合在嵌入式环境运行
  • 拟合高斯分布
    • 把“语音帧特征”丢进一个高斯混合模型训练器
    • 把“噪声帧特征”丢进另一个 GMM 训练器
    • 使用EM算法迭代得到每个分量的均值、方差、权重
  • 量化和存储
    • 得到的均值、方差、权重定点数存储(int16_t),方便在 C 代码里运行
    • 该套参数固定写死在代码表里,运行时虽然会根据实际情况修改,但不会在运行后进行存储

由此我们也可以直觉看出来这套模型的明显的优缺点:

特性优点缺点
计算效率高,适合实时
模型复杂度低,易实现表达能力有限
多模态表示可以区分静音/语音只适合简单多模态
鲁棒性可自适应噪声变化对异常噪声敏感
时序建模需要额外平滑

2)训练结果

webrtc中训练后的结果是以Q7格式分别存储在:

  • kNoiseDataWeights:噪声的两个高斯分布权重,表格中的G0和G1
  • kNoiseDataMeans:噪声的两个高斯分布均值
  • kNoiseDataStds:噪声的两个高斯分布的方差
  • kSpeechDataWeights:噪声的两个高斯分布权重,表格中的G0和G1
  • kSpeechDataMeans:噪声的两个高斯分布均值
  • kSpeechDataStds:噪声的两个高斯分布的方差
  • G0:低幅度、常见特征,是频带特征里的主分量
  • G1:高幅度、变化性更大的特征,是频带特征里的补充分量
频带高斯Noise 权重Speech 权重Noise 均值Speech 均值Noise StdSpeech Std
0G0344867388306378555
G162824892100851064505
1G07245706510078493567
G16687671511823582524
2G05350677111843688585
G12547336963095931231
3G0948076469473474509
G1664638639571697828
4G05683782010879475492
G16241726675816881540
5G07578502081804211079
G11038143627483455850

在代码中,将会在WebRtcVad_InitCore接口中存储在VadInstT结构体中。

2.噪声高斯模型分布

下面将根据具体的参数,画出6个子带的两个高斯分布G0,G1图像,然后再根据权重进行G0和G1加权后的高斯混合图像。

1)matlab代码

clc; clear; close all;% 原始 Q7 数据
kNoiseDataMeans_Q7   = [6738, 4892, 7065, 6715, 6771, 3369, 7646, 3863, 7820, 7266, 5020, 4362];
kNoiseDataStds_Q7    = [378, 1064, 493, 582, 688, 593, 474, 697, 475, 688, 421, 455];
kNoiseDataWeights_Q7 = [34, 62, 72, 66, 53, 25, 94, 66, 56, 62, 75, 103];% 转为 Q0(浮点)
kNoiseDataMeans   = double(kNoiseDataMeans_Q7) / 128;
kNoiseDataStds    = double(kNoiseDataStds_Q7) / 128;
kNoiseDataWeights = double(kNoiseDataWeights_Q7);   % 权重本身可以直接使用kTableSize = length(kNoiseDataMeans);
numBands = kTableSize / 2;figure;for band = 1:numBands% 提取 G0, G1 的参数mean0 = kNoiseDataMeans(2*band-1);mean1 = kNoiseDataMeans(2*band);std0  = kNoiseDataStds(2*band-1);std1  = kNoiseDataStds(2*band);weight0 = kNoiseDataWeights(2*band-1);weight1 = kNoiseDataWeights(2*band);% x 范围,取 ±4σx_min = min(mean0-4*std0, mean1-4*std1);x_max = max(mean0+4*std0, mean1+4*std1);x = linspace(x_min, x_max, 500);% 高斯分布G0 = (1/(std0*sqrt(2*pi))) * exp(-0.5*((x-mean0)/std0).^2);G1 = (1/(std1*sqrt(2*pi))) * exp(-0.5*((x-mean1)/std1).^2);% 加权混合Gmix = (weight0*G0 + weight1*G1) / (weight0 + weight1);% 绘图subplot(2,3,band);plot(x, G0, 'b', 'LineWidth',1.5); hold on;plot(x, G1, 'r', 'LineWidth',1.5);plot(x, Gmix, 'k--', 'LineWidth',1.5);title(['Subband ' num2str(band)]);legend('G0','G1','G_{mix}');xlabel('Value'); ylabel('Probability Density');
end

2)结果

在这里插入图片描述

2.人声高斯模型分布

和计算噪声高斯模型分布一样,只需要将matlab中代码替换即可,这里就不贴出代码,直接看结果:
在这里插入图片描述

三、VAD人声判断

在上一篇文章webrtc之语音活动上——VAD能量检测原理以及源码详解中,我们已经知道了:

  • 检测模式的区别
  • 帧长划分方法
  • 6个非等宽频带的能量
  • 频带划分的意义

1.高斯分布计算

1)公式

源码中关于PDF计算使用Q格式运算以保证精度,其计算公式为:
1σ⋅exp⁡(−(x−m)22∗σ2)\frac{1}{\sigma} \cdot \exp\Big(-\frac{(x - m)^2} { 2 * \sigma^2}\Big)σ1exp(2σ2(xm)2)
该接口的计算方式少了一个2π\sqrt{2\pi}2π,并且这里同样将log⁡\loglog运算简化,因此它是一个相对值(比例),而缩放后的 PDF是用于相对比较,不是严格的概率密度,不会影响后续决策与更新。

2)对应代码

接口名为WebRtcVad_GaussianProbability,以下是参数和返回值:

  • input:频带能量,以Q4格式保存
  • mean:噪声/人声的高斯均值,Q7格式
  • std:噪声/人声的高斯方差,Q7格式
  • deltax−ms2\frac{x-m}{s^2}s2xm,用于后续更新模型,Q11格式
  • 返回值:概率密度,Q20格式

2.人声判定策略

1)判断机制

VAD判断人声是双重机制:

  • 局部判定: 捕捉某个子带特别强烈的语音
  • 全局判定:要求整体证据够强
  • 最终决策:两者取 OR,既保证灵敏度,又保证鲁棒性

2)重要阈值确认

在设置模式WebRtcVad_set_mode_core接口里,确定

  • individual:单个频带人声阈值范围,用于局部判定
  • total:整体人声阈值阈值范围,用于整体

而对于不同帧长再进一步确定阈值,这里是在GmmProbability真正进行决策的地方进行确定。而对于:

  • 短帧10ms:由于信息少,则使用高阈值以避免误判
  • 中等20ms:阈值低,放宽一些
  • 长帧30ms:本身信息稳定,为了避免太宽松 又设高

除此之外,在进行全局判定时,不同频带的权重存储在kSpectrumWeight中。
在这里插入图片描述
值得注意的是,这些阈值的由来是根据大规模实验 + ROC 曲线分析 + 主观听感测试 手工调出来的经验值。

3)整体流程

在这里插入图片描述

第一步:计算噪声/人声概率分布

同一段频带语音根据噪声/人声的高斯参数,分别进行计算主分量/补充分量的概率分布,再根据权值计算真正的概率密度,代码如下:

for (channel = 0; channel < kNumChannels; channel++) {h0_test = 0;h1_test = 0;for (k = 0; k < kNumGaussians; k++) {gaussian = channel + k * kNumChannels;tmp1_s32 = WebRtcVad_GaussianProbability(features[channel],self->noise_means[gaussian],self->noise_stds[gaussian],&deltaN[gaussian]);noise_probability[k] = kNoiseDataWeights[gaussian] * tmp1_s32;h0_test += noise_probability[k];  // Q27tmp1_s32 = WebRtcVad_GaussianProbability(features[channel],self->speech_means[gaussian],self->speech_stds[gaussian],&deltaS[gaussian]);speech_probability[k] = kSpeechDataWeights[gaussian] * tmp1_s32;h1_test += speech_probability[k];  // Q27}
第二步:噪声/人声比较

判断单个频带是否为人声使用的是似然对数比,也就是log⁡2(Pr(X∣H1)Pr(X∣H0))\log2(\frac{Pr(X|H1)}{Pr(X|H0)})log2(Pr(XH0)Pr(XH1)),这么做的好处是:

  • 避免概率数值太小引起数值问题
  • LLR 的加和性质,可以进行多个频带综合判定

值得注意的是:这近似在数帧平均下通常偏差较小,但对极端 / 较小样本会产生偏差,所以后面通过局部+全局、hangover机制与模型更新来抵消误判

这里的移位操作是为了简化log⁡\loglog运算,用最高位表示为整数部分,用整数位的差值来近似log⁡2(h1/h0)\log2(h1/h0)log2(h1/h0),核心代码为:

      shifts_h0 = WebRtcSpl_NormW32(h0_test);shifts_h1 = WebRtcSpl_NormW32(h1_test);if (h0_test == 0) {shifts_h0 = 31;}if (h1_test == 0) {shifts_h1 = 31;}log_likelihood_ratio = shifts_h0 - shifts_h1;
第三步:判断单个阈值

如果单个频带的信号人声强烈,将会直接认定为语音,值得注意的是即使判断为人声了,还是会接着计算其他频带,这是为了更新模型。核心代码为:

if ((log_likelihood_ratio * 4) > individualTest) {vadflag = 1;}
第四步:全局判定

这里分为两部分

  • 根据权重加合
  • 整体阈值判断

其核心代码如下:

sum_log_likelihood_ratios +=(int32_t) (log_likelihood_ratio * kSpectrumWeight[channel]);.......vadflag |= (sum_log_likelihood_ratios >= totalTest);         

四、模型更新

在进行模型更新时,需要考虑到平滑处理,所以这里运用到大量的平滑滤波的思想,见文章语音信号处理三十一——常用的时域/频域平滑滤波。

1.计算条件概率

首先获取各个频带噪声/人声在主分量和补充分量的条件概率,分别存储在ngprvecsgprvec中。

如果h<0h<0h<0,那么默认该数值必然是主分量特征,以下是噪声的条件概率核心代码,人声类似:

h0 = (int16_t) (h0_test >> 12);  // Q15if (h0 > 0) {tmp1_s32 = (noise_probability[0] & 0xFFFFF000) << 2;  // Q29ngprvec[channel] = (int16_t) WebRtcSpl_DivW32W16(tmp1_s32, h0);  // Q14ngprvec[channel + kNumChannels] = 16384 - ngprvec[channel];} else {ngprvec[channel] = 16384;}

2.获取噪声基线和均值

1)噪声基线的作用

WebRTC VAD 和很多能量型 VAD 都是基于一个基本假设:在连续音频信号中,语音的能量通常高于环境噪声的能量。

而噪声基的含义是指:环境噪声在短时间或一段时间内的平均能量水平或特征值参考。

2)确定噪声基线

噪声基线的获取是一种中值滤波的思想。在接口WebRtcVad_FindMinimum中,它的大致流程为:

输入: feature_value, channel, self┌─► [1] 更新历史值的年龄
│      ├─ 遍历16个存储的最小值
│      ├─ age < 100 → age++
│      └─ age == 100 → 移除该值,数组前移,最后位置填充大数
│
└─► [2] 判断是否插入新值├─ feature_value 比数组中的某些值小│     └─ 找到插入位置 position│          └─ 从尾部往后移,插入新值,age=1└─ 否则丢弃(说明它不是最小16个之一)┌─► [3] 计算当前中位数
│      ├─ frame_counter > 2 → 取 smallest_values[2] (第3小)
│      └─ frame_counter <= 2 → 取 smallest_values[0] (最小值)
│
└─► [4] 平滑更新 mean_value[channel]├─ 如果 current_median < mean_value → α = 0.2 (快速下降)└─ 否则 α = 0.99 (缓慢上升)└─ mean_value[channel] ← α * mean_value[channel] + (1-α) * current_median[5] 返回 mean_value[channel]

这里可以对照代码进行理解。

3)获取噪声均值

实现主要在WeightedAverage接口内,该接口的使用方法为:

  • 输入主分量和补充分量的均值
  • 输入对应的权重
  • 输出计算后的均值

3.更新噪声均值

1)短时更新

WebRTC 里不可能每次都存很多帧再做完整的EM,并且GMM更新中webrtc希望带权,所以这里采用的是梯度递推进行更新,公式为:
μnoisenow=μnoiseold+η⋅γnow(x−μnoiseold)\mu_{noise}^{now} = \mu_{noise}^{old}+\eta\cdot \gamma_{now}(x-\mu_{noise}^{old})μnoisenow=μnoiseold+ηγnow(xμnoiseold)

  • η\etaη:权系数
  • γnow\gamma_{now}γnow:当前频带当前分量的条件概率

由于篇幅原因,这里不做推导了,可以看看别的文章,或者可以私信博主

这里加权是并不希望噪声平均变化过快。对应代码为:

static const int16_t kNoiseUpdateConst = 655; // Q15
nmk2 = nmk;
if (!vadflag) {delt = (int16_t)((ngprvec[gaussian] * deltaN[gaussian]) >> 11);nmk2 = nmk + (int16_t)((delt * kNoiseUpdateConst) >> 22);
}

2)平滑修正

首先会用噪声基线减去频带噪声均值得到差值,再根据差值进行指数加权移动平均的思想,和当前帧的噪声均值相加为下一帧的噪声均值,对应代码:

ndelt = (feature_minimum << 4) - tmp1_s16;
nmk3 = nmk2 + (int16_t)((ndelt * kBackEta) >> 9);

值得注意的是:平滑修正不受vadflag影响

3)边界约束

即强制噪声均值不能小于某个最小值,和防止噪声均值过大,避免它被语音能量无限拉升。代码如下:

tmp_s16 = (int16_t) ((k + 5) << 7);if (nmk3 < tmp_s16) {nmk3 = tmp_s16;}tmp_s16 = (int16_t) ((72 + k - channel) << 7);if (nmk3 > tmp_s16) {nmk3 = tmp_s16;}

4)策略意义

对于nmk2会受到γ\gammaγ的影响,而γ\gammaγ并不是固定常数,而是随着当前帧的条件概率变动,一旦语音能量进入,更新方向就会发生错误。此时平滑修正的意义就体现出来:

  • 短时更新可能受到语音污染,可能偏离真实噪声基线
  • 如果当前帧判定为非噪声,那么均值会被kBackEta慢慢收敛回长期基线

4.更新人声均值

和更新噪声均值一样的梯度更新计算方法,只是区别是:

  • 不需要进行长期拉回,因为人声是特征动态明显,不需要噪声基线作为参考
  • 噪声的限幅控制是为了保证模型稳定,避免漂移到语音区,而人声限幅是跟随语音分布变化,防止过度漂移,所以限幅范围不同
  • 人声均值向上取整而不是向下取整smk2 = smk + ((tmp_s16 + 1) >> 1);

5.更新人声方差

1)更新公式

和上文一样,这里采用的是递推更新:
σnew=σold+η⋅wkσold((x−μold2)σold2−1)\sigma_{new}=\sigma_{old}+\eta\cdot \frac{w_k}{\sigma_{old}}\Big(\frac{(x-\mu_{old}^2)}{\sigma^2_{old}} -1\Big)σnew=σold+ησoldwk(σold2(xμold2)1)

2)代码解析

tmp_s16 = ((smk + 4) >> 3);tmp_s16 = features[channel] - tmp_s16;  tmp1_s32 = (deltaS[gaussian] * tmp_s16) >> 3;tmp2_s32 = tmp1_s32 - 4096;tmp_s16 = sgprvec[gaussian] >> 2;tmp1_s32 = tmp_s16 * tmp2_s32;tmp2_s32 = tmp1_s32 >> 4; if (tmp2_s32 > 0) {tmp_s16 = (int16_t) WebRtcSpl_DivW32W16(tmp2_s32, ssk * 10);} else {tmp_s16 = (int16_t) WebRtcSpl_DivW32W16(-tmp2_s32, ssk * 10);tmp_s16 = -tmp_s16;}tmp_s16 += 128;  // Rounding.ssk += (tmp_s16 >> 8);

其中:

  • wkw_kwk:来自于sgprvec
  • η\etaη:为0.025,代码中体现在WebRtcSpl_DivW32W16产生了0.1的分母,sgprvec[gaussian] >> 2产生了4的分母
  • tmp_s16 = ((smk + 4) >> 3):这里的加4原因是为了保证右移3位是向上取整保存精度

3)边界约束

方差值不能小于384

static const int16_t kMinStd = 384;
if (ssk < kMinStd) {ssk = kMinStd;}

6.更新噪声方差

和更新人声方差一样,区别在于更新步长的大小是根据偏移量决定的,不过范围大致在0.015~0.02 之间。

7.模型分离

为了防止speech GMM 模型和 noise GMM 模型的均值太接近而粘在一起,需要最终结合两者进行决策。

1)计算全局均值

noise_global_mean  = WeightedAverage(&self->noise_means[channel], 0, &kNoiseDataWeights[channel]);
speech_global_mean = WeightedAverage(&self->speech_means[channel], 0, &kSpeechDataWeights[channel]);
  • 对每个 channel,把各个高斯分量的均值做加权平均。
  • 得到一个 全局 noise 平均值 和一个 全局 speech 平均值。

2)计算差值

diff = (int16_t)(speech_global_mean >> 9) - (int16_t)(noise_global_mean >> 9);
  • speech 全局均值 − noise 全局均值。
  • 如果差值太小,说明两个模型过于接近

3)如果模型太近,强行拉开

if (diff < kMinimumDifference[channel]) {tmp_s16 = kMinimumDifference[channel] - diff;// tmp1_s16 ≈ 0.8 * (缺口)// tmp2_s16 ≈ 0.2 * (缺口)// 把 speech 模型整体往上推一点speech_global_mean = WeightedAverage(&self->speech_means[channel],tmp1_s16, &kSpeechDataWeights[channel]);// 把 noise 模型整体往下拉一点noise_global_mean  = WeightedAverage(&self->noise_means[channel],-tmp2_s16, &kNoiseDataWeights[channel]);
}
  • 如果 gap 太小,就把 speech 往上推 80%,noise 往下拉 20%,强制保持至少 kMinimumDifference 的区分度。
  • 这样避免两个模型均值重叠,保持判决的鲁棒性。

4)限制模型漂移范围

if (speech_global_mean >> 7 > kMaximumSpeech[channel]) {// speech 上限...
}
if (noise_global_mean >> 7 > kMaximumNoise[channel]) {// noise 上限...
}

作用是给 speech 和 noise 均值设上界,避免模型被极端值推得太远。

五、hangover 机制

1.策略作用

WebRTC VAD 的hangover 机制,用来在语音刚结束时 延长一小段时间继续判为语音,避免抖动。

2.整体流程

检测到语音 → num_of_speech++├─ 如果 num_of_speech <= kMaxSpeechFrames → over_hang = overhead1└─ 如果 num_of_speech >  kMaxSpeechFrames → over_hang = overhead2检测到非语音├─ 如果 over_hang > 0 → 继续输出语音, over_hang--└─ 如果 over_hang = 0 → 输出静音

其中:

  • overhead1:给短语音的尾巴加一点点余量。
  • overhead2:给长语音的尾巴加更长的余量。
  • num_of_speech:用来区分当前语音段的“长/短”。
  • over_hang:用来平滑语音和静音的切换,防止“断断续续”

3.对应代码

1)非语音处理

if (!vadflag) {if (self->over_hang > 0) {vadflag = 2 + self->over_hang;self->over_hang--;}self->num_of_speech = 0;
}
  • 但 hangover 计数器 over_hang > 0:说明前面刚有语音 → 进入“延长语音”阶段

    • 把 vadflag 设置为 2 + self->over_hang(这里 2 是特殊标记,表明这不是直接检测出的语音,而是 hangover 延长出来的语音)
    • over_hang--:计数器递减
  • 否则就彻底当作 silence

  • num_of_speech = 0:清零语音帧计数器

2)语音

else {self->num_of_speech++;if (self->num_of_speech > kMaxSpeechFrames) {self->num_of_speech = kMaxSpeechFrames;self->over_hang = overhead2;} else {self->over_hang = overhead1;}
}
  • num_of_speech++:累计连续语音帧数
  • 如果超过 kMaxSpeechFrames(最大语音帧数限制):
    • num_of_speech 固定到上限
    • over_hang = overhead2(长的 hangover 时间)
  • 否则:over_hang = overhead1(短的 hangover 时间)

总结

WebRTC VAD 展示了一个典型的“经典信号处理 + 工程优化”方案:

  • 在特征层面,采用子带能量的对数刻画,使得语音与噪声分布更接近高斯;
  • 在模型层面,使用固定参数的双高斯混合模型,结合局部和全局判决,提高鲁棒性;
  • 在实现层面,大量利用 Q 格式、移位近似、预计算表,保证了低算力环境下的实时性;
  • 在动态性上,通过逐帧更新和边界约束,使模型能逐渐适应环境变化。

当然,这种基于 GMM 的方法也有局限:在强噪声、非平稳噪声环境下可能误判,且阈值调优高度依赖经验。随着算力提升,DNN/RNN 基的 VAD 在鲁棒性上表现更优,但代价是更高的复杂度与延迟。

反正收藏也不会看,不如点个赞吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/97837.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/97837.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/97837.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】-- 主从复制

文章目录1. 主从复制1.1 主从复制是怎么个事&#x1f914;1.2 拓扑结构1.2.1 一主一从拓扑1.2.2 一主多从拓扑1.2.3 树形拓扑1.3 主从复制原理1.3.1 复制过程1.3.2 数据同步PSYNC1.3.2.1 replicationid/replid (复制id)1.3.2.2 复制偏移量维护1.3.3 psync运行流程1.3.4 全量复制…

开源炸场!阿里通义千问Qwen3-Next发布:80B参数仅激活3B,训练成本降90%,长文本吞吐提升10倍​

开源炸场&#xff01;阿里通义千问Qwen3-Next发布&#xff1a;80B参数仅激活3B&#xff0c;训练成本降90%&#xff0c;长文本吞吐提升10倍​ 开源世界迎来震撼突破&#xff01; 通义千问团队最新发布的Qwen3-Next架构&#xff0c;以其独创的"小而精"设计理念&#x…

【C++入门】C++基础

目录 1. 命名空间 1.1 命名空间的创建和使用 2. 输入输出 2.1 输出 2.2 输入 3. 缺省参数 3.1 全缺省 3.2 半缺省 4.函数重载 4.1 为什么C支持重载而C语言不支持&#xff1f; 4.1.2 编译的四个过程 4.2 extern是什么 5.引用 5.1 引用的特性 5.1.1 引用的“隐式类…

如何往mp4视频添加封面图和获取封面图?

前言&#xff1a;大家好&#xff0c;之前有给大家分享过mp4录像的方案&#xff0c;今天给大家分享的内容是&#xff1a;如何在添加自定义的封面图到mp4里面去&#xff0c;以及在进入回放mp4视频列表的时候&#xff0c;怎么获取mp4视频里面的封面图&#xff0c;当然这个获取到的…

你的第一个Transformer模型:从零实现并训练一个迷你ChatBot

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;注册即送-H卡级别算力&#xff0c;80G大显存&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生更享专属优惠。 引言&#xff1a;破除神秘感&#xff0c;拥抱核心思想 …

【20期】沪深指数《实时交易数据》免费获取股票数据API:PythonJava等5种语言调用实例演示与接口API文档说明

​ 随着量化投资在金融市场的快速发展&#xff0c;高质量数据源已成为量化研究的核心基础设施。本文将系统介绍股票量化分析中的数据获取解决方案&#xff0c;涵盖实时行情、历史数据及基本面信息等关键数据类型。 本文将重点演示这些接口在以下技术栈中的实现&#xff1a; P…

RabbitMQ如何保障消息的可靠性

文章目录什么是消息可靠性&#xff1f;RabbitMQ消息可靠性的三个维度1. 生产者到Exchange的可靠性2. Exchange到Queue的可靠性3. Queue到消费者的可靠性核心机制详解Publisher Confirm机制消息持久化Mandatory参数消费者确认机制&#xff08;ACK&#xff09;最佳实践建议1. 合理…

二十、DevOps落地:Jenkins基础入门(一)

二十、DevOps落地&#xff1a;Jenkins基础入门&#xff08;一&#xff09; 文章目录二十、DevOps落地&#xff1a;Jenkins基础入门&#xff08;一&#xff09;1、DevOps初识1.1 什么是DevOps1.2 DevOps相关工具链1.3 什么是CICD&#xff1f;1.4 持续集成CI介绍1.5 持续交付和持…

简单易实现的数据校验方法Checksum

简单易实现的数据校验方法Checksum 在数据传输中&#xff0c;Checksum&#xff08;校验和&#xff09; 扮演着 “数据完整性哨兵” 的角色。它的主要作用是 快速检测数据在传输过程中是否发生了错误 。 下面我将详细解释它的作用、工作原理、优缺点以及典型应用。 核心作用&…

再次深入学习深度学习|花书笔记1

我已经两年没有碰过深度学习了&#xff0c;写此文记录学习过程&#xff0c;加深理解。 深度学习再次深入学习深度学习|花书笔记1信息论第四节 数值计算中的问题上溢出 和 下溢出病态条件优化法再次深入学习深度学习|花书笔记1 这本书说的太繁琐了&#xff0c;如果是想要基于这…

DeerFlow实践:华为LTC流程的评审智能体设计

目录 一、机制设计核心逻辑 二、4 个评审点智能体机制详解 &#xff08;一&#xff09;立项决策&#xff08;ATI&#xff09;智能体机制 1. 知识调用与匹配 2. 评审校验流程 3. 异常处理 &#xff08;二&#xff09;投标决策&#xff08;ATB&#xff09;智能体机制 1. …

C++与Lua交互:从原理到实践指南

核心原理&#xff1a;Lua虚拟栈机制 C与Lua能够高效交互的核心在于Lua虚拟栈的设计&#xff0c;这是一个精巧的中立通信区&#xff0c;解决了两种语言间的本质差异&#xff1a;特性对比CLua语言类型静态编译型动态解释型数据管理明确内存布局虚拟机统一管理类型系统编译时确定运…

CSS 编码规范

CSS 编码规范1 CSS1.1 编码规范1.1.1 【强制】所有声明必须以分号结尾1.1.2 【推荐】使用 2 个空格缩进1.1.3 【推荐】选择器与 { 之间保留一个空格1.1.4 【推荐】属性值规范1.1.5 【推荐】组合器规范1.1.6 【推荐】逗号分隔规范1.1.7 【推荐】注释规范1.1.8 【推荐】右大括号规…

ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务

已经不止一次自己本机电脑安装的Oracle使用plsqldev软件登入提示这个了.一般前一天还好好的&#xff0c;今天就不行了.好好总结一下吧&#xff0c;也共大家一起借鉴.主要原因还是数据的归档日志因为内部内存已经耗尽&#xff0c;不能在进行归档导致数据库启动异常&#xff0c;没…

Spring框架的JDBC模板技术和事务管理

SpringJDBCJDBC模板技术概述JDBC的模板类的使用Spring框架的事务管理配置文件方式半注解的方式纯注解的方式JDBC模板技术概述 什么是 JDBC 模板技术&#xff1f; JDBC 模板技术是 Spring 框架为简化持久层&#xff08;数据库操作&#xff09;编程而提供的一种封装机制&#xf…

将文件部署到受管主机

目录 1.ansible.builtin中用于创建、更新或删除多行文本块的模块是什么 2.copy模块的作用 3.fetch模块的作用 4.file模块的作用 5.lineinfile模块的作用 6.stat模块的作用 7.要确保受管主机上存在文件&#xff0c;类似touch命令功能&#xff0c;还能设置权限等的模块及操作是怎…

Dell PowerEdge R620 服务器内存和硬盘罢工了

文章目录前言调查原因查找解决方案硬盘问题内存问题总结前言 月黑风高夜&#xff0c;服务宕机时。做服务端技术的&#xff0c;谁还没半夜遇到个服务挂掉的情况&#xff0c;而像我这种半兼职网管的工作&#xff0c;遇到机器问题的概率也就更大了&#xff0c;本来周五晚上写完总…

2025:SourceTree 启用/禁用Mercurial 或 Git,像素级细节

最近使用Git管理工具的时候&#xff0c;发现还是SourceTree好用些&#xff0c;但是使用SourceTree带来一个问题&#xff1a;就是每次在重新打开SourceTree的时候&#xff0c;都会重新下载Mercurial.zip文件&#xff0c;查了一下&#xff0c;一般情况下我们是不需要使用Mercuria…

安卓 Google Maps 的使用和开发步骤

文章目录1. main2. Android 谷歌地图3. 源码Reference1. main 在国内选择的SDK可以是高德、百度、腾讯、xxxx等&#xff0c;但在国外&#xff0c;你首选是谷歌&#xff0c;因此要进行Google地图的开发你首先要解决下面三个问题 VPN Google账号 信用卡American Express&#x…

Linux -- 应用层协议Http

1.HTTP背景知识 HTTP协议&#xff1a;HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;的本质是运行在 TCP/IP 协议族之上的 “应用层协议”&#xff0c;核心作用是定义客户端&#xff08;如浏览器、APP&#xff09;与服务器之间的 “数据…