714 lines
24 KiB
Markdown
714 lines
24 KiB
Markdown
# 《林夏》音频素材本地制作执行文档
|
||
|
||
> **前置文档**:`林夏.md`(游戏设计)、`SOP_AI音频生产.md`(生产规范)
|
||
> **执行环境**:本机 WSL2 · RTX 5090 32GB · CosyVoice2-0.5B · FFmpeg 7.0
|
||
> **目标**:按 SOP 规范在本地生产全部音频素材
|
||
|
||
---
|
||
|
||
## 0. 本机资源清单
|
||
|
||
| 资源 | 路径 / 状态 | 用途 |
|
||
|---|---|---|
|
||
| CosyVoice 2.0 (0.5B) | `/home/xsl/tools/CosyVoice` · conda env: `cosyvoice` | TTS 主力(替代 SOP 中 MiniMax/豆包方案) |
|
||
| FFmpeg 7.0 | `/usr/local/bin/ffmpeg` | 音频格式转换、响度归一化、听筒效果 |
|
||
| GPU | RTX 5090 32GB | 跑 CosyVoice 推理 |
|
||
| MuseTalk | `/home/xsl/work/MuseTalk` | **注意:这是唇形同步工具,不是 TTS** |
|
||
|
||
> **SOP 中的 MiniMax / 豆包 API 全部替换为本地 CosyVoice**。音乐(Suno)、音效(ElevenLabs SFX)、环境采样(Freesound)仍走云端。
|
||
|
||
---
|
||
|
||
## 1. 环境部署
|
||
|
||
### 1.1 创建项目目录结构
|
||
|
||
```bash
|
||
cd /home/xsl/blind
|
||
mkdir -p audio/{00_raw/{tts,music,sfx,ambience,ringtone},01_processed/{tts,music,sfx,ambience,ringtone},02_final/{tts,music,sfx,ambience,ringtone},03_qa_reports,99_legal}
|
||
mkdir -p audio/00_raw/tts/prompt_ref # 存放角色参考音频
|
||
```
|
||
|
||
### 1.2 验证 CosyVoice 环境
|
||
|
||
```bash
|
||
# 激活 conda 环境
|
||
conda activate cosyvoice
|
||
|
||
# 验证依赖
|
||
cd /home/xsl/tools/CosyVoice
|
||
python -c "
|
||
import sys
|
||
sys.path.append('third_party/Matcha-TTS')
|
||
from cosyvoice.cli.cosyvoice import CosyVoice2
|
||
model = CosyVoice2('pretrained_models/CosyVoice2-0.5B')
|
||
print('可用预训练音色:', model.list_available_spks())
|
||
print('采样率:', model.sample_rate)
|
||
print('CosyVoice2 加载成功')
|
||
"
|
||
```
|
||
|
||
如果报错 `ModuleNotFoundError`,先装依赖:
|
||
```bash
|
||
conda activate cosyvoice
|
||
cd /home/xsl/tools/CosyVoice
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### 1.3 启动 CosyVoice WebUI
|
||
|
||
```bash
|
||
conda activate cosyvoice
|
||
cd /home/xsl/tools/CosyVoice
|
||
python webui.py --port 8000
|
||
```
|
||
|
||
浏览器访问 `http://localhost:8000`。WebUI 提供 4 种推理模式:
|
||
|
||
| 模式 | 说明 | 本项目用途 |
|
||
|---|---|---|
|
||
| **预训练音色** | 从内置音色列表选择 | 快速试听、系统 TTS |
|
||
| **3s极速复刻** | 提供 3-30s 参考音频 + 对应文字 | **主力模式**:为每个角色准备参考音频 |
|
||
| **跨语种复刻** | 跨语言克隆 | 暂不需要 |
|
||
| **自然语言控制** | 用文字描述控制语气风格 | CosyVoice2 不支持此模式,改用 instruct2 API |
|
||
|
||
### 1.4 验证 FFmpeg
|
||
|
||
```bash
|
||
ffmpeg -version | head -1
|
||
# 预期输出: ffmpeg version 7.0.2-static
|
||
|
||
# 测试 loudnorm 滤镜
|
||
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -af "loudnorm=I=-16:TP=-1:LRA=11" -y /tmp/test_loudnorm.wav
|
||
echo "FFmpeg loudnorm 可用"
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 角色参考音频准备(关键步骤)
|
||
|
||
CosyVoice2 的"3s极速复刻"需要**每个角色一段 5-20 秒的参考音频**。参考音频的音色决定了最终 TTS 的音色。
|
||
|
||
### 2.1 参考音频来源方案
|
||
|
||
| 角色 | 音色要求 | 参考音频获取方案 |
|
||
|---|---|---|
|
||
| `mom` 妈妈 | 50 岁,温柔克制 | 方案 A:找一位 45-55 岁女性朋友录 15 秒朗读(最佳);方案 B:从影视作品中截取合适片段 |
|
||
| `azhe` 阿哲 | 27 岁,理性偏冷 | 从播客/有声书截取年轻男声冷淡片段 |
|
||
| `xiaomei` 小美 | 24 岁,活泼 | 找年轻女性朋友录制,或从 vlog 截取 |
|
||
| `zhounan` 周南 | 26 岁,温和拘谨 | 播客/有声书截取温和男声 |
|
||
| `hr` HR 王姐 | 38 岁,知性 | 新闻女主播或播客截取 |
|
||
| `anan` 安安 | 23 岁,可爱 | 年轻女性活泼语调截取 |
|
||
| `dorm-a/b/c` | 三个不同年轻女声 | 需要 3 段风格不同的女声参考 |
|
||
| `delivery` 外卖小哥 | 30 岁,北方腔 | 短视频/影视截取 |
|
||
| `system` 系统 | 中性 AI 女声 | 用 CosyVoice 预训练音色即可 |
|
||
| `self-3y` 3年前自己 | 同 xiaomei 基底 | 复用 xiaomei 参考音频,调速度和音调 |
|
||
|
||
### 2.2 参考音频制作规格
|
||
|
||
- **时长**:5-20 秒(推荐 10 秒左右)
|
||
- **格式**:WAV,16kHz 以上采样率
|
||
- **内容**:完整的中文句子(需同时记录对应文字)
|
||
- **质量**:干声为主,避免底噪、混响、BGM
|
||
|
||
存放路径:`audio/00_raw/tts/prompt_ref/{角色代号}_ref.wav`
|
||
|
||
```bash
|
||
# 如果参考音频不是 wav 或采样率不对,用 FFmpeg 转换
|
||
ffmpeg -i input.mp3 -ar 16000 -ac 1 audio/00_raw/tts/prompt_ref/mom_ref.wav
|
||
```
|
||
|
||
### 2.3 创建参考音频记录表
|
||
|
||
在 `audio/00_raw/tts/prompt_ref/` 下创建 `ref_manifest.txt`,记录每条参考音频对应的文字:
|
||
|
||
```
|
||
mom_ref.wav | 今天天气不错,妈妈给你做了红烧排骨,等你回来吃饭。
|
||
azhe_ref.wav | 好的,我知道了,那件事明天再说吧。
|
||
xiaomei_ref.wav | 哎你猜怎么着,今天路上遇到一个超级搞笑的事!
|
||
zhounan_ref.wav | 嗯,其实我也不太确定,就是觉得应该跟你说一声。
|
||
hr_ref.wav | 好的,那我们下周三前把流程走完,有什么问题随时联系我。
|
||
anan_ref.wav | 天呐天呐你快来!这边超级好玩的!
|
||
dorm_a_ref.wav | 嘿你们看看我,今天升职了!请大家吃饭!
|
||
dorm_b_ref.wav | 恭喜恭喜啊,太厉害了,我就知道你行的。
|
||
dorm_c_ref.wav | 嗯,不错,对了夏夏怎么不说话啊。
|
||
delivery_ref.wav | 你好你的外卖到了,放门口了啊,麻辣烫注意别洒了。
|
||
```
|
||
|
||
---
|
||
|
||
## 3. TTS 生产流程
|
||
|
||
### 3.1 用 WebUI 逐条生产(推荐新手)
|
||
|
||
1. 浏览器打开 `http://localhost:8000`
|
||
2. 选择模式:**3s极速复刻**
|
||
3. 上传对应角色的参考音频
|
||
4. 在"prompt文本"中输入参考音频对应的文字
|
||
5. 在"合成文本"中输入要合成的台词
|
||
6. 调整速度(对应 SOP §4.2 各角色 Speed 值)
|
||
7. 点击"生成音频"
|
||
8. 试听满意后,右键保存音频
|
||
|
||
### 3.2 用 Python 脚本批量生产(推荐熟练后)
|
||
|
||
创建批量生产脚本 `audio/batch_tts.py`:
|
||
|
||
```python
|
||
#!/usr/bin/env python3
|
||
"""
|
||
《林夏》TTS 批量生产脚本
|
||
用法: conda activate cosyvoice && python audio/batch_tts.py
|
||
"""
|
||
import os, sys, json, torch, torchaudio
|
||
|
||
# 设置路径
|
||
COSYVOICE_DIR = "/home/xsl/tools/CosyVoice"
|
||
PROJECT_DIR = "/home/xsl/blind"
|
||
sys.path.insert(0, COSYVOICE_DIR)
|
||
sys.path.insert(0, f"{COSYVOICE_DIR}/third_party/Matcha-TTS")
|
||
|
||
from cosyvoice.cli.cosyvoice import CosyVoice2
|
||
from cosyvoice.utils.file_utils import load_wav
|
||
|
||
# 加载模型
|
||
print("正在加载 CosyVoice2 模型...")
|
||
model = CosyVoice2(f"{COSYVOICE_DIR}/pretrained_models/CosyVoice2-0.5B")
|
||
print(f"模型加载完成,采样率: {model.sample_rate}")
|
||
|
||
# ============================================================
|
||
# 角色配置 — 按 SOP §4.2 配置卡填写
|
||
# speed: 对应 SOP 中的 Speed 值
|
||
# ref_wav: 参考音频路径
|
||
# ref_text: 参考音频对应文字
|
||
# ============================================================
|
||
ROLES = {
|
||
"mom": {
|
||
"speed": 0.92,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/mom_ref.wav",
|
||
"ref_text": "今天天气不错,妈妈给你做了红烧排骨,等你回来吃饭。",
|
||
},
|
||
"azhe": {
|
||
"speed": 1.05,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/azhe_ref.wav",
|
||
"ref_text": "好的,我知道了,那件事明天再说吧。",
|
||
},
|
||
"xiaomei": {
|
||
"speed": 1.10,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/xiaomei_ref.wav",
|
||
"ref_text": "哎你猜怎么着,今天路上遇到一个超级搞笑的事!",
|
||
},
|
||
"zhounan": {
|
||
"speed": 0.95,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/zhounan_ref.wav",
|
||
"ref_text": "嗯,其实我也不太确定,就是觉得应该跟你说一声。",
|
||
},
|
||
"hr": {
|
||
"speed": 1.00,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/hr_ref.wav",
|
||
"ref_text": "好的,那我们下周三前把流程走完,有什么问题随时联系我。",
|
||
},
|
||
"anan": {
|
||
"speed": 1.15,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/anan_ref.wav",
|
||
"ref_text": "天呐天呐你快来!这边超级好玩的!",
|
||
},
|
||
"delivery": {
|
||
"speed": 1.20,
|
||
"ref_wav": f"{PROJECT_DIR}/audio/00_raw/tts/prompt_ref/delivery_ref.wav",
|
||
"ref_text": "你好你的外卖到了,放门口了啊,麻辣烫注意别洒了。",
|
||
},
|
||
# system 用预训练音色,不走 zero-shot
|
||
}
|
||
|
||
# ============================================================
|
||
# 台词表 — 从林夏.md §3 / §5 抄写
|
||
# 每条格式: (文件名前缀, 角色, 台词, speed_override=None)
|
||
# ============================================================
|
||
LINES = [
|
||
# === 第 01 条 · 21:03 妈妈电话 ===
|
||
("lv11_2103_mom_call_01", "mom",
|
||
"今晚煮了你爱吃的排骨,给你拍张照啊。下周五回来一趟,你高中物理老师的儿子,挺好的。"),
|
||
|
||
# === 第 02 条 · 21:15 小美三连 ===
|
||
("lv11_2115_xiaomei_voice_01", "xiaomei", "在吗"),
|
||
("lv11_2115_xiaomei_voice_02", "xiaomei", "你被甩了我都听说了"),
|
||
("lv11_2115_xiaomei_voice_03", "xiaomei", "明天晚上喝酒,海底捞旁边那家,不许放鸽子"),
|
||
|
||
# === 第 03 条 · 21:20 外卖小哥 ===
|
||
("lv11_2120_delivery_call_01", "delivery",
|
||
"你好你的麻辣烫到了,放门口可以吗?"),
|
||
|
||
# === 第 04 条 · 21:30 阿哲语音 ===
|
||
("lv11_2130_azhe_voice_01", "azhe",
|
||
"嗯……夏夏。我那个AirPods充电盒,好像落你抽屉里了。你方便的时候帮我寄一下吗,工位还要用。地址我等下发你。"),
|
||
|
||
# === 第 05 条 · 21:45 HR 王姐 ===
|
||
("lv11_2145_hr_voice_01", "hr",
|
||
"夏夏啊,离职流程你下周三之前办完哈,公积金那边记得跟进。辛苦啦,外面有合适的工作我帮你留意。"),
|
||
|
||
# === 第 06 条 · 22:00 安安 ===
|
||
("lv11_2200_anan_voice_01", "anan",
|
||
"夏夏!我在helens!要不要过来!我请客!"),
|
||
|
||
# === 第 10 条 · 23:00 妈妈文字 ===
|
||
("lv11_2300_mom_text_01", "mom", "睡了吗?"),
|
||
|
||
# === 第 11 条 · 23:15 阿哲电话 ===
|
||
("lv11_2315_azhe_call_01", "azhe",
|
||
"我能过去拿吗?就5分钟"),
|
||
|
||
# === 第 12 条 · 23:30 周南 ===
|
||
("lv11_2330_zhounan_voice_01", "zhounan",
|
||
"林夏,是我,周南。你应该不记得我了吧,初三坐你后桌那个"),
|
||
|
||
# === 第 14 条 · 00:15 妈妈电话 ===
|
||
("lv11_0015_mom_call_01", "mom",
|
||
"夏夏,你不接电话我有点担心。妈妈知道你工作忙,你早点睡。"),
|
||
|
||
# ... 更多台词按需添加
|
||
# 长戏(妈妈 4'17"、小美醉语音、周南 4 段)需分段处理,见 §3.3
|
||
]
|
||
|
||
|
||
def synthesize_line(filename, role, text, speed_override=None):
|
||
"""合成单条台词"""
|
||
cfg = ROLES[role]
|
||
speed = speed_override or cfg["speed"]
|
||
ref_wav = cfg["ref_wav"]
|
||
ref_text = cfg["ref_text"]
|
||
|
||
if not os.path.exists(ref_wav):
|
||
print(f" [跳过] 参考音频不存在: {ref_wav}")
|
||
return
|
||
|
||
# 加载参考音频
|
||
prompt_speech = load_wav(ref_wav, 16000)
|
||
# 裁剪到合理长度
|
||
if prompt_speech.shape[1] > 16000 * 30:
|
||
prompt_speech = prompt_speech[:, :16000 * 30]
|
||
|
||
output_path = f"{PROJECT_DIR}/audio/00_raw/tts/{filename}_v1.wav"
|
||
print(f" 合成中: {filename} [{role}] speed={speed}")
|
||
print(f" 台词: {text[:50]}...")
|
||
|
||
speeches = []
|
||
for chunk in model.inference_zero_shot(
|
||
tts_text=text,
|
||
prompt_text=ref_text,
|
||
prompt_speech_16k=prompt_speech,
|
||
stream=False,
|
||
speed=speed,
|
||
):
|
||
speeches.append(chunk["tts_speech"])
|
||
|
||
if speeches:
|
||
full_speech = torch.concat(speeches, dim=1)
|
||
torchaudio.save(output_path, full_speech, model.sample_rate)
|
||
duration = full_speech.shape[1] / model.sample_rate
|
||
print(f" 已保存: {output_path} ({duration:.1f}s)")
|
||
else:
|
||
print(f" [失败] 未生成音频: {filename}")
|
||
|
||
|
||
def main():
|
||
print(f"\n{'='*60}")
|
||
print(f" 《林夏》TTS 批量生产")
|
||
print(f" 共 {len(LINES)} 条台词")
|
||
print(f"{'='*60}\n")
|
||
|
||
for i, line in enumerate(LINES):
|
||
filename = line[0]
|
||
role = line[1]
|
||
text = line[2]
|
||
speed_override = line[3] if len(line) > 3 else None
|
||
|
||
print(f"\n[{i+1}/{len(LINES)}] {filename}")
|
||
synthesize_line(filename, role, text, speed_override)
|
||
|
||
print(f"\n{'='*60}")
|
||
print(" 批量合成完成!请到 audio/00_raw/tts/ 检查结果")
|
||
print(f"{'='*60}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
### 3.3 长戏分段处理
|
||
|
||
SOP §5.3 规定:**超过 30 秒的台词必须分段录制**。
|
||
|
||
**妈妈 4'17" 长语音(第 16 条)分 8 段**:
|
||
|
||
| 段号 | 文件名 | 台词 | 情绪提示 |
|
||
|---|---|---|---|
|
||
| 1 | `lv11_0200_mom_voice_01_seg1` | "夏夏。妈妈知道你今天电话不接。妈妈不催你,就是想跟你说一件事。" | gentle, hesitant |
|
||
| 2 | `lv11_0200_mom_voice_01_seg2` | "你24了对吧。妈妈24那年——你应该没听我说过——" | gentle, nostalgic |
|
||
| 3 | `lv11_0200_mom_voice_01_seg3` | "那时候妈妈在广州,给一个香港老板做秘书,1995年。那年我也被分手过。也被开除过。一模一样的春天。" | gentle, sad but holding back |
|
||
| 4 | `lv11_0200_mom_voice_01_seg4` | "那年我哭得特别难看。后来呢?后来就没什么后来了。我回了老家,认识了你爸,生了你。" | gentle, releasing |
|
||
| 5 | `lv11_0200_mom_voice_01_seg5` | "我跟你说这个不是想跟你说什么都会过去——我想跟你说,那年夏天我没死,是因为我外婆每天晚上都给我打一个电话。" | gentle, emotional |
|
||
| 6 | `lv11_0200_mom_voice_01_seg6` | "她不说什么,就问我今天吃饭了没。" | gentle, peaceful |
|
||
| 7 | `lv11_0200_mom_voice_01_seg7` | "我现在打给你,你不接也没事。妈妈给你发个语音,你什么时候想听就听。你不用回我。" | gentle, tender |
|
||
| 8 | `lv11_0200_mom_voice_01_seg8` | "你今天接了好多电话吧。妈妈不打扰你了。晚安。" | gentle, almost smiling |
|
||
|
||
每段单独合成后用 FFmpeg 拼接(见 §4.3)。
|
||
|
||
**小美醉语音(第 09 条)分 4 段**,speed 调低到 0.95,每段间加长停顿。
|
||
|
||
**周南 4 段长讲述(第 13 条)** 每段独立合成,段间留 800ms 静音。
|
||
|
||
### 3.4 系统 TTS
|
||
|
||
系统提示音(播报未读消息、念选项等)用 CosyVoice **预训练音色**模式:
|
||
|
||
1. WebUI 选"预训练音色"
|
||
2. 从下拉菜单选一个清晰的中性女声
|
||
3. 合成所有系统语句
|
||
|
||
系统语句示例:
|
||
- "阿哲,发来1条语音。8秒。"
|
||
- "是否回复?"
|
||
- "一,好,明天寄。二,你怎么不自己来拿。三,你那边都好吗。四,什么都不说。"
|
||
- "妈妈3条未读、小美1条已读、阿哲1条未播放"
|
||
|
||
### 3.5 self-3y 三版预录
|
||
|
||
使用与 `xiaomei` 同基底参考音频,但调整 speed 为 +0.05(即 1.15):
|
||
|
||
| 版本 | 文件名 | 内容要求 | 情绪 |
|
||
|---|---|---|---|
|
||
| 明朗版 | `lv11_0030_self-3y_voice_01` | 3 年前刚来北京,对一切新鲜 | 兴奋带紧张 |
|
||
| 迷茫版 | `lv11_0030_self-3y_voice_02` | 3 年前已经累了 | 假装坚强 |
|
||
| 温柔版 | `lv11_0030_self-3y_voice_03` | 写给未来的自己 | 平静有力 |
|
||
|
||
> 台词需要编剧写好,每版约 50 秒。
|
||
|
||
---
|
||
|
||
## 4. 后期处理(FFmpeg)
|
||
|
||
### 4.1 单条 TTS 标准处理链
|
||
|
||
```bash
|
||
# 变量
|
||
INPUT="audio/00_raw/tts/lv11_2103_mom_call_01_v1.wav"
|
||
OUTPUT="audio/01_processed/tts/lv11_2103_mom_call_01_v1.wav"
|
||
|
||
# 完整处理链(一条命令):
|
||
# [1] 高通 80Hz 去低频噪
|
||
# [2] 压缩器 4:1
|
||
# [3] 响度归一化到 -16 LUFS
|
||
# [4] 限制峰值 -1 dBTP
|
||
ffmpeg -i "$INPUT" \
|
||
-af "highpass=f=80,\
|
||
acompressor=threshold=-18dB:ratio=4:attack=20:release=250,\
|
||
loudnorm=I=-16:TP=-1:LRA=11,\
|
||
alimiter=limit=0.891" \
|
||
-ar 48000 -sample_fmt s16 -ac 1 \
|
||
-y "$OUTPUT"
|
||
```
|
||
|
||
### 4.2 加听筒效果(电话 / 微信语音形态)
|
||
|
||
```bash
|
||
INPUT="audio/01_processed/tts/lv11_2103_mom_call_01_v1.wav"
|
||
OUTPUT="audio/01_processed/tts/lv11_2103_mom_call_01_v1_phone.wav"
|
||
|
||
# 模拟手机听筒: HPF 300Hz + LPF 3.4kHz + 轻微饱和
|
||
ffmpeg -i "$INPUT" \
|
||
-af "highpass=f=300:poles=2,\
|
||
lowpass=f=3400:poles=2,\
|
||
equalizer=f=1500:t=q:w=1:g=1,\
|
||
acompressor=threshold=-20dB:ratio=3:attack=5:release=50" \
|
||
-y "$OUTPUT"
|
||
```
|
||
|
||
> **例外**:`mom` 第 16 条 4'17" 长语音**不加听筒效果**——SOP 设计意图是"完整音质版"。
|
||
|
||
### 4.3 长戏分段拼接
|
||
|
||
```bash
|
||
# 1. 先给每段加 200ms 静音头尾
|
||
for seg in audio/00_raw/tts/lv11_0200_mom_voice_01_seg*.wav; do
|
||
ffmpeg -i "$seg" \
|
||
-af "adelay=200|200,apad=pad_dur=0.2" \
|
||
-y "${seg%.wav}_padded.wav"
|
||
done
|
||
|
||
# 2. 生成拼接文件列表
|
||
ls audio/00_raw/tts/lv11_0200_mom_voice_01_seg*_padded.wav | \
|
||
sed "s/^/file '/" | sed "s/$/'/" > /tmp/concat_list.txt
|
||
|
||
# 3. 拼接 + crossfade
|
||
ffmpeg -f concat -safe 0 -i /tmp/concat_list.txt \
|
||
-af "acrossfade=d=0.05:c1=tri:c2=tri" \
|
||
-y audio/00_raw/tts/lv11_0200_mom_voice_01_full.wav
|
||
|
||
# 注意:FFmpeg concat + acrossfade 只支持两个输入。
|
||
# 多段拼接需要逐段 crossfade,或直接 concat 后人工检查接缝。
|
||
# 推荐方案:先 concat 拼接,段间 200ms 静音已经足够自然。
|
||
ffmpeg -f concat -safe 0 -i /tmp/concat_list.txt \
|
||
-c copy \
|
||
-y audio/00_raw/tts/lv11_0200_mom_voice_01_full.wav
|
||
```
|
||
|
||
### 4.4 批量后期处理脚本
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# 批量处理 00_raw/tts/ → 01_processed/tts/
|
||
# 用法: bash audio/process_tts.sh
|
||
|
||
for f in audio/00_raw/tts/lv11_*.wav; do
|
||
basename=$(basename "$f")
|
||
output="audio/01_processed/tts/$basename"
|
||
|
||
# 跳过已处理的
|
||
[ -f "$output" ] && echo "跳过: $basename" && continue
|
||
|
||
echo "处理: $basename"
|
||
ffmpeg -i "$f" \
|
||
-af "highpass=f=80,\
|
||
acompressor=threshold=-18dB:ratio=4:attack=20:release=250,\
|
||
loudnorm=I=-16:TP=-1:LRA=11" \
|
||
-ar 48000 -sample_fmt s16 -ac 1 \
|
||
-y "$output" 2>/dev/null
|
||
|
||
echo " → $output"
|
||
done
|
||
|
||
echo "TTS 后期处理完成"
|
||
```
|
||
|
||
### 4.5 最终打包(wav → ogg)
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# 01_processed → 02_final (ogg Vorbis q=5)
|
||
for f in audio/01_processed/tts/*.wav; do
|
||
basename=$(basename "${f%.wav}.ogg")
|
||
output="audio/02_final/tts/$basename"
|
||
ffmpeg -i "$f" -c:a libvorbis -q:a 5 -y "$output" 2>/dev/null
|
||
done
|
||
echo "OGG 打包完成"
|
||
```
|
||
|
||
### 4.6 角色专属后期(按 SOP §4.2 配置卡)
|
||
|
||
```bash
|
||
# mom: 高频衰减 -3dB + 轻 reverb
|
||
ffmpeg -i input.wav -af "equalizer=f=5000:t=h:w=2000:g=-3" -y output.wav
|
||
|
||
# azhe: 中频衰减 -2dB("闷"感)
|
||
ffmpeg -i input.wav -af "equalizer=f=2000:t=q:w=1:g=-2" -y output.wav
|
||
|
||
# delivery: 低频衰减 -6dB(模拟听筒)
|
||
ffmpeg -i input.wav -af "equalizer=f=200:t=h:w=200:g=-6" -y output.wav
|
||
|
||
# self-3y: 高频衰减 -2dB + 底噪(录音笔质感)
|
||
ffmpeg -i input.wav \
|
||
-af "equalizer=f=5000:t=h:w=2000:g=-2" \
|
||
-y output.wav
|
||
# 底噪可以后续叠加一层轻微白噪声
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 音乐素材(Suno)
|
||
|
||
音乐生产仍走 Suno 云端,按 SOP §6 操作。
|
||
|
||
### 5.1 操作步骤
|
||
|
||
1. 登录 Suno(需要 Pro 订阅)
|
||
2. 选 Custom Mode
|
||
3. 按 SOP §6.2 的 4 段曲 Prompt 生成
|
||
4. 每段跑 2 轮(出 4 首候选),选最佳
|
||
5. 用 Extend 延长到目标时长
|
||
6. 下载 WAV 格式到 `audio/00_raw/music/`
|
||
|
||
### 5.2 铃声生成
|
||
|
||
按 SOP §6.3 的 9 个角色铃声 Prompt 在 Suno 生成。
|
||
下载到 `audio/00_raw/ringtone/`。
|
||
|
||
### 5.3 音乐后期处理
|
||
|
||
```bash
|
||
# 音乐处理: 削 AI 浊感 + 响度 -18 LUFS
|
||
for f in audio/00_raw/music/*.wav; do
|
||
basename=$(basename "$f")
|
||
ffmpeg -i "$f" \
|
||
-af "equalizer=f=350:t=q:w=1.5:g=-2,\
|
||
loudnorm=I=-18:TP=-1:LRA=11" \
|
||
-ar 48000 \
|
||
-y "audio/01_processed/music/$basename" 2>/dev/null
|
||
done
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 音效素材(ElevenLabs SFX)
|
||
|
||
### 6.1 操作步骤
|
||
|
||
1. 登录 ElevenLabs(Creator 订阅,含 Sound Effects)
|
||
2. 按 SOP §7.2 的 Prompt 逐条生成
|
||
3. **每条跑 3 次**,挑最佳
|
||
4. 下载后转 WAV:
|
||
```bash
|
||
# ElevenLabs 默认 mp3,转为 wav
|
||
for f in audio/00_raw/sfx/*.mp3; do
|
||
ffmpeg -i "$f" -ar 48000 -y "${f%.mp3}.wav" && rm "$f"
|
||
done
|
||
```
|
||
|
||
### 6.2 音效后期
|
||
|
||
```bash
|
||
# 短音效响度 -14 LUFS
|
||
for f in audio/00_raw/sfx/*.wav; do
|
||
basename=$(basename "$f")
|
||
ffmpeg -i "$f" \
|
||
-af "loudnorm=I=-14:TP=-1:LRA=11" \
|
||
-ar 48000 \
|
||
-y "audio/01_processed/sfx/$basename" 2>/dev/null
|
||
done
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 环境底噪(Freesound)
|
||
|
||
### 7.1 操作步骤
|
||
|
||
1. 按 SOP §8.1 的清单在 Freesound 搜索
|
||
2. 过滤:CC0 优先,≥ 44.1kHz,下载 WAV
|
||
3. 文件名加 Freesound ID:如 `amb_apartment_night_fs123456.wav`
|
||
4. **立刻登记到版权表**
|
||
|
||
### 7.2 环境底噪后期
|
||
|
||
```bash
|
||
# 环境底噪处理: HPF 60Hz + LPF 12kHz + 响度 -22 LUFS
|
||
for f in audio/00_raw/ambience/*.wav; do
|
||
basename=$(basename "$f")
|
||
ffmpeg -i "$f" \
|
||
-af "highpass=f=60,lowpass=f=12000,\
|
||
loudnorm=I=-22:TP=-3:LRA=11" \
|
||
-ar 48000 -ac 2 \
|
||
-y "audio/01_processed/ambience/$basename" 2>/dev/null
|
||
done
|
||
```
|
||
|
||
### 7.3 单声道转假性立体声
|
||
|
||
```bash
|
||
# 如果下载到的是单声道,做假性立体声
|
||
INPUT="audio/01_processed/ambience/amb_apartment_night.wav"
|
||
OUTPUT="audio/01_processed/ambience/amb_apartment_night_stereo.wav"
|
||
|
||
ffmpeg -i "$INPUT" \
|
||
-filter_complex "\
|
||
[0:a]asplit=2[L][R];\
|
||
[R]adelay=12|12,equalizer=f=8000:t=q:w=1:g=1[Rd];\
|
||
[L][Rd]amerge=inputs=2[out]" \
|
||
-map "[out]" -ac 2 \
|
||
-y "$OUTPUT"
|
||
```
|
||
|
||
---
|
||
|
||
## 8. QA 验收
|
||
|
||
### 8.1 响度检查脚本
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# 检查所有已处理音频的响度
|
||
echo "=== TTS 响度检查 (目标 -16 LUFS) ==="
|
||
for f in audio/01_processed/tts/*.wav; do
|
||
result=$(ffmpeg -i "$f" -af "loudnorm=print_format=json" -f null - 2>&1 | \
|
||
grep -A1 '"input_i"' | tail -1 | tr -d ' ",' | cut -d: -f2)
|
||
echo " $(basename "$f"): ${result} LUFS"
|
||
done
|
||
|
||
echo ""
|
||
echo "=== 音乐响度检查 (目标 -18 LUFS) ==="
|
||
for f in audio/01_processed/music/*.wav; do
|
||
result=$(ffmpeg -i "$f" -af "loudnorm=print_format=json" -f null - 2>&1 | \
|
||
grep -A1 '"input_i"' | tail -1 | tr -d ' ",' | cut -d: -f2)
|
||
echo " $(basename "$f"): ${result} LUFS"
|
||
done
|
||
|
||
echo ""
|
||
echo "=== 环境底噪响度检查 (目标 -22 LUFS) ==="
|
||
for f in audio/01_processed/ambience/*.wav; do
|
||
result=$(ffmpeg -i "$f" -af "loudnorm=print_format=json" -f null - 2>&1 | \
|
||
grep -A1 '"input_i"' | tail -1 | tr -d ' ",' | cut -d: -f2)
|
||
echo " $(basename "$f"): ${result} LUFS"
|
||
done
|
||
```
|
||
|
||
### 8.2 验收流程
|
||
|
||
1. **自检**:跑响度检查脚本,确认所有文件在目标范围 ±1 LUFS
|
||
2. **单条听感**:用耳机逐条听,标记不满意的(记录到 `03_qa_reports/`)
|
||
3. **上下文听感**:把同一时间段的所有音频按时间轴拼起来完整听一遍
|
||
4. **不满意的重做**:回到 §3 重新合成,调参或换参考音频
|
||
|
||
---
|
||
|
||
## 9. 执行顺序总结
|
||
|
||
```
|
||
第 1 步 · 部署环境 (§1)
|
||
↓ 约 30 分钟
|
||
第 2 步 · 准备参考音频 (§2) ← 这一步最关键,音色质量取决于此
|
||
↓ 约 1-3 天(需要找人录音或搜集素材)
|
||
第 3 步 · 系统 TTS 先行 (§3.4) ← 最简单的先跑通流程
|
||
↓ 约 1 小时
|
||
第 4 步 · 短台词批量生产 (§3.1/3.2)
|
||
↓ 约 3-4 小时
|
||
第 5 步 · 长戏分段录制 (§3.3)
|
||
↓ 约 2-3 小时
|
||
第 6 步 · 后期处理 (§4)
|
||
↓ 约 2-3 小时
|
||
第 7 步 · 铃声 + 音乐 (§5) ← 可与 TTS 并行
|
||
↓ 约 3 小时
|
||
第 8 步 · 音效 (§6) ← 可与 TTS 并行
|
||
↓ 约 3 小时
|
||
第 9 步 · 环境底噪 (§7) ← 可与 TTS 并行
|
||
↓ 约 2 小时
|
||
第 10 步 · QA 验收 (§8)
|
||
↓ 约 4-6 小时
|
||
第 11 步 · 最终打包 (§4.5)
|
||
↓ 约 30 分钟
|
||
完成
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 A · 常见问题
|
||
|
||
**Q: CosyVoice 生成的语音有电流声/杂音?**
|
||
A: 检查参考音频质量。参考音频必须干净无噪,采样率 ≥ 16kHz。
|
||
|
||
**Q: 语速不自然?**
|
||
A: speed 参数范围 0.5-2.0。如果调速后音质下降,改为在合成文本中加标点控制节奏(句号=长停顿,逗号=短停顿)。
|
||
|
||
**Q: 怎样让同一个音色听起来"醉了"?**
|
||
A: CosyVoice 没有情绪控制参数。替代方案:
|
||
1. 在文本中插入省略号、重复字来模拟口齿不清
|
||
2. 后期用 FFmpeg 轻微变速+加混响:
|
||
```bash
|
||
ffmpeg -i drunk.wav -af "atempo=0.95,aecho=0.8:0.7:40:0.3" -y drunk_fx.wav
|
||
```
|
||
|
||
**Q: WebUI 启动报 CUDA 错误?**
|
||
A: 确认 conda env 正确:`conda activate cosyvoice`。确认 GPU 可用:`nvidia-smi`。
|
||
|
||
**Q: 生成很慢?**
|
||
A: RTX 5090 应该很快(远超实时)。如果慢,检查是否误用了 CPU。在 Python 中加 `print(torch.cuda.is_available())` 确认。
|