核心课程 / 自然语言交互应用 / EXP-03
EXP-03

本地语音合成:文字怎么变成能听见的声音

机器人会听还不够,还得会说。这一页把 Matcha-TTS + Vocos 的合成链拆成六格, 并用实测告诉你 RTF 是怎么算的、为什么语速调快反而更“费”。

⭐ 4 学时Matcha-TTSVocos音素化RTF

01目标:一句话变一个 wav,并报出生成代价

给你
一行文本:你好,我是小飞,欢迎来到实验室
拿到
/tmp/tts.wav:3.44 s,16000 Hz,55026 采样点
Gen 1.17 s,RTF 0.339

板上真实跑出来的那一段(--speed 1.0):

Text:  你好,我是小飞,欢迎来到实验室
Speed: 1.0
Audio:  3.44s  (16000 Hz, 55026 samples)
Gen:    1.17s
RTF:    0.339  (faster than real-time)
Output: /tmp/tts_s1.0.wav

学完你该能做出这三件事

  • 合成 一段中文语音并用板载扬声器放出来(命令、模型路径、播放设备都写对)。
  • 算清 RTF 的含义:它等于 Gen ÷ Audio,小于 1 才是实时。
  • 解释 为什么语速从 1.0 调到 1.3,RTF 反而变大了。
⚠ 重难点 直觉是“语速调快,合成更快”。实际是合成一次的时间几乎不变(0.99~1.32 s), 而音频变短了(4.20 s → 2.61 s),于是 RTF = Gen ÷ Audio 反而升高。 这条反直觉是这一页的核心。

🔑 一句话:TTS 的产物不是一个“声音文件”,是「音频时长 + 生成耗时」这一对数。

02问题:它怎么知道每个字该念多长

导航播报、跌倒告警、对话回复 —— 机器人的每一句话都要现场“造”出来, 不能提前录:地点名、时间、人名都是变的。录好的音频库撑不住这三类场景。

你给一行字,它给你一段波形。中间要回答三件事:

疑问 1
字怎么变成音?

“实验室”三个字,模型看到的到底是什么?它不认识汉字。

疑问 2
每个音念多长,谁定的?

同一句话,慢读 4.20 s、快读 2.61 s,时长是谁算出来的?

疑问 3
为什么两个模型拼起来用?

Matcha-TTS 和 Vocos 各自干什么,能不能只用一个?

先猜一下 三个疑问里,哪个的答案不在任何一个模型里,而在一个“查表 + 规则”的前端里? 记住你的选择,第 04 节步骤 2 后面有答案。

🔑 一句话:字不认识字,它只认识音素;时长不是模型随手编,是由一套前端规则给的。

03现象:三档语速的真实数字

🎬 现象速览 · 30 秒先看效果

一条命令合成一个 wav,终端会打印四行:文本、时长与采样点数、生成耗时、RTF。 同一句话只改 --speed,四行里有三行会跟着变。下面是板上的真实输出。

命令里的 speedAudioGenRTFwav 大小
0.8(慢)4.20 s / 67235 点1.32 s0.315134514 B
1.0(默认)3.44 s / 55026 点1.17 s0.339110096 B
1.3(快)2.61 s / 41823 点0.99 s0.38083690 B

看第三、四列:语速越快,Audio 越短、Gen 也略短, 但 RTF 反而更高。原因在第 04 节最后一行公式。

怎么跑出来

模型都在 ~/models/tts,脚本自带默认路径,所以只需给文本和语速:

终端一 · 合成

cd ~/ros2_ws/src/xflz_eai_car1/interaction/car1_speech/tools
python3 tts_test.py --text "你好,我是小飞,欢迎来到实验室" --speed 1.0 --no-play -o /tmp/tts_s1.0.wav
Speed: 1.0 Audio: 3.44s (16000 Hz, 55026 samples) Gen: 1.17s RTF: 0.339 (faster than real-time) ← 四行齐了就算跑通;RTF 小于 1 说明能边生成边播

🔍 看什么:三处。

  • 看 Audio 与 Speed 成反比 —— 0.8 时 4.20 s,1.3 时 2.61 s,比例约 1.6 倍。
  • 看 Gen 几乎不变 —— 1.32 / 1.17 / 0.99,说明生成一次的计算量由“句子长度”决定,不由“念多快”决定。
  • 看 RTF 反向变化 —— 这就是重难点里那条反直觉的实测证据。

