# lux-tts-java **Repository Path**: wuyuan/lux-tts-java ## Basic Information - **Project Name**: lux-tts-java - **Description**: luxTTS 测试 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-09 - **Last Updated**: 2026-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # lux-tts-java 纯 Java 的 [LuxTTS](https://github.com/YatharthS/LuxTTS) ONNX Runtime 推理:运行时零 PyTorch、零 Python, 从 token 到 48 kHz 语音全在 JVM 内完成(文本音素化除外,见下)。 ✅ 已在 Windows + JDK 25 + CPU 端到端跑通(4 个模型 SHA256 与官方参考哈希一致) ## 架构 ``` tokens (脚本产出) ──┐ ├──► text_encoder.onnx ──► text_condition [1, T, dim] prompt (.lxp) ──────┘ │ 噪声 x + mel prompt + guidance │ fm_decoder.onnx × numSteps(anchor ODE 采样) │ vocos.onnx ──► 48kHz + 24kHz 双路 │ Linkwitz-Riley crossover 合并 ──► 48 kHz float[] ──► WAV ``` 三个 ONNX 模型的输入输出(与 [ningyos/luxtts-onnx](https://github.com/ningyos/luxtts-onnx) 参考实现逐项对齐): | 模型 | 输入 | 输出 | | --- | --- | --- | | `text_encoder.onnx` | `tokens` int64[1,N]、`prompt_tokens` int64[1,M]、`prompt_features_len` int64 标量、`speed` float32 标量 | `text_condition` float32[1,T,dim] | | `fm_decoder.onnx` | `t` float32 标量、`x` float32[1,T,feat_dim]、`text_condition`、`speech_condition` float32[1,T,100]、`guidance_scale` float32 标量 | `v` float32[1,T,feat_dim] | | `vocos.onnx` | `features` float32[1,100,T](latent / 0.1) | `audio_48k`、`audio_24k` | `feat_dim` 从 `fm_decoder.onnx` 的 custom metadata 读取(回退到输入 shape 推断)。 ## 为什么文本音素化在 Python 侧 LuxTTS 的 tokenizer 依赖 jieba(中文分词)+ pypinyin(拼音/变调)+ espeak-ng(英文音素), Java 端无可等价复刻的库。因此采用"预处理分离"架构:**音素化用 Python 做一次,ONNX 推理全部在 Java**。 - 待合成文本 → `scripts/tokenize_text.py` 产出 token id 文件(可缓存) - 参考音频(声音克隆)→ `scripts/prep_prompt.py` 产出 `.lxp` prompt 文件(一次性) ## 快速开始 ### 1. 模型(约 495 MB,工程内已下载好) `models/` 下四个文件:`text_encoder.onnx`(来自 `YatharthS/LuxTTS`)、`fm_decoder.onnx`(同上)、 `vocos.onnx`(来自 `ProgCat/luxtts-onnx`)、`tokens.txt`。需要重新下载时: ```bash curl -L -o models/tokens.txt https://hf-mirror.com/YatharthS/LuxTTS/resolve/main/tokens.txt curl -L -o models/text_encoder.onnx https://hf-mirror.com/YatharthS/LuxTTS/resolve/main/text_encoder.onnx curl -L -o models/fm_decoder.onnx https://hf-mirror.com/YatharthS/LuxTTS/resolve/main/fm_decoder.onnx curl -L -o models/vocos.onnx https://hf-mirror.com/ProgCat/luxtts-onnx/resolve/main/vocos.onnx ``` SHA256(应与官方一致): `text_encoder.onnx=495eca2d...` `fm_decoder.onnx=4510d4f5...` `vocos.onnx=8c00bfef...` `tokens.txt=ce98c1af...` ### 2. Python 预处理环境(uv 管理) 依赖已声明在 `pyproject.toml` / `uv.lock`,`luxtts-onnx` 以本地 vendor 方式引入 (PyPI 无此包,且 piper-phonemize 无 Python 3.12 可用版本,中文路径不依赖它): ```bash uv sync # 按 uv.lock 恢复 .venv(Python 3.12) ``` ```bash # 文本 → token ids(注意脚本名不带 .py 后缀与标准库 tokenize 冲突) uv run python scripts/tokenize_text.py --tokens models/tokens.txt \ --text "你好,世界,这是 Java 集成 LuxTTS 的测试。" --out tokens.txt # 参考音频 → 声音克隆 prompt(.lxp) uv run python scripts/prep_prompt.py --audio ref.wav --transcript "参考音频的转写" \ --tokens models/tokens.txt --out my_voice.lxp ``` ### 3. Java 构建与运行 ```bash mvn -q package -DskipTests java --enable-native-access=ALL-UNNAMED -jar target/lux-tts-java-1.0.0.jar \ models tokens.txt my_voice.lxp out.wav # 可选参数:[numSteps=8] [tShift=0.9] [guidanceScale=3.0] [speed=1.0] ``` 输出 `out.wav`(48 kHz / 16-bit / 单声道)。 `--enable-native-access=ALL-UNNAMED` 仅 JDK 24+ 需要(消除 onnxruntime 原生库警告)。 ## Java API 示例 ```java try (LuxTTSOnnx tts = new LuxTTSOnnx("models", 4)) { // 4 个 CPU 线程 long[] tokens = Main.readTokensFile("tokens.txt"); Prompt prompt; try (FileInputStream in = new FileInputStream("my_voice.lxp")) { prompt = Prompt.read(in); } LuxTTSOnnx.Options opt = new LuxTTSOnnx.Options(); opt.numSteps = 8; opt.guidanceScale = 3.0f; float[] audio48k = tts.generate(tokens, prompt, opt); // [-1, 1] float WavWriter.write("out.wav", audio48k, 48000); } ``` ## 端到端验证(2026-09-09,本机) - 中文音素化:`"你好,世界,这是 Java 集成 LuxTTS 的测试。"` → 27 个 token id - prompt:3.0 s 参考音频 → 282 帧 mel(合成 FM 信号,仅用于管线验证) - 推理:`feat_dim=100`(metadata 读取)→ 8 步 ODE → 输出 2.78 s / 48 kHz / 16-bit WAV - 性能:CPU 4 线程,加载 46.9 s,生成 14.7 s(RTF≈5.3;与参考实现 CPU 量级一致,GPU 可到 0.08x) - 波形:峰值 1.0(clamp 生效)、RMS 0.213、活跃帧 99.2% > 注:验证用的参考音频是合成信号,生成的"语音"非自然人声,仅证明链路正确。 > 真实音色请用真人参考音频(5-15 秒清晰人声)重新生成 prompt。 ## 实现要点 - **ODE 采样**:anchor 更新 `x1=x+(1-t)v`、`x0=x-tv`,`x=(1-t_next)x0+t_next x1`,末步取 `x1`。 - **t_shift 时间表**:`t' = t_shift·t / (1+(t_shift-1)t)`。 - **空输出保护**:`MIN_GEN_FRAMES=16`,文本过短时对 `text_condition` edge-padding。 - **24k→48k 重采样**:频域理想插值(频谱搬移 + `N_out/N_in` 增益补偿), 优于线性插值(后者 6 kHz 处有 ~19% 幅度误差)。 - **crossover**:4 阶 Linkwitz-Riley(`|H|²=1/(1+(f/fc)^8)`),FFT 域零相位滤波。 - **RNG**:`java.util.Random` 高斯噪声,与 numpy 分布一致但序列不同(不影响听感)。 - **volume**:clamp [-1,1] 后 RMS 匹配(只放大不缩小)。 ## 自检(不需要模型) ```bash java -cp target/lux-tts-java-1.0.0.jar io.luxtts.SelfTest ``` 验证时间表公式、crossover/重采样数学(3 kHz 重建误差 <0.004)、prompt 往返、WAV 头。 ## 目录结构 ``` lux-tts-java/ ├── pom.xml / pyproject.toml / uv.lock ├── models/ # ONNX 模型(4 个文件) ├── scripts/ # tokenize_text.py / prep_prompt.py / _patch_vendor.py ├── vendor/luxtts-onnx # 本地 vendor 参考实现(已裁剪依赖,供预处理复用) ├── src/main/java/io/luxtts/ │ ├── LuxTTSOnnx.java # 推理主类(text_encoder + ODE + vocos) │ ├── Crossover.java # 频域重采样 + Linkwitz-Riley │ ├── Prompt.java # .lxp 格式读写 │ ├── WavWriter.java # 48kHz WAV 写出 │ ├── Main.java # CLI │ └── SelfTest.java # 数值自检 └── out.wav / tokens.txt / test_prompt.lxp # 端到端验证产物 ``` ## 依赖 - `com.microsoft.onnxruntime:onnxruntime:1.17.0`(自带 win/linux/mac 原生库) - `com.github.wendykierp:JTransforms:3.1`(任意长度实数 FFT) - JDK 17+(JDK 25 验证通过) - Python 3.12 + uv(仅预处理) ## License Apache-2.0。推理逻辑移植自 [ningyos/luxtts-onnx](https://github.com/ningyos/luxtts-onnx) (Apache-2.0,源自 LuxTTS / ZipVoice / Vocos)。