Voicebox E2E 测试夹具详解:为 test_all_models_e2e.py 准备 reference_voice 的完整指南
本文围绕 backend/tests/fixtures/README.md 展开,讲解 Voicebox 端到端(E2E)全模型测试的夹具准备方法:reference_voice.wav 与 reference_voice.txt 两个文件的格式要求、用途、版本控制注意事项,以及如何用命令行参数指向自定义参考音频。结合 测试主脚本、测试设计文档 与后端画像/音频校验源码,读完后可独立搭好环境跑通一次完整的 10 模型 E2E 测试。
夹具目录的定位:为所有"支持克隆的引擎"提供统一参考音频
Voicebox 的 E2E 测试脚本 test_all_models_e2e.py 会把每一个 TTS 引擎对着冻结的 PyInstaller 二进制(而非开发服务器)逐一跑一遍,记录逐模型的 pass/fail 并输出 JSON + Markdown 报告。其中矩阵中 7 行属于 cloned 类型的引擎(qwen 1.7B/0.6B、luxtts、chatterbox、chatterbox_turbo、tada 1B/3B)共享同一个克隆语音画像,而这个画像完全由夹具目录中的两个文件决定:
reference_voice.wav— 一段干净的语音样本:单声道(mono)、16–24 kHz 采样率、时长约 5–15 秒;reference_voice.txt— 该 WAV 的逐字精确转写文本(单行即可,末尾换行符可有可无)。
这份"一个 WAV + 一份精确文本"的组合,正是语音克隆的标准输入范式:模型用它学习目标音色,而转写文本告诉模型这段音频对应什么内容。测试矩阵与引擎配置的完整设计见 E2E_MODEL_TEST_DESIGN.md,其中"Cloned engines (1, 2, 5, 6, 7, 8, 9) share one profile created once with the reference WAV"一句明确了夹具文件的消费方式。
从后端源码可以印证引擎范围:services/profiles.py 中定义了克隆引擎白名单
CLONING_ENGINES = {"qwen", "luxtts", "chatterbox", "chatterbox_turbo", "tada"}
与 README 中列举的"qwen, luxtts, chatterbox, chatterbox_turbo, tada"完全一致(另外两个引擎 kokoro 与 qwen_custom_voice 使用 preset 画像,不需要参考音频)。
测试脚本如何读取这两个文件
脚本在启动时把默认路径钉死在夹具目录:
FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
命令行参数解析中,参考音频的默认值即指向夹具目录(test_all_models_e2e.py#L430-L439):
--reference-wav PATH 默认:backend/tests/fixtures/reference_voice.wav
--reference-text STR 默认:读取与 WAV 同名的 .txt(即 fixtures/reference_voice.txt)
实际的解析逻辑在 resolve_reference() 函数(test_all_models_e2e.py#L463-L483)中,有三条硬性校验:
- WAV 文件不存在时直接报错,错误信息会引导你"把样本放到
backend/tests/fixtures/reference_voice.wav,或用--reference-wav指定",并指回 fixtures/README.md; - 若未显式传
--reference-text,脚本按wav.with_suffix(".txt")查找同名转写文件,找不到则报Reference transcription not found; - 转写内容
strip()后为空会抛Reference transcription is empty。
注意一个细节:--reference-text 的默认读取位置是与 WAV 同目录的同名 .txt,而不是固定读取 fixtures/reference_voice.txt。因此用 --reference-wav 指向其他路径时,转写文件也要跟着放在旁边(或显式传 --reference-text)。
另外,参考文件只在矩阵中确实包含 cloned 行时才被要求:
needs_reference = any(r.profile_kind == "cloned" for r in rows)
所以如果只用 --only kokoro 跑 preset 引擎,可以完全不准备夹具。
为什么 README 给的规格(16–24 kHz、5–15 秒)是安全的
README 建议的"约 5–15 秒"并非随意取值——它落在后端真正的校验区间之内。上传样本时,画像服务会调用 utils/audio.py 中的 validate_and_load_reference_audio():
def validate_and_load_reference_audio(
audio_path: str,
min_duration: float = 2.0,
max_duration: float = 30.0,
min_rms: float = 0.01,
) -> Tuple[bool, Optional[str], Optional[np.ndarray], Optional[int]]:
即:时长必须在 2–30 秒之间,且经过预处理(削峰归一化,避免"slightly-hot recordings"被误判为削波)后的波形 RMS 必须 ≥ 0.01(否则报 "Audio is too quiet or silent")。README 建议的 5–15 秒留足了上下边界余量,16–24 kHz 单声道则是各克隆引擎(qwen、luxtts、chatterbox 等)通用的干净采样规格。若你的音频不满足这些条件,POST /profiles/{id}/samples 会在服务端直接返回 400。
实际调用链:夹具文件如何变成一次语音克隆
当测试走到 create_cloned_profile()(test_all_models_e2e.py#L242-L259)时,两个夹具文件被消费为两步 HTTP 调用:
第一步创建克隆画像,命中 routes/profiles.py 的 POST /profiles:
POST /profiles
{
"name": "e2e-cloned",
"voice_type": "cloned",
"language": "en"
}
第二步以 multipart 上传参考音频,其中 reference_text 就是 reference_voice.txt 的内容(或 --reference-text 的值):
POST /profiles/{profile_id}/samples (multipart)
file: reference_voice.wav (Content-Type: audio/wav)
reference_text: <reference_voice.txt 的逐字转写>
服务端对应实现见 routes/profiles.py#L153-L191:允许的音频扩展名包括 .wav/.mp3/.m4a/.ogg/.flac/.aac/.webm/.opus,单样本上限 50 MB;随后在 services/profiles.py 的 add_profile_sample() 中执行上文提到的时长与 RMS 校验(通过 asyncio.to_thread 移出事件循环),通过后才落库。这解释了为什么 README 强调转写必须精确:它是克隆链路的核心数据,而不是一段随意的占位文本。
完整运行命令与全部可调参数
README 给出的最小命令——指向不同的参考文件:
python backend/tests/test_all_models_e2e.py \
--reference-wav /path/to/your.wav \
--reference-text "exact transcription here"
结合脚本的 parse_args()(test_all_models_e2e.py#L426-L447),完整参数面如下(默认值均已按当前仓库源码核对):
| 参数 | 默认值 | 说明 |
|---|---|---|
--reference-wav PATH |
backend/tests/fixtures/reference_voice.wav |
克隆引擎使用的参考音频 |
--reference-text STR |
读取 WAV 同目录同名 .txt |
参考音频的逐字转写 |
--only ENGINE[,...] |
无 | 只跑指定引擎,如 kokoro,qwen |
--skip ENGINE[,...] |
无 | 跳过指定引擎 |
--binary PATH |
自动探测 backend/dist/ 下的二进制 |
显式指定 voicebox-server 二进制 |
--skip-build |
关 | 找不到二进制时直接报错而非自动构建 |
--timeout-cached SEC |
180 | 模型已缓存时单模型超时(秒) |
--timeout-download SEC |
1200 | 模型未缓存(需下载)时超时(秒) |
--port N |
自动取空闲端口 | 覆盖自动端口选择 |
--keep-data-dir |
关 | 运行结束后不删除临时数据目录 |
--output-dir PATH |
backend/tests/results/ |
JSON/Markdown 报告输出目录 |
脚本自身不依赖 pytest,只用标准库 + httpx(已在 backend/requirements.txt 中声明 httpx>=0.27.0),保证在干净检出的仓库上可作为单条命令运行。
版本控制提醒:这个目录默认没有被 gitignore
README 明确警告:如果参考音频包含个人声音,应把它排除在版本控制之外——而 backend/tests/fixtures/ 目录默认并不在 .gitignore 中(当前仓库根 .gitignore 确实只忽略了 data/、dist/、*.log 等,没有 fixtures 条目;测试设计文档中的文件布局也注明 results/ 是 gitignored,而夹具文件是 user-provided)。因此建议本地补充忽略规则,例如在本地 .gitignore(或 .git/info/exclude)中加入:
backend/tests/fixtures/reference_voice.*
这样既能保留目录和 README,又不会把你的声音样本提交进仓库。
一次运行中夹具文件的完整生命周期
把 E2E_MODEL_TEST_DESIGN.md 的流程与源码对照,夹具文件只参与其中一步,整体流程是:
- 解析路径:查找(必要时构建)
backend/dist/下的 voicebox-server 二进制; - 解析夹具:
resolve_reference()校验 WAV 存在且转写非空(仅当矩阵含 cloned 行); - 启动服务器:以
--host 127.0.0.1 --port <free> --data-dir <tempdir> --parent-pid <pid>拉起二进制,轮询/health直至 healthy(120 秒上限); - 创建画像:用夹具文件创建 1 个克隆画像 + 2 个 preset 画像(kokoro 的
af_heart、qwen_custom_voice 的Ryan); - 逐行生成:每行先查
GET /models/status判断模型是否已缓存以选择超时,再POST /generate(固定文本 "The quick brown fox jumps over the lazy dog."、seed=42、normalize=true),随后流式消费GET /generate/{id}/status的 SSE 直到 completed/failed/timeout; - 产出报告:写入
backend/tests/results/e2e-<platform>-<arch>-<timestamp>.json与同名.md(含逐模型 PASS/FAIL 表、耗时、音频时长、失败时的服务端日志尾部),同时保存server-<timestamp>.log; - 清理:
try/finally中 SIGTERM(Windows 用taskkill /F /T)杀掉服务器,默认删除临时数据目录(--keep-data-dir可保留)。
判定标准也值得注意:设计文档在 Non-goals 中写明不验证音质(无 WER、无波形对比),"通过" = 端点返回 completed 且产出了非空 WAV;测试脚本也确实会在 audio_bytes == 0 时把该行改判为 failed 并追加 "(audio file is empty)"。
小结
backend/tests/fixtures/README.md 虽然篇幅短小,但定义了 Voicebox 全模型 E2E 测试的数据输入契约:一段 5–15 秒、16–24 kHz 单声道的干净语音 + 一份逐字转写。本文补充了其背后的完整机制——脚本默认路径与三条校验(test_all_models_e2e.py#L463-L483)、服务端 2–30 秒 / RMS≥0.01 的硬约束(utils/audio.py#L323)、克隆画像的两步 HTTP 创建流程(routes/profiles.py#L153-L191)、CLONING_ENGINES 白名单(services/profiles.py),以及完整的 CLI 参数表。按此准备夹具文件并留意目录未被 gitignore 的风险,即可直接驱动一次覆盖 qwen、qwen_custom_voice、luxtts、chatterbox、chatterbox_turbo、tada、kokoro 全部引擎的端到端回归测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00