🔑 一句话:Audio 跟语速反着走,RTF 跟着 Audio 反着走。

04原理:文字到波形的六格

CORE IDEA · 一句话原理

把文字先“读成音”,再把音“画成波形”。 前一半靠查表和规则(前端),后一半靠两个模型: Matcha-TTS 把音素摊成一张“频谱图”,Vocos 把这张图还原成波形。就像先写简谱、再演奏出来。

② 的三个疑问在 04 的哪几格回答
疑问 1 字怎么变成音步骤 1~3(规范化 → 音素化)
疑问 2 时长谁定的步骤 4(声学模型同时给出时长与频谱)
疑问 3 为什么要两个模型步骤 4 与 5 分工
文字进 → 声音出:六次形态变化 1 拿到一行原始文本 形态:字符串,里面可能夹着数字、日期、英文 2 前端把数字、日期改成“该怎么念” 形态:可朗读的汉字串(像 number-zh.fst / date-zh.fst 两条规则) 3 查 lexicon 把汉字翻成音素 形态:音素序列(sh i2 yan4 sh i4 …),模型只认这个 4 Matcha-TTS 摊出频谱图,同时定下时长 形态:梅尔谱(时间帧 × 频率格)—— 相当于一张“简谱” 5 Vocos 声码器把频谱还原成波形 形态:16000 Hz 采样点序列(55026 个点 = 3.44 s) 6 送进声卡,扬声器推动空气 形态:声音(这一步出错,前面全白做)
图 4-1|六格链路。1~3 格是查表与规则,4~5 格是两个模型,6 格是最容易翻车的一步。

1模型不认识汉字,那第一步给了它什么?

吃什么:一行字符串。做什么:先做文本规范化 —— 「2026-11-09」要读成「二零二六年十一月九日」, 「3.5」要读成「三点五」。吐什么:一串纯中文。用什么:前端的 number-zh.fst、date-zh.fst 规则文件。

动参数会怎样:这层规则缺了,模型会把「11-09」念成「一一杠零九」——不是模型笨,是没人告诉它这是日期。

2为什么还要查一张 lexicon 表?

吃什么:规范后的中文。做什么:按 lexicon.txt(约 1.4 MB)把每个字查成带声调的音素, 再交给 espeak-ng-data 处理生僻字。吐什么:音素序列。

揭晓答案(疑问 3 的一半) 第 02 节里「不在任何一个模型里」的是疑问 3 的前半段:字→音这一步是查表 + 规则, 没有神经网络参与。查不到的字会退回 espeak-ng 猜音,所以生僻字容易念错。

3音素怎么变成一张“简谱”?

吃什么:音素序列。做什么:Matcha-TTS 一次性预测每个音素占多少帧(这就是时长), 并给出每帧的频率分布。吐什么:梅尔谱。动参数会怎样:--speed 就是在这层缩放时长, 0.8 让每音素多占帧 → 音频变长。

4有了谱,为什么还要第二个模型?

吃什么:梅尔谱。做什么:Vocos 声码器把谱还原成波形采样点。 吐什么:16 kHz 的浮点序列。

为什么不能一个模型干完:声学模型输出的是“频率随时间怎么变”,丢掉了相位; 相位恰恰决定波形长什么样。Vocos 专门负责把相位补回来 —— 这就是第二个模型的全部职责。

5“能边生成边播”凭什么判断?

吃什么:生成的音频与耗时。做什么:算 RTF = Gen ÷ Audio。 吐什么:一个小于 1 的数就代表实时。

RTF = 生成耗时 ÷ 音频时长
0.339 = 1.17 s ÷ 3.44 s

动参数会怎样:语速调到 1.3,Audio 掉到 2.61 s 而 Gen 只降到 0.99 s, 于是 RTF 升到 0.380。RTF 变大不是变慢,是音频变短了。

6为什么“放不出来”是最常见的结果?

吃什么:一个 wav 文件。做什么:用 ALSA 送到某张声卡。吐什么:声音。 动参数会怎样:-D plughw:3,0 写错成 plughw:0,0,声音就走到别的声卡上(甚至没有声音)。

