摘要(100 字):长录音要切成小段送识别,切点必须落在停顿处------切坏一个词,转写就错一段。本文讲分段器的完整设计:停顿中点切割、段长上下限、连续长讲的兜底切法,以及十七项测试当场咬出的两个真 bug(其中一个会让它在你说话说到第 39 秒时开始乱切)。
列位,上一篇讲了"什么该进门",这篇讲"进门之后怎么切"。
背景是第 7 篇的事故:ASR 在长音频上随机丢尾巴,唯一的根治是把长录音切成小段分别送识别。切段听着简单,切点放哪儿是个正经学问------切在一个词的中间,那个词就废了,转写错一段;切在停顿中间,两边都是纯静音,神不知鬼不觉。
一、切点第一原则:只切停顿中点
分段器盯着实时进来的音频,逐帧做人声判定(能量、过零率、噪声底自适应,都是上一篇的家底)。当连续八百毫秒静音 出现,判定为"说话人停顿了",切点放在这段静音的中点------离两边的语音都有四百毫秒的安全距离。
为什么是八百毫秒?太短,句中换气(两三百毫秒)就把一句话切碎了;太长,段落拖大又回到长音频的老问题。这个值是按真实口述的语速调出来的,而且允许以后再调。
二、段长上下限:三个约束一箭双雕
- 下限三秒:太短的段不单独送,并进下一段。碎片请求又慢又容易错。
- 上限六十秒:一石二鸟------远低于 ASR 静默截断的高发区(约 165 秒),也远低于 API 的 10MB 硬限(约 5 分 27 秒的音频)。
- 连续长讲的兜底 :有人一口气讲两分钟不停怎么办?到上限后,在尾部十秒窗口里找能量最低的那一帧切下去------能量最低点几乎必然是字与词的间隙,虽不如停顿中点完美,但也远好过在元音中间开刀。
三、测试当场咬出两个 bug
分段器写完后抽成了纯逻辑模块(不碰网络、不碰 Tauri),配了十七项测试,用合成语音验证:200 赫兹正弦波当"人声",低幅噪声当"静音",把"说话---停顿---说话"的音频喂进去,断言切点两侧必须都是静音。
第一轮就红了两个:
**Bug 一:噪声底漂移。**噪声底是自适应的------安静时往下跟、嘈杂时往上跟,本意是适应环境变化。但上漂的速率没设防:连续说话时它一路追涨,第 39 秒涨得比人声还高,于是开始把说话误判成停顿------在你话说到一半时切了一刀。这就是那种"短测试全绿、长口述必炸"的定时炸弹,肉眼 review 根本看不出来。修法:只有能量落在"疑似噪声"区间才让噪声底跟随,持续高能量(那是在说话)完全不许带动它。
**Bug 二:强制切段被跳过。**代码结构上,人声帧的处理分支提前返回了,跳过了后面的"段长到顶该强切"检查------于是连续说话时六十秒上限形同虚设,段长一路失控。这种"少了个 else"的错,又是测试抓的。
四、验证的价值
修完十七项全绿,其中最值钱的三条断言是:
- 切点前一百毫秒必须是静音;
- 切点后一百毫秒必须是静音;
- 分块粒度(一百二十八个样本一块喂、还是整段一次喂)不影响任何切点。
第三条专防"实时和离线行为不一致"------录音是流式的,测试常拿整段音频喂,两边行为必须逐样本一致。
经验
"分割点处理好"翻译成工程语言,不是多看两眼代码,是把它钉死在测试里:切点两侧的能量、段长的边界、分块粒度的不变性,一条都不能少。自适应算法(尤其带漂移的)必须配长序列测试,短用例永远是绿的。
本篇是《ChatFly 开发实录》第 8 篇。相关篇目:第 7 篇《ASR 把我的话吃了》(为什么必须切段)、第 6 篇《不说话,它就疯了》(人声判定的由来)。