Appearance
音频回放与质量回归
“这次识别通过了”不是一个足够精确的结论。流式语音服务至少同时包含协议行为、文本质量、角色质量、目标身份质量、时延、并发和部署兼容七个维度。项目实践中把它们拆开验证,才能知道一次修复真正改善了什么。
七个验收维度
| 维度 | 最小证据 | 常见误判 |
|---|---|---|
| 协议完整性 | 状态码、事件顺序、Final 冻结、terminal、正常关闭 | 有文字就算接口通过 |
| 转写质量 | 原始音频、人工参考、逐句文本、漏字和串音标注 | Preview/Final 存在就算准确 |
| 角色质量 | 人工身份标注、角色数、切换点、重叠区表现 | 模型槽位数量等于真实身份 |
| 目标身份质量 | 纯目标说话人、纯非目标说话人、交替、重叠、短句和句首句尾样本 | 两个负例通过即可外推 |
| 时延 | 真实末音到 VAD、提交、Preview、Final、terminal 的时间线 | 从录音开始计时一个总耗时 |
| 并发性能 | 固定配置下的吞吐、队列、资源和错误统计 | 网关并发数等于每个模型的容量 |
| 部署兼容 | 目标硬件、驱动、固件、系统、容器运行时和设备映射 | 自有机器通过等于目标环境通过 |
证据包应该包含什么
一次可复核的回归不需要复制所有原音,但必须能让别人重建结论。推荐按以下结构组织:
text
case/
├── manifest.json # 用例、参数、环境和源码提交
├── input.sha256 # 原始 PCM 或输入文件摘要
├── expected.json # 人工参考、角色标注和通过标准
├── events.ndjson # 原始事件,保留顺序和时间戳
├── metrics.json # 时延、错误、资源和并发统计
└── conclusion.md # 通过项、失败项和边界manifest.json 至少记录四类信息:
json
{
"case": "交替说话与重叠语音",
"source_commit": "<commit>",
"environment": "<hardware-driver-os-runtime>",
"start": {
"sample_rate": 16000,
"channels": 1,
"processor_mode": "role"
},
"acceptance": ["protocol", "text", "role", "latency"]
}摘要用于绑定证据,不用于取代证据。输入文件、镜像、交付 ZIP 和报告都应能通过 SHA-256 回到当次源码和配置。
回放脚本也会制造误差
逐帧发送后固定 sleep 会累积读取、编码和发送开销,长录音因此越来越慢。应使用单调时钟和样本进度计算绝对截止时间:due = started + samples_sent / sample_rate,每次只等待到 due。赶不上时记录调度偏差,不悄悄补静音或改变输入节奏。
比较两版服务时,固定 PCM、启动参数、发送节奏、入口和超时规则。浏览器音频预处理、重采样、量化和帧拼接变化,都可能让“同一段声音”变成不同字节。前端优化帧长或发送方式后,还要核对首尾样本与最终质量,不能以音频播放时长接近作为输入一致证明。
下面的片段使用绝对时间发送等长 PCM 帧。frame_samples 是每声道样本数,不是字节数;不同格式要按声道和位宽换算。发送本身耗时计入时间线,落后时立即发送并记录偏差,不在每帧后额外追加固定 sleep。
python
import asyncio
import time
async def replay(frames, send_pcm, sample_rate, frame_samples):
started = time.monotonic()
sent_samples = 0
for frame in frames:
due = started + sent_samples / sample_rate
await asyncio.sleep(max(0.0, due - time.monotonic()))
await send_pcm(frame)
sent_samples += frame_samples(frame)四层音频与输出对照
漏字首先定位在采集 PCM、接收 PCM、分段 PCM、模型输入哪一层。对同格式同范围的连续音频比较字节数和摘要;对子段核对区间与覆盖关系;模型输入包含参考或 padding 时按合同分解,不能直接和原音整体比较。
bash
wc -c capture.pcm received.pcm
shasum -a 256 capture.pcm received.pcm
cmp capture.pcm received.pcm接收完整但模型尾帧缺失,查提交与收尾;模型输入完整但原始解码缺字,查上下文、切分和模型质量;原始事件有字而 UI 缺字,查合并与渲染。保存 raw text 与规范化文本,避免重复折叠或字符串替换隐藏模型错误。
文本使用人工参考统计 CER/WER,并单列删除、插入和替换;说话人使用对应时间区间的身份标注,检查换人、同一人返回与重叠。目标模式分别统计目标内容保留和非目标误输出。若采用 DER,还要固定边界容忍、重叠计分和身份映射规则,避免不同口径的数字互相比较。
证明实际执行了目标路径
页面显示模式开启、日志中的布尔字段为 true,都不一定证明目标识别已执行。应核对任务路由和模型实际输入,例如登记参考是否进入预期输入、目标参数是否在最终任务保留、失败恢复是否绕过门控。中间层清空参数后仍回报原始模式,会产生虚假的验收结论。
普通识别、角色分离和目标识别分别留存证据;新增成功样本不能覆盖旧版本的失败记录。报告中写明构建身份和环境,避免把不同版本的短测、长测和并发结果拼成一次“全通过”。
用例矩阵要覆盖变化点
不要只用一条“正常会议录音”。至少覆盖以下组合:
- 纯目标说话人、纯非目标说话人、目标说话人与非目标说话人交替。
- 重叠说话、短句、句首弱音、句尾弱音和长停顿。
- 单人、多人、角色切换密集和跨窗口连续会话。
- 正常结束、调用方停止、服务端 EOF、网络中断和重启恢复。
- 冷启动、热态、低并发和接近容量上限的并发。
每增加一种模型、VAD、门控或协议映射,就要判断它影响了哪些矩阵项。没有受影响项的回归记录,不能支撑“整体通过”。
报告结论如何写
报告把结论分成三种状态,避免把证据不足写成失败或通过:
- 通过:样本、环境和验收条件均满足,且没有观察到违反项。
- 未通过:出现了明确违反项,附原始事件、音频片段或日志位置。
- 未验证:缺少目标环境、人工标注或触发条件,不能据此关闭问题。
例如,“协议通过,角色质量未验证”比“本轮通过”更准确;“测试入口未复现”只能说明当前样本和环境未复现,不能替代目标环境上的触发条件回归。
最小回归流程
- 固定源码提交、模型摘要、启动参数和输入摘要。
- 先跑能直接覆盖问题的小样本,确认修复方向。
- 按角色、时延、协议和异常收尾扩展矩阵。
- 保留原始事件和失败证据,不用人工整理后的截图替代。
- 将每个维度分别判定,再给出范围明确的总评。
- 将断言绑定到模型、运行库、配置和实际执行入口。
小结
输入一致、模型路径一致、指标口径一致,才构成有效的前后对照。协议完成、文字准确、角色稳定和目标过滤分别验证,优化哪一层就用对应断言证明。
