- 共享引擎(app/src/main):GestureEngine 6 手势识别、AudioEngine 三层音频 (ExoPlayer BGM 交叉淡入 / 对白 ducking / SoundPool 低延迟)、Game/GameEngine 状态机接口、MainActivity 全屏封面 + TalkBack - 《声命线》storyline:末世互动广播剧,3 幕 8 抉择点 4 结局 - 《声东击西》reflex:听声辨位反应,12 波战斗 + BGM 三档 BPM 变速 - 《暗室》escape:3x3 网格密室逃脱,钥匙→铁柜→保险丝→电箱→大门谜题链 - 文档:setup(从零搭建)/ architecture(架构)/ engine-api(引擎 API)/ asset-pipeline(资产流水线)/ games/*(三款游戏设计文档) - 资产管线:Qwen3-TTS 对白 + 程序合成音效/BGM + Mureka BGM 的 gen 脚本与台账, 全部 ogg 资产入库可复现 - env.d 环境脚本:android/tts 入库;sfx/mureka 含密钥不入库,仅提交 .example.sh 模板
6.6 KiB
SoundGame — 资产生产流水线
三类音频资产(对白 voice / 音效 sfx / 音乐 bgm)的完整生成与再生成流程。
所有脚本都在 app/src/<flavor>/gen/ 下,可随时复现全部资产。
通用约定:
- GPU 任务必须串行:
flock /home/ubuntu/SoundGame/.gpu.lock <命令>(TTS 与 Stable Audio 3 共用一块 RTX 3090)。 - 环境脚本:
source env.d/tts.sh(HF_HOME + hf-mirror 镜像)、source env.d/android.sh(ffmpeg 在 PATH)、source env.d/sfx.sh(HF_TOKEN, 含密钥不入库,模板见env.d/sfx.example.sh)。 - 中间产物 wav 输出到各 gen 目录或
/tmp/slg/(不入库),最终 ogg 进app/src/<flavor>/assets/audio/{bgm,voice,sfx}/。
1. TTS 对白(Qwen3-TTS CustomVoice)
引擎与用法详见 env.d/tts.md(CustomVoice 预置音色 + instruct 情感指令、
VoiceDesign、预置音色表)。流水线层面:
source env.d/tts.sh
# 《声命线》:48 条,6 条一批;输出 /tmp/slg/wav/,可传文件名参数只重生指定条目
flock .gpu.lock .venv-tts/bin/python app/src/storyline/gen/gen_voice.py [文件id...]
bash app/src/storyline/gen/convert.sh # wav -> ogg 进 assets(含电台带通效果)
# 《声东击西》:33 条,逐条生成;输出 gen/wav_voice/
cd app/src/reflex/gen
flock /home/ubuntu/SoundGame/.gpu.lock /home/ubuntu/SoundGame/.venv-tts/bin/python gen_voice.py
# 《暗室》:41 条,8 条一批,统一峰值归一 0.89;输出 gen/wav/voice/
flock .gpu.lock .venv-tts/bin/python app/src/escape/gen/gen_voice.py
- 每条生成后自动校验:时长 > 0.3s 且峰值 > 0.05,不达标直接 assert 失败。
- 音色分配见各游戏文档(docs/games/*.md):旁白 Uncle_Fu、林晚 Serena、 猎狗 Dylan、小满/广播 Vivian;escape 全员 Uncle_Fu 靠 instruct 区分语气。
- 已知不稳条目:reflex 的
num_6("六"),单独用app/src/reflex/gen/fix_num6.py重试生成(最多 4 次取时长合格的一条)。 - 改台词只需改 gen_voice.py 里的
LINES表再重跑;文件名即游戏代码引用的 id。
2. 音效(Stable Audio 3 + 程序合成)
2a. Stable Audio 3 small-sfx(主力,AI 生成)
source env.d/sfx.sh # HF_TOKEN(gated 模型,直连官方端点,不走 hf-mirror)
cd tools/stable-audio-3
uv run stable-audio --model small-sfx \
-p "footsteps on gravel, slow walking" --duration 3 \
-o /home/ubuntu/SoundGame/tools/samples/sfx_test.wav
- 提示词必须英文;
--duration≤ 120s;--steps默认 8;small 模型峰值显存 约 2.4GB。批量:-p "p1" "p2" --duration 3 5。 - 首次使用需完成 gated 许可 + token 流程,详见
env.d/sfx.md/docs/setup.md。 - 生成后用 ffmpeg 转 ogg 进 assets(规格见下文"资产规格")。
2b. 程序合成(保底/已在线方案,numpy + soundfile,纯 CPU 无需 GPU 锁)
| 游戏 | 脚本 | 输出 |
|---|---|---|
| storyline | app/src/storyline/gen/gen_sfx.py |
/tmp/slg/wav/sfx/(24kHz),convert.sh 转 ogg |
| reflex | app/src/reflex/gen/gen_sfx_bgm.py |
gen/wav_sfx/(44.1kHz),convert_audio.py 转 ogg |
| escape | app/src/escape/gen/gen_sfx.py |
gen/wav/sfx/(44.1kHz),手动 ffmpeg 转 ogg |
当前线上资产全部为程序合成版;Stable Audio 3 环境已就绪,可按 README 待办逐步替换(替换时保持同名同目录,游戏代码零改动)。
3. BGM
3a. Mureka API(storyline / escape)
source env.d/mureka.sh # MUREKA_API_KEY(含密钥不入库,模板 env.d/mureka.example.sh)
python3 tools/mureka_gen.py \
--prompt "dark ambient horror game background music, low drone, ..., seamless loop, instrumental" \
--out tools/mureka_out/storyline # 提交→轮询→下载全部候选 flac
python3 tools/mureka_gen.py --task-id <id> --out tools/mureka_out/xxx # 只轮询已有任务
后处理(两条已上线 BGM 相同参数,记录见 tools/mureka_log.md):
裁剪前 120s → 首尾 3s acrossfade(tri) 无缝循环化 → loudnorm I=-20/TP=-2/LRA=11
→ aresample 44.1kHz 立体声 → libvorbis -q:a 3
成品 117.00s,保持原文件名(dark_drone.ogg / drone.ogg)直接替换 assets。
原程序合成版备份在 tools/bgm_backup/。
成本参考:POST /v1/instrumental/generate 每条任务 $0.03(一次出 2 个
候选),项目预算上限 $0.50,已用 $0.06(2 条任务均一次成功)。每次调用必须
登记到 tools/mureka_log.md。
3b. 程序合成(reflex,有意保留)
app/src/reflex/gen/gen_sfx_bgm.py 的 make_pulse_bgm(bpm, bars=8):
心跳底鼓 + 低音脉冲音序 + 镲噪声 + 不协和 pad,100/128/152 BPM 三档各 8 小节。
玩法要求 BPM 随波次精确切换,AI 生成曲目 BPM 不精确会破坏机制,不要用
Mureka 替换 reflex 的 BGM。
资产规格
| 项 | 标准 |
|---|---|
| 容器/编码 | ogg vorbis(-c:a libvorbis) |
| 码率 | voice/sfx -q:a 4;bgm -q:a 3(控制体积) |
| 采样率 | TTS 源 24kHz;程序合成 reflex/escape 44.1kHz;BGM 统一 aresample 44.1kHz |
| BGM 响度 | loudnorm I=-20 LUFS / TP=-2 / LRA=11 |
| BGM 循环 | 首尾交叉淡化无缝循环(Mureka 版 3s acrossfade;合成版 2s/4s 等功率交叉) |
| 时长校验 | 每条 > 0.3s(数字语音等短条目除外,fix_num6 放宽到 0.3~1.5s 区间判定) |
| 峰值校验 | 生成后 assert 峰值 > 0.05(防静音/失败条目混入) |
| 峰值归一 | storyline 合成 sfx 0.85;escape voice 0.89 / sfx 0.85(环境音 0.55-0.7);reflex voice 0.6 / sfx 0.7-0.95 |
目录约定
app/src/<flavor>/
assets/audio/{bgm,voice,sfx}/*.ogg # 最终资产(入库)
gen/ # 生成脚本(入库)+ 中间 wav/字体(不入库)
tools/
mureka_gen.py / mureka_log.md # Mureka 调用脚本与台账(入库)
mureka_out/ bgm_backup/ samples/ # 原始下载/备份/冒烟输出(不入库)
stable-audio-3/ # SFX 仓库(代码入库,.venv 不入库)
引擎按 id 前缀(bgm/ voice/ sfx/)分发到三个播放层,文件名即代码中的
引用 id;新增资产保持"同名替换、新增先改代码引用"的原则。
封面图
- storyline:
gen/make_cover.py(PIL,字体 /tmp/slg/NotoSansSC.ttf) - escape:
gen/gen_cover.py(PIL,字体 gen/font.otf,不入库需自备) - reflex:封面无独立生成脚本(骨架渐变图 + 后期替换)
- 统一输出
app/src/<flavor>/res/drawable/cover.png,1080x1920,文件名固定。