AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

ASR、VAD 与端点检测

我的学习 →

正在读取学习记录…

ASR 是自动语音识别,把声音变成文字;VAD 是语音活动检测,判断这一小段声音里有没有人在说话;端点检测判断一句话什么时候说完。三者连起来,语音 Agent 才知道何时收集输入、何时开始回答。像面对面交谈时等待对方停顿:停顿太短就抢话,太长则反应慢。

把连续音频切成20毫秒小帧,某帧能量超过阈值就标为有声。毫秒常写作 ms;1000毫秒等于1秒,所以20毫秒是0.02秒。发现有声后,连续两帧静音触发端点。本例有五帧:静音、有声、有声、静音、静音,累计时间是5 × 20 = 100毫秒,也就是0.1秒。读完第五帧后结束收音,再把这段音频交给ASR识别文字。

从输入到可核对结果

示例生成具有标准 WAV 头和16位单声道样本的人工振幅夹具;样本用于能量分段,不含人声词句。wave 读帧,struct 把二进制字节还原为有符号整数。每帧160个样本、采样率8000次每秒,帧长为160÷8000秒,即20毫秒。

energy 取绝对振幅的平均值,大于500时重置静音计数。active 记住前面是否出现有声,避免开场静音就误触发句子结束;只有active后才累计silence。实际VAD还要区分背景噪声与人声,简单能量门限在嘈杂环境里容易误判。Python wave解释WAV读写参数;实时VAD文档说明静音检测与语义端点的取舍。

帧结束时刻平均振幅判断连续静音计数
20ms0开场静音0
40ms1200有声0
60ms1200有声0
80ms0停顿1
100ms0触发端点2
16位WAV / 每帧20ms,然后振幅门限500,然后有声后累计2帧静音,然后100ms端点 → ASR,有声帧 → 静音计数归零
按箭头阅读一次正常路径;下方分支标出当前主题的条件变化。

保存并运行这两个文件

在 PowerShell 创建练习目录:

PowerShell
New-Item -ItemType Directory -Force l9-asr
cd l9-asr

macOS 终端:

Bash
mkdir -p l9-asr
cd l9-asr

把下面第一段完整代码保存为 endpoint_lab.py,第二段保存为 test_endpoint_lab.py,两份文件放在这个目录。

Python
import io
import struct
import wave

SILENCE_FRAMES = 2
RATE = 8000
FRAME_SAMPLES = 160

def fixture_wav():
    # 人工振幅夹具:静音、有声能量、有声能量、静音、静音。
    # 生成真正的16位单声道WAV,数值用于测试分段,不包含人声词句。
    amplitudes = [0, 1200, 1200, 0, 0]
    memory = io.BytesIO()
    with wave.open(memory, "wb") as output:
        output.setparams((1, 2, RATE, 0, "NONE", "not compressed"))
        for amplitude in amplitudes:
            output.writeframes(struct.pack("<h", amplitude) * FRAME_SAMPLES)
    return memory.getvalue()

def endpoint(data, silence_frames):
    if silence_frames < 1:
        raise ValueError("静音帧数至少为1")
    with wave.open(io.BytesIO(data), "rb") as audio:
        if (audio.getnchannels(), audio.getsampwidth(), audio.getframerate()) != (1, 2, RATE):
            raise ValueError("需要8kHz单声道16位音频")
        active = False
        silence = 0
        index = 0
        while raw := audio.readframes(FRAME_SAMPLES):
            samples = struct.unpack("<" + "h" * (len(raw) // 2), raw)
            energy = sum(abs(value) for value in samples) / len(samples)
            # 简单能量VAD:把超过阈值的帧标为有声。
            if energy > 500:
                active, silence = True, 0
            elif active:
                silence += 1
                if silence >= silence_frames:
                    return (index + 1) * 20
            index += 1
    return None

if __name__ == "__main__":
    result = endpoint(fixture_wav(), SILENCE_FRAMES)
    print("音频:5帧,每帧20ms")
    print("端点:" + (str(result) + "ms" if result is not None else "等待更多静音"))
    print("下一步:把分段音频交给ASR识别文字")
Python
import unittest
import endpoint_lab as lab


class MechanismTests(unittest.TestCase):
    # 正常、边界和失败输入都由这里提供,直接运行即可。

    def test_normal(self):
        self.assertEqual(lab.endpoint(lab.fixture_wav(), 2), 100)

    def test_boundary_one_frame(self):
        self.assertEqual(lab.endpoint(lab.fixture_wav(), 1), 80)
        self.assertIsNone(lab.endpoint(lab.fixture_wav(), 3))

    def test_failure(self):
        with self.assertRaises(ValueError):
            lab.endpoint(lab.fixture_wav(), 0)
        with self.assertRaises(Exception):
            lab.endpoint(b"broken wav", 2)


if __name__ == "__main__":
    unittest.main()

打开 PowerShell,逐行输入下列命令。cd 是进入文件夹,dir 是列出文件;确认列表里有刚保存的两个 .py 文件,再运行 Python。

Bash
# 在刚才创建的练习目录检查文件,再运行程序。
ls
python -X utf8 endpoint_lab.py
python -X utf8 -m unittest -v test_endpoint_lab.py

程序输出:

TXT
音频:5帧,每帧20ms
端点:100ms
下一步:把分段音频交给ASR识别文字

测试末尾应显示 Ran 3 tests 与 OK。若找不到文件,检查当前目录与扩展名;若导入失败,确认测试和主程序在同一文件夹。测试描述的三组材料就在上方代码,不需要另外准备输入。

改一处,再看结果

输出中的端点为100ms,来自读过五个真实WAV帧后的计算。最后一行标出后续步骤:分段结束后再把音频传给ASR,得到可供Agent使用的文本。

把 SILENCE_FRAMES = 2 改成 SILENCE_FRAMES = 1,保存并重跑。端点变成80ms,提前20ms。恢复为2后重跑。门限调短能降低等候时间,也更容易把话中停顿当作结尾;调长能容忍停顿,却会拖慢首个回答。

如果始终没有端点,先检查采样率、声道和振幅,再看是否存在足够长的静音。真实录音还需要麦克风权限;上传给识别服务前应取得录音与上传授权,并按音频长度控制费用。ASR返回的文字可能漏词或误识别数字,付款和联系人等关键信息应回读确认。

改动后仍在刚才的 PowerShell 目录运行:

Bash
python -X utf8 endpoint_lab.py

确认上述变化后,把参数恢复成原值,保存并再次执行这条命令。

把机制接回 Agent

实时ASR接入按采集音频、VAD分段、提交片段、接收部分转写、确认最终转写推进。部分转写可能在后续音频到达后修订,Agent应区分临时文字与稳定结果。把端点时间、转写结束时间和回答首音时间分别记录,才能找到慢在收音还是识别。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“ASR、VAD 与端点检测”,哪种做法更稳妥?

对应知识点:L9-asr

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L9-asr

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L9-asr

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L9-asr

图片查看器
100%

文章配图 · 按 Esc 关闭