首页
/ Voicebox E2E 测试夹具详解:为 test_all_models_e2e.py 准备 reference_voice 的完整指南

Voicebox E2E 测试夹具详解:为 test_all_models_e2e.py 准备 reference_voice 的完整指南

2026-09-05 18:18:47作者:虞亚竹Luna

本文围绕 backend/tests/fixtures/README.md 展开,讲解 Voicebox 端到端(E2E)全模型测试的夹具准备方法:reference_voice.wavreference_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)中,有三条硬性校验:

  1. WAV 文件不存在时直接报错,错误信息会引导你"把样本放到 backend/tests/fixtures/reference_voice.wav,或用 --reference-wav 指定",并指回 fixtures/README.md
  2. 若未显式传 --reference-text,脚本按 wav.with_suffix(".txt") 查找同名转写文件,找不到则报 Reference transcription not found
  3. 转写内容 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.pyPOST /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.pyadd_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 的流程与源码对照,夹具文件只参与其中一步,整体流程是:

  1. 解析路径:查找(必要时构建)backend/dist/ 下的 voicebox-server 二进制;
  2. 解析夹具resolve_reference() 校验 WAV 存在且转写非空(仅当矩阵含 cloned 行);
  3. 启动服务器:以 --host 127.0.0.1 --port <free> --data-dir <tempdir> --parent-pid <pid> 拉起二进制,轮询 /health 直至 healthy(120 秒上限);
  4. 创建画像:用夹具文件创建 1 个克隆画像 + 2 个 preset 画像(kokoro 的 af_heart、qwen_custom_voice 的 Ryan);
  5. 逐行生成:每行先查 GET /models/status 判断模型是否已缓存以选择超时,再 POST /generate(固定文本 "The quick brown fox jumps over the lazy dog."、seed=42normalize=true),随后流式消费 GET /generate/{id}/status 的 SSE 直到 completed/failed/timeout;
  6. 产出报告:写入 backend/tests/results/e2e-<platform>-<arch>-<timestamp>.json 与同名 .md(含逐模型 PASS/FAIL 表、耗时、音频时长、失败时的服务端日志尾部),同时保存 server-<timestamp>.log
  7. 清理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 全部引擎的端到端回归测试。

登录后查看全文
热门项目推荐
相关项目推荐