猴哥的第 188 期分享,欢迎追看
前面分享了,给小智 AI 接入 ASR:
延迟是关键,为了抛弃对 GPU 的依赖,接入了火山引擎的流式语音识别
:
ASR 部分的流程图如下:
其中,VAD
之前采用的模型来自 FunASR。
最近在把 小智AI服务端
拆成各个独立的微服务,封装成 Docker 镜像,目标是所有微服务可以在 CPU 上跑起来。
不过这个 VAD
实现需要依赖 torch
,打包后的镜像体积来到了 1.59G。
急需寻找 VAD
的平替方案!
今日分享:一款轻量高效的 VAD
方案 -- TEN-VAD
,并给出对比实测,给有类似需求的朋友一点点参考!
1.TEN-VAD 简介
TEN-VAD 是一个低延迟,高准确率 语音活动检测模型,来看官方效果图:
有哪些亮点?
1. 跨平台兼容:
TEN VAD 开源了 Linux(.so)、Windows(.dll)、macOS & iOS(.framework)、Android(.so)、Web(.wasm)所需的二进制文件及示例代码,开箱即用。
2. 轻量级:
Linux 平台下的动态库链接,低至 306KB:
- TEN-VAD 使用
本文将基于 Linux 平台进行实测。
2.1 工作原理
官方提供了动态链接库 - libten\_vad.so
,它是由编译型语言编写,并编译成机器码的二进制文件。
而 Python 中,可以通过 ctypes 模块,调用 C/C++ 编写的动态库:
from ctypes import CDLL
self.vad\_library = CDLL("ckpts/libten\_vad.so") # 加载动态库
调用过程如下:
Python 代码 → ctypes → libten\_vad.so → 执行 C/C++ 代码 → 返回结果
从而实现:用简单的 Python 语言,充分利用 C/C++ 的性能优势。
注意 :系统中需安装libc++1
:
sudo apt update
sudo apt install libc++1
2.2 实现逻辑
官方提供了 Python 调用的示例代码,位于include/ten\_vad.py
。
无需任何复杂依赖,只要环境中有 numpy
即可跑起来!
核心功能函数如下:
def process(self, audio\_data: np.ndarray):
input\_pointer = self.get\_input\_data(audio\_data)
self.vad\_library.ten\_vad\_process(
self.vad\_handler,
input\_pointer,
c\_size\_t(self.hop\_size),
POINTER(c\_float)(self.out\_probability),
POINTER(c\_int32)(self.out\_flags),
)
return self.out\_probability.value, self.out\_flags.value
输入是 np.int16
类型的 PCM 音频格式,长度等于 hop\_size
(默认256个采样点)。
注意 :输入必须是 16kHz 采样的音频数据,因此 256 采样点对应 16ms 的音频。
输出是语音概率和标志位,用于判断每个小段音频是否包含语音活动。
2.3 流式输入
问题来了 :如何用它实时检测流式输入音频中的语音活动?
要实现哪些功能呢?
- 语音段检测
- 将输入的长音频分割成小块(每块 hop_size 个采样点)
- 逐块检测是否包含语音活动
- 记录语音段的开始和结束时间
- 连续处理与缓存
- 支持流式处理(通过 truncated 参数控制)
- 维护 vad_cache 缓存,保存历史检测结果
- 处理跨批次的语音段连接
以下给出笔者的实现,供大家参考:
def generate(self, input: np.ndarray, truncated=False):
'''process audio data trunks'''
if truncated:
self.last\_pos = 0
self.vad\_cache = []
num\_frames = input.shape[0] // self.hop\_size
speech\_segments = []
current\_segment\_start = None
for i in range(num\_frames):
audio\_data\_trunk = input[i * self.hop\_size: (i + 1) * self.hop\_size]
\_, out\_flag = self.process(audio\_data\_trunk)
# 计算当前chunk的时间戳(毫秒)
current\_time\_ms = self.last\_pos + i * self.hop\_size // 16 # 采样率为16kHz
if out\_flag == 1 and current\_segment\_start is None:
# 如果检测到语音且当前没有记录起始位置,则记录起始位置
current\_segment\_start = current\_time\_ms
if out\_flag == 0 and current\_segment\_start is not None:
# 如果检测到非语音且之前有语音段,则结束当前段
speech\_segments.append([current\_segment\_start, current\_time\_ms])
current\_segment\_start = None
# 处理最后一段语音(如果结束时仍在语音中)
if current\_segment\_start is not None:
speech\_segments.append([current\_segment\_start, -1])
# 处理vad\_cache
if len(self.vad\_cache) > 0 and self.vad\_cache[-1][1] == -1:
if len(speech\_segments) == 0:
self.vad\_cache[-1][1] = self.last\_pos
else:
self.vad\_cache[-1][1] = speech\_segments[0][1]
speech\_segments = speech\_segments[1:]
self.vad\_cache.extend(speech\_segments)
self.last\_pos += num\_frames * self.hop\_size // 16
return self.vad\_cache
2.4. 延时实测
输入为 240ms 的音频片段,延时实测效果如下:
- 方案一:FunASR 中的 VAD模型:
Time cost: 0.16324639320373535s []
Time cost: 0.007938146591186523s [[0, -1]]
Time cost: 0.007047176361083984s []
Time cost: 0.006856679916381836s []
Time cost: 0.006888389587402344s [[-1, 930], [930, -1]]
- 方案二:TEN-VAD 模型:
Time cost: 0.004173994064331055s [[208, -1]]
Time cost: 0.003608226776123047s [[240, -1]]
Time cost: 0.004375457763671875s [[480, 656]]
Time cost: 0.0036001205444335938s []
Time cost: 0.004243135452270508s [[1040, -1]]
可以发现,对于 240ms 的音频输入,延时稳定在 3-4ms
,只有方案一的一半。
由于抛弃了对 torch
的依赖,整个 ASR 镜像的体积也降到了 342MB
:
REPOSITORY IMAGE ID SIZE
espbot-asr-worker af4487d1b92c 1.59GB
espbot-asr-worker-ten 6960580c9785 342MB
- 接入小智AI
由于 VAD 部分的延时几乎可忽略,整个处理过程的延时基本等于设置的静音时长
:
而 ASR 这里采用流式识别
,延时也几乎可忽略,因此整个方案的延时约等于0。
写在最后
本文分享了一款轻量高效的 VAD
方案 -- TEN-VAD
,并给出了对比实测。
如果对你有帮助,欢迎点赞收藏 备用。
👇 关注猴哥,快速入门AI工具
# AI 工具:
盘点9家免费且靠谱的AI大模型 API,统一封装,任性调用!
免费GPU算力本地跑DeepSeek R1,无惧官方服务繁忙!
# AI应用** :**
弃坑 Coze,我把 Dify 接入了个人微信,AI小助理太强了
我把「FLUX」接入了「小爱」,微信直接出图,告别一切绘画软件!