从 Galgame 中训练想要的角色声音模型

用 Qwen3-TTS 微调,让 AI 用你喜欢的角色声音说话

前言

每个 Galgame 玩家都有自己喜欢的角色。如果能让这个角色"活"过来,用她的声音说任何话,是不是很酷?

本文记录了我从一款 Galgame 中提取角色语音数据,并用 Qwen3-TTS 训练出该角色声音模型的完整过程。最终成果通过AstrBot 插件,可以自动将中文回复翻译成日语,再用训练好的角色声音合成语音。

你需要准备

工具 用途 获取方式
GARbro Galgame 资源解包 GitHub
Python 3.12 运行环境 Anaconda
Qwen3-TTS TTS 模型 pip install qwen-tts
Whisper 语音识别(转录) pip install openai-whisper
CUDA GPU 加速训练/推理 NVIDIA 显卡

第一步:用 GARbro 解包 Galgame

什么是 GARbro?

GARbro 是一个万能的 Galgame 资源浏览器/解包工具,支持几乎所有日系游戏的文件格式(XP3、ARC、HCB 等)。

解包步骤

  1. 安装 GARbro(建议用中文版 garbro-cn

  2. 打开游戏目录下的资源文件(如 .hcb.xp3

  3. 浏览其中的文件结构:

    • voices/ — 语音文件(.ogg 格式)
    • text/ — 剧本文本
    • images/ — CG 图片
  4. 全选 → 右键 → 解包到指定目录

E:\galgame_kb\
├── voices\          ← 所有语音文件
│   ├── voice_00001.ogg
│   ├── voice_00002.ogg
│   └── ...
├── text\            ← 剧本文本
│   ├── all_dialogue.txt
│   └── shinku_dialogue.txt
└── shinku_voice_indices.txt  ← 角色语音索引

提取角色语音

不是所有语音都是你想训练的角色。需要通过游戏脚本或 HCB 文件分析,确定哪些语音属于目标角色。

我的做法是写 Python 脚本分析 HCB 文件(游戏引擎的编译脚本),找到 SHINKU_ 开头的场景标签,定位真红的台词和对应的语音文件索引:

# 伪代码:在 HCB 二进制中搜索角色标签
while i < len(data):
    if data[i:i+7] == b'SHINKU_':
        label = extract_label(data, i)
        shinku_labels.append(label)
    i += 1

最终得到属于目标角色的语音文件。

第二步:准备训练数据

2.1 语音转文字(Whisper)

游戏语音没有附带字幕文本映射,需要识别每条语音说了什么。用 OpenAI 的 Whisper 模型:

import whisper

model = whisper.load_model("medium")

for voice_file in voice_files:
    result = model.transcribe(voice_file, language="ja")
    text = result["text"].strip()
    # 保存到训练 JSONL

输出效果:

voice_00001.ogg → "どうしたの?大丈夫かな?"
voice_00002.ogg → "ねえ、ちょっと待ってよ"
voice_00003.ogg → "えっ、なに?"

2.2 音频格式转换

Qwen3-TTS 要求 24kHz WAV 格式。游戏中的语音通常是 44.1kHz 或 48kHz 的 OGG 格式,需要统一转换:

import torchaudio

wav, sr = torchaudio.load("voice_00001.ogg")
if sr != 24000:
    wav = torchaudio.functional.resample(wav, sr, 24000)
torchaudio.save("utt_0001.wav", wav, 24000)

2.3 选择参考音频

参考音频(ref_audio)是 TTS 时用来"告诉"模型要模仿谁的声音。选择标准:

  • 时长 3~10 秒
  • 声音清晰,没有背景音乐
  • 情绪稳定(不要太激动或太安静)

2.4 组装 JSONL 训练文件

{"audio": "utt_0001.wav", "text": "どうしたの?大丈夫かな?", "ref_audio": "ref_24k.wav"}
{"audio": "utt_0002.wav", "text": "ねえ、ちょっと待ってよ", "ref_audio": "ref_24k.wav"}
{"audio": "utt_0003.wav", "text": "えっ、なに?", "ref_audio": "ref_24k.wav"}

注意:ref_audio 建议所有样本用同一个,这样训练出的声音一致性更好。

第三步:提取 Audio Codes

Qwen3-TTS 的训练不是直接用原始音频,而是先用 Tokenizer 把音频编码成离散的 token(audio_codes):

python prepare_data.py \
    --device cuda:0 \
    --tokenizer_model_path Qwen/Qwen3-TTS-Tokenizer-12Hz \
    --input_jsonl train_raw.jsonl \
    --output_jsonl train_with_codes.jsonl

处理后的数据多了 audio_codes 字段:

{
    "audio": "utt_0001.wav",
    "text": "どうしたの?大丈夫かな?",
    "ref_audio": "ref_24k.wav",
    "audio_codes": [1995, 1159, 355, 22, ...]
}

第四步:微调模型

4.1 下载基础模型

# Tokenizer(编码/解码音频)
modelscope download --model Qwen/Qwen3-TTS-Tokenizer-12Hz

# Base 模型(微调起点)
modelscope download --model Qwen/Qwen3-TTS-12Hz-1.7B-Base

4.2 开始训练

python sft_12hz.py \
    --init_model_path Qwen3-TTS-12Hz-1.7B-Base \
    --output_model_path output \
    --train_jsonl train_with_codes.jsonl \
    --batch_size 2 \
    --lr 1e-5 \
    --num_epochs 10 \
    --speaker_name shinku

4.3 训练过程解读

Epoch 0 | Step 0  | Loss: 13.08   ← 刚开始,模型完全不认识这个声音
Epoch 0 | Step 10 | Loss: 8.49    ← 快速下降,模型在学
Epoch 3 | Step 0  | Loss: 5.97    ← 学到了基本特征
Epoch 7 | Step 0  | Loss: 4.47    ← 声音越来越像
Epoch 9 | Step 30 | Loss: 4.96    ← 接近收敛

关键参数说明:

  • Batch Size = 2:每次喂 2 条数据(受显存限制)
  • Learning Rate = 1e-5:学习率,小一点学得稳
  • 10 Epochs:71 条数据 × 10 遍 = 710 次训练

4.4 过拟合警告

Epoch 不是越多越好。训练太多遍,模型会"背答案"——对训练数据表现很好,但说新内容时声音会变形。

70 条左右的数据,7~15 个 epoch 比较安全。

第五步:部署 API 服务器

写一个 FastAPI 服务器,让 AstrBot 等外部程序可以调用:

from qwen_tts import Qwen3TTSModel

model = Qwen3TTSModel.from_pretrained(
    "output/checkpoint-epoch-9",
    device_map="cuda:0"
)

@app.post("/tts")
async def tts(request: TTSRequest):
    audio_list, sample_rate = model.generate_custom_voice(
        text=request.text,
        speaker="shinku",
        language="japanese",
        non_streaming_mode=True,
    )
    # 返回 WAV 音频

启动:python server.py,监听 0.0.0.0:9880

第六步:接入你的应用

训练好的模型可以接入各种应用,以下是一个实际例子——将角色声音集成到聊天机器人中:

用户 QQ 消息
    ↓
LLM 生成中文回复
    ↓
插件拦截文本
    ↓
调用 LLM 翻译成日语(带强制翻译提示词)
    ↓
调用本地 Qwen3-TTS 服务器(端口 9880)
    ↓
返回日语语音到 QQ

常见问题

Q: 训练数据太少怎么办?

70 条是个最低门槛。如果效果不够好,可以:

  • 用 Whisper 转录更多语音
  • 从游戏其他场景中找目标角色的语音
  • 用 Data Augmentation(变速、加噪等)扩充数据

Q: 训练需要什么硬件?

  • 最低:10G+显存N卡
  • 显存不够:用 0.6B 小模型替代 1.7B

Q: Loss 降到多少算好?

没有绝对标准。一般来说:

  • 从 13+ 降到 5 以下就有效果
  • 3~5 之间通常比较理想
  • 低于 2 可能过拟合了

Q: 可以训练其他语言吗?

可以。Qwen3-TTS 原生支持 10 种语言(中日英韩德法俄葡西意),微调不限语言。

总结

整个流程概括:

GARbro 解包 → 提取角色语音 → Whisper 转文字 → 转 24kHz WAV → 训练 JSONL → 微调 Qwen3-TTS → 部署 API → 集成 AstrBot

数据量不大(70 条),训练时间不长(约 20 分钟),但效果已经能听出角色的音色特征。如果想要更精细的效果,增加训练数据是最有效的方式。


项目地址astrbot_plugin_translate_tts

模型来源Qwen3-TTS

工具GARbroWhisper

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