实测这台板上:六麦阵列那张卡只有采集,放音要用 USB Audio Device(card 3)。 这一步在 05 节会给命令。

每格答全的五列表

格吃什么做什么用什么吐出动参数会怎样
1原始文本规范化数字与日期number-zh.fst / date-zh.fst中文串缺规则 → 日期念成符号
2中文串查表转音素lexicon.txt + espeak-ng-data音素序列生僻字 → 念错音
3音素序列预测帧数与时频分布Matcha-TTS(model-steps-3.onnx)梅尔谱--speed 缩放时长
4梅尔谱还原波形与相位Vocos(vocos-16khz-univ.onnx)采样点换声码器 → 音质变
5采样点 + 耗时算实时性脚本内 RTF 计算RTF 数值语速↑ → RTF↑
6wav送声卡ALSA aplay / -D声音设备名错 → 无声
术语一句话什么时候失效
音素把字拆到“声音最小单位”,如 sh / i2多音字靠词典,词典没收录就念错
梅尔谱把声音按频率分格、按时间排开的图只保留幅度,丢了相位,所以需要声码器
声码器把频谱图还原成波形的模型换模型即换音色,跨模型不能混用
RTF生成耗时 ÷ 音频时长≥1 就无法边说边播,只能等生成完

🔑 一句话:1~3 格决定“念得对不对”,4~5 格决定“像不像人”,6 格决定“你听不听得见”。

05实践:三步合成并对照

  1. 确认模型与脚本在位 约 5 分钟 进工具目录,先看模型五个文件齐全没有:两个 onnx 加起来约 120 MB,缺一个就会在加载时报错。
  2. 合成第一句并不播放 约 10 分钟 用 --no-play 先把 wav 落盘,看 Audio / Gen / RTF 三行,确认链路通。
  3. 换语速做对照,再放出来听 约 15 分钟 同一句话跑 0.8 / 1.0 / 1.3 三档,记录三个 RTF;最后用 -D plughw:3,0 播放一次。

对账表:命令 → 函数 → 文件

格命令 / 参数函数或变量文件
1~2(自动)前端规则与 lexicon 查表~/models/tts/
3--acoustic-model(默认 model-steps-3.onnx)Matcha-TTS 合成tools/tts_test.py:133
3--speed时长缩放tools/tts_test.py:152
4--vocoder(默认 vocos-16khz-univ.onnx)Vocos 还原波形tools/tts_test.py:135
5(自动)RTF = gen_time / audio_durtools/tts_test.py 输出段
6-D plughw:3,0 / --card 0ALSA 播放与音量设定tools/tts_test.py:167-174

踩坑速查

学员原话原因怎么办
“合成完了但一点声音都没有”-D 默认指向 plughw:0,0,本板放音在 card 3先 aplay -l 找 USB Audio Device,再加 -D plughw:3,0
“声音在别的地方响”板上有 4 张声卡,默认卡不一定是你要的那张同上,显式指定设备
“日期读成了‘一一杠零九’”前端规则文件没被加载(--rule-fsts 给空)保留默认的三个 fst,别手动改
“生僻字念错”lexicon.txt 没这个词,退回 espeak 猜音可接受;要准就得往 lexicon 里加词条
“RTF 怎么反而变大了”分母(音频)变短得比分母(耗时)更快看第 04 节步骤 5 的公式,别看错量
🔁 现象呼应 回到第 03 节那张三行表:0.8 的 Audio 4.20s、1.3 的 Audio 2.61s 差 1.6 倍, 而 Gen 只从 1.32 降到 0.99 —— 这个比例正是 RTF 反向变化的来源。

🧪 Vibe Coding 复盘

想要提示词验收判据
模块职责「这个脚本里哪几段是前端、哪几段是模型推理、哪几段是播放?给出对应的参数和函数」必须点出 --rule-fsts(前端)、--acoustic-model/--vocoder(模型)、-D(播放)
控制流「画出从文本到 wav 的流程图,标出 RTF 在哪一步被算出来」RTF 在生成之后、播放之前
时序「合成与播放是并行还是串行?如果要在播放的同时生成下一句,需要改哪里?」当前是串行;要点出“先落盘再播放”
AI 常见说法源码实际
“一个模型端到端出波形”两个模型:Matcha-TTS 出谱、Vocos 出波形
“语速影响模型推理速度”--speed 只缩放时长;Gen 基本不变
“汉字直接进模型”先规范、再查 lexicon 转音素
“播放设备默认就行”默认 plughw:0,0,本板要 plughw:3,0

🔑 一句话:先落盘看三个数字,再放出来听 —— 分两步排查比一次到底快。

06交互:语速、字数与 RTF 的关系

📌 掌握检查:把语速拖到 1.3,RTF 应该变大还是变小?先答出来,再拖。

互动 6-1 · 合成代价实验台
1.00
14
4
默认 14 字、语速 1.0,对应实测 Audio 3.44 s / Gen 1.17 s / RTF 0.339。

三个结论,拖一遍就能看出来:

  • 语速 ↑ → Audio ↓ → RTF ↑(重难点那条)。
  • 字数 ↑ → Audio 与 Gen 一起 ↑,但 RTF 变化不大 —— 说明 RTF 与句子长短基本无关。
  • 线程数 ↑ → Gen ↓ → RTF ↓,这是唯一能真正“提速”的旋钮。

可信度说明:台子用两次实测点(6 字 / 14 字)拟合出「约 0.245 秒每字 ÷ 语速」, 线程数按 4 线程实测值线性外推。绝对值仅供对比趋势,不能当性能基准。

🔑 一句话:语速改的是分母,线程改的是分子,想变快只能动分子。

07验证:三道题

Q1(原理)模型收到的输入是什么?

汉字要先经 lexicon 查表转成音素,模型只认音素。

Q2(实践)合成成功但没声音,最先查什么?

本板放音在 card 3,默认的 plughw:0,0 不是它。

Q3(边界)语速从 1.0 调到 1.3,RTF 从 0.339 升到 0.380,说明什么?

Gen 实际从 1.17 降到 0.99,是分母掉得更快。

✅ 目标对账

① 的目标对应题号
能合成并放出来Q2(设备选对才能出声)
能算清 RTFQ3
能解释语速与 RTF 的反向关系Q3 + Q1 的链路(音素→时长)

🔑 一句话:三题分别考「模型吃什么 / 声音从哪出 / 那个数怎么算」。

08总结:前端定读法,声码器定音质

知识点关系表

概念它解决了什么和别的关系
文本规范化让数字、日期有读法在音素化之前,缺它则后面全错
lexicon把汉字映射成音素查不到才交给 espeak-ng
Matcha-TTS音素 → 梅尔谱 + 时长语速在这一层介入
Vocos梅尔谱 → 波形必须与声学模型配套
RTF判断能否边说边播由 Gen 与 Audio 两个量共同决定
ALSA 设备决定声音从哪出与前面五格无关,但能让结果“看不见”

两个反直觉

  • 直觉是“语速快 = 合成快”,实际是合成耗时几乎不变,是音频变短让 RTF 升高。
  • 直觉是“一个模型端到端出声音”,实际是两个模型分工:一个管“念什么、多长”,一个管“波形长什么样”。

注意事项

  • 数值陷阱:RTF 小于 1 只说明“能实时”,不代表音质好;两者是不同维度。
  • 两个 onnx 必须配套使用,换声码器会让音色与之前完全不同。
  • 播放设备显式指定,别依赖默认卡;本板实测放音在 card 3。
  • 语速别超过 1.5:实测 1.3 已能听出偏快,再快会吞字。

前置依赖

类别具体缺了会怎样
硬件一块能放音的声卡(本板 card 3 USB Audio Device)合成成功但没声音
模型~/models/tts/:两个 onnx + lexicon + tokens + fst + espeak 数据加载时报文件不存在
库sherpa-onnx(本机 1.13.4)导入即失败
配置--speed 等参数用默认即可;改 --rule-fsts 会破坏读法日期、数字读错
环境能读写 /tmp;aplay 可用无法落盘或无法播放

这套东西用在哪儿

  • 导航播报:把「前往 3 号工位」现场合成,不用提前录音。
  • 告警播报:跌倒、越界等事件用固定句式即时念出(本板实测「检测到老人跌倒,正在呼叫帮助」3.12 s)。
  • 陪伴对话:把 LLM 回复逐句合成,边生成边播,见 EXP-04。

🔑 记忆点:念得对靠前端,像人靠声码器,听得见靠声卡 —— 三件事分开查,别混在一起。