从"外国人味"到地道中文:kokoroi-rs v0.1.2 架构升级深度解析
摘要:kokoroi-rs v0.1.2 版本对中文 TTS 引擎进行了重大架构升级,核心改进包括 G2P 引擎从 IPA 全面切换为 Bopomofo 音素方案、模型体系从单一模型升级为三档可选(S/M/L)、以及音频后处理从"过处理"重构为"最小干预"原则。本文深度解析了这些技术细节与设计考量,帮助开发者理解如何消除中文合成语音的"外国人味",实现更自然流畅的语音输出。
关键词:kokoroi-rs, 中文 TTS, G2P, Bopomofo, 注音符号, 音素转换, 模型量化, 音频后处理, 开源项目, Rust
G2P 引擎全面切换 Bopomofo、模型三档可选、音频后处理重构------一次让中文 TTS 脱胎换骨的升级
引子:一个困扰已久的问题
如果你用过早期的 kokoroi-rs,可能会注意到一个细微但难以忽视的问题:合成的中文语音听起来总有一种"外国人味"------发音是准的,但语调、韵律总觉得哪里不对劲。
问题出在 G2P(Grapheme-to-Phoneme,字素转音素) 环节。v0.1.1 及之前版本使用 IPA(国际音标)作为中文音素表示,但 IPA 对中文字素的覆盖不够精确,导致模型学到的音素分布与真实中文语音存在偏差。
v0.1.2 解决这个问题的方案很直接------全面切换为 Bopomofo(注音符号)音素方案。这只是一次升级的冰山一角,本文将系统拆解这次架构升级背后的技术细节与设计考量。
一、G2P 引擎重构:从 IPA 到 Bopomofo
1.1 为什么是 Bopomofo?
Kokoro 模型原生训练使用的正是 Bopomofo 音素表示。早期版本采用 IPA 方案,相当于在模型输入端做了一次"音素转译"------先把中文转成 IPA,再映射到模型词表。这个转译过程引入了信息损失,尤其体现在声调、连读变调等中文特有的韵律特征上。
v0.1.2 将 G2P 流水线全面切换到 Bopomofo,并配合专为中文优化的 ZH_VOCAB 词表,让模型直接"读懂"注音符号,消除了中间转译层。
#mermaid-svg-3wUw1zvsY8irB5Bz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3wUw1zvsY8irB5Bz .error-icon{fill:#552222;}#mermaid-svg-3wUw1zvsY8irB5Bz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3wUw1zvsY8irB5Bz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3wUw1zvsY8irB5Bz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3wUw1zvsY8irB5Bz .marker.cross{stroke:#333333;}#mermaid-svg-3wUw1zvsY8irB5Bz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3wUw1zvsY8irB5Bz p{margin:0;}#mermaid-svg-3wUw1zvsY8irB5Bz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster-label text{fill:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster-label span{color:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster-label span p{background-color:transparent;}#mermaid-svg-3wUw1zvsY8irB5Bz .label text,#mermaid-svg-3wUw1zvsY8irB5Bz span{fill:#333;color:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz .node rect,#mermaid-svg-3wUw1zvsY8irB5Bz .node circle,#mermaid-svg-3wUw1zvsY8irB5Bz .node ellipse,#mermaid-svg-3wUw1zvsY8irB5Bz .node polygon,#mermaid-svg-3wUw1zvsY8irB5Bz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3wUw1zvsY8irB5Bz .rough-node .label text,#mermaid-svg-3wUw1zvsY8irB5Bz .node .label text,#mermaid-svg-3wUw1zvsY8irB5Bz .image-shape .label,#mermaid-svg-3wUw1zvsY8irB5Bz .icon-shape .label{text-anchor:middle;}#mermaid-svg-3wUw1zvsY8irB5Bz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3wUw1zvsY8irB5Bz .rough-node .label,#mermaid-svg-3wUw1zvsY8irB5Bz .node .label,#mermaid-svg-3wUw1zvsY8irB5Bz .image-shape .label,#mermaid-svg-3wUw1zvsY8irB5Bz .icon-shape .label{text-align:center;}#mermaid-svg-3wUw1zvsY8irB5Bz .node.clickable{cursor:pointer;}#mermaid-svg-3wUw1zvsY8irB5Bz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3wUw1zvsY8irB5Bz .arrowheadPath{fill:#333333;}#mermaid-svg-3wUw1zvsY8irB5Bz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3wUw1zvsY8irB5Bz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3wUw1zvsY8irB5Bz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wUw1zvsY8irB5Bz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3wUw1zvsY8irB5Bz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wUw1zvsY8irB5Bz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster text{fill:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz .cluster span{color:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3wUw1zvsY8irB5Bz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3wUw1zvsY8irB5Bz rect.text{fill:none;stroke-width:0;}#mermaid-svg-3wUw1zvsY8irB5Bz .icon-shape,#mermaid-svg-3wUw1zvsY8irB5Bz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wUw1zvsY8irB5Bz .icon-shape p,#mermaid-svg-3wUw1zvsY8irB5Bz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3wUw1zvsY8irB5Bz .icon-shape .label rect,#mermaid-svg-3wUw1zvsY8irB5Bz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wUw1zvsY8irB5Bz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3wUw1zvsY8irB5Bz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3wUw1zvsY8irB5Bz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} v0.1.2 --- Bopomofo 方案
中文文本
jieba 分词 + POS
多音字消歧
变调处理
Bopomofo 注音转换
ZH_VOCAB Tokenization
ONNX 推理
v0.1.1 --- IPA 方案
中文文本
jieba 分词
拼音转换
IPA 音素映射
MODEL_VOCAB Tokenization
ONNX 推理
1.2 G2P 流水线对比
v0.1.2 的中文 G2P 流水线在多个环节做了增强:
#mermaid-svg-KBZ8XPAFs3yiqLW1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .error-icon{fill:#552222;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .marker.cross{stroke:#333333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 p{margin:0;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster-label text{fill:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster-label span{color:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster-label span p{background-color:transparent;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .label text,#mermaid-svg-KBZ8XPAFs3yiqLW1 span{fill:#333;color:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .node rect,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node circle,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node ellipse,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node polygon,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .rough-node .label text,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node .label text,#mermaid-svg-KBZ8XPAFs3yiqLW1 .image-shape .label,#mermaid-svg-KBZ8XPAFs3yiqLW1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .rough-node .label,#mermaid-svg-KBZ8XPAFs3yiqLW1 .node .label,#mermaid-svg-KBZ8XPAFs3yiqLW1 .image-shape .label,#mermaid-svg-KBZ8XPAFs3yiqLW1 .icon-shape .label{text-align:center;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .node.clickable{cursor:pointer;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .arrowheadPath{fill:#333333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KBZ8XPAFs3yiqLW1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBZ8XPAFs3yiqLW1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster text{fill:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .cluster span{color:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KBZ8XPAFs3yiqLW1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .icon-shape,#mermaid-svg-KBZ8XPAFs3yiqLW1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .icon-shape p,#mermaid-svg-KBZ8XPAFs3yiqLW1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .icon-shape .label rect,#mermaid-svg-KBZ8XPAFs3yiqLW1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBZ8XPAFs3yiqLW1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KBZ8XPAFs3yiqLW1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KBZ8XPAFs3yiqLW1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} v0.1.2 G2P 流水线
v0.1.1 G2P 流水线
输入处理
文本输入
数字转换
标点映射
直接分词 + IPA 映射
MODEL_VOCAB
178 token 词表
segment_with_pos
词性标注分词
pre_merge_for_modify
合并修饰词
多音字消歧
PolyphonicDisambiguator
tone_sandhi
变调处理
pinyin_to_bopomofo
拼音 → 注音
ZH_VOCAB
178 token 扩展词表
ONNX 推理
关键改进点:
- 词性标注分词 :
segment_with_pos不仅分词,还输出每个词的词性(POS),为后续变调处理提供上下文 - 多音字消歧 :
PolyphonicDisambiguator基于上下文规则处理多音字,例如"了"在"了解"中读liǎo,在"好了"中读le - 变调处理:实现三声变调(两个三声相连,前变二声)以及"一"、"不"的变调规律
- 拼音 → 注音 :
pinyin_to_bopomofo将带声调数字的拼音转换为 Bopomofo 符号
1.3 效果对比
| 维度 | v0.1.1 (IPA) | v0.1.2 (Bopomofo) |
|---|---|---|
| 中文语音自然度 | 有外国人味道 | 自然流畅 |
| 首字延迟 | ~730ms 静音可闻 | ~50ms 正常前置 |
| 处理速度 (L 模型) | ~2.2s | ~1.9s |
| 实时率 | ~5.2x | ~5.4x |
二、模型体系升级:三档可选,丰俭由人
2.1 从单一到三档
v0.1.1 只有单一的 kokoro-v1.0.onnx(fp32,约 325MB)。v0.1.2 升级为 v1.1-zh 系列三档模型:
| 模型 | 精度 | 大小 | 场景 |
|---|---|---|---|
| S (kokoro-v1.1-zh-s.onnx) | int4 | 47MB | 最小体积,快速加载,低内存 |
| M (kokoro-v1.1-zh-m.onnx) | int8 | 79MB | 默认推荐,平衡体积与质量 |
| L (kokoro-v1.1-zh-l.onnx) | fp32 | 311MB | 完整精度,最佳质量 |
#mermaid-svg-KpQIMDJy702JoD5i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KpQIMDJy702JoD5i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KpQIMDJy702JoD5i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KpQIMDJy702JoD5i .error-icon{fill:#552222;}#mermaid-svg-KpQIMDJy702JoD5i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KpQIMDJy702JoD5i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KpQIMDJy702JoD5i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KpQIMDJy702JoD5i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KpQIMDJy702JoD5i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KpQIMDJy702JoD5i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KpQIMDJy702JoD5i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KpQIMDJy702JoD5i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KpQIMDJy702JoD5i .marker.cross{stroke:#333333;}#mermaid-svg-KpQIMDJy702JoD5i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KpQIMDJy702JoD5i p{margin:0;}#mermaid-svg-KpQIMDJy702JoD5i .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-KpQIMDJy702JoD5i .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-KpQIMDJy702JoD5i .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-KpQIMDJy702JoD5i .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-KpQIMDJy702JoD5i .legend text{fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-KpQIMDJy702JoD5i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 43% 41% 10% 6% 模型体积对比 v1.0 fp32 (旧) v1.1-zh L fp32 v1.1-zh M int8 v1.1-zh S int4
默认模型从 325MB 的 fp32 模型切换到 79MB 的 int8 模型,体积缩小 75%,而合成质量保持高水平。对于资源受限的场景(如嵌入式设备、边缘计算),47MB 的 int4 模型提供了更轻量的选择。
2.2 模型输入/输出接口
所有 v1.1-zh 模型统一使用以下接口:
#mermaid-svg-seZYOmHe6vJqMJG2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-seZYOmHe6vJqMJG2 .error-icon{fill:#552222;}#mermaid-svg-seZYOmHe6vJqMJG2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-seZYOmHe6vJqMJG2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-seZYOmHe6vJqMJG2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-seZYOmHe6vJqMJG2 .marker.cross{stroke:#333333;}#mermaid-svg-seZYOmHe6vJqMJG2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-seZYOmHe6vJqMJG2 p{margin:0;}#mermaid-svg-seZYOmHe6vJqMJG2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster-label text{fill:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster-label span{color:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster-label span p{background-color:transparent;}#mermaid-svg-seZYOmHe6vJqMJG2 .label text,#mermaid-svg-seZYOmHe6vJqMJG2 span{fill:#333;color:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 .node rect,#mermaid-svg-seZYOmHe6vJqMJG2 .node circle,#mermaid-svg-seZYOmHe6vJqMJG2 .node ellipse,#mermaid-svg-seZYOmHe6vJqMJG2 .node polygon,#mermaid-svg-seZYOmHe6vJqMJG2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-seZYOmHe6vJqMJG2 .rough-node .label text,#mermaid-svg-seZYOmHe6vJqMJG2 .node .label text,#mermaid-svg-seZYOmHe6vJqMJG2 .image-shape .label,#mermaid-svg-seZYOmHe6vJqMJG2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-seZYOmHe6vJqMJG2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-seZYOmHe6vJqMJG2 .rough-node .label,#mermaid-svg-seZYOmHe6vJqMJG2 .node .label,#mermaid-svg-seZYOmHe6vJqMJG2 .image-shape .label,#mermaid-svg-seZYOmHe6vJqMJG2 .icon-shape .label{text-align:center;}#mermaid-svg-seZYOmHe6vJqMJG2 .node.clickable{cursor:pointer;}#mermaid-svg-seZYOmHe6vJqMJG2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-seZYOmHe6vJqMJG2 .arrowheadPath{fill:#333333;}#mermaid-svg-seZYOmHe6vJqMJG2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-seZYOmHe6vJqMJG2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-seZYOmHe6vJqMJG2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-seZYOmHe6vJqMJG2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-seZYOmHe6vJqMJG2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-seZYOmHe6vJqMJG2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster text{fill:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 .cluster span{color:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-seZYOmHe6vJqMJG2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-seZYOmHe6vJqMJG2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-seZYOmHe6vJqMJG2 .icon-shape,#mermaid-svg-seZYOmHe6vJqMJG2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-seZYOmHe6vJqMJG2 .icon-shape p,#mermaid-svg-seZYOmHe6vJqMJG2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-seZYOmHe6vJqMJG2 .icon-shape .label rect,#mermaid-svg-seZYOmHe6vJqMJG2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-seZYOmHe6vJqMJG2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-seZYOmHe6vJqMJG2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-seZYOmHe6vJqMJG2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输出
模型
输入
input_ids
int641, N
style
float321, 256
speed
float321
ONNX 模型
v1.1-zh
waveform
float32N
模型输入输出保持稳定,切换模型无需修改业务代码,只需更换模型文件路径。
三、音频后处理:从"过处理"到"最小干预"
v0.1.1 的音频后处理存在过度加工的问题:全局 DC 偏移消除、RMS 窗口尾部检测、淡入淡出------这些操作本意是提升听感,但实际效果适得其反,静音区被偏移引入嗡嗡声,尾部裁切又带来爆音。
v0.1.2 采取最小干预原则,仅保留振幅阈值静音裁切:
#mermaid-svg-j6d0KBxb40N2yWrp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j6d0KBxb40N2yWrp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j6d0KBxb40N2yWrp .error-icon{fill:#552222;}#mermaid-svg-j6d0KBxb40N2yWrp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j6d0KBxb40N2yWrp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j6d0KBxb40N2yWrp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j6d0KBxb40N2yWrp .marker.cross{stroke:#333333;}#mermaid-svg-j6d0KBxb40N2yWrp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j6d0KBxb40N2yWrp p{margin:0;}#mermaid-svg-j6d0KBxb40N2yWrp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j6d0KBxb40N2yWrp .cluster-label text{fill:#333;}#mermaid-svg-j6d0KBxb40N2yWrp .cluster-label span{color:#333;}#mermaid-svg-j6d0KBxb40N2yWrp .cluster-label span p{background-color:transparent;}#mermaid-svg-j6d0KBxb40N2yWrp .label text,#mermaid-svg-j6d0KBxb40N2yWrp span{fill:#333;color:#333;}#mermaid-svg-j6d0KBxb40N2yWrp .node rect,#mermaid-svg-j6d0KBxb40N2yWrp .node circle,#mermaid-svg-j6d0KBxb40N2yWrp .node ellipse,#mermaid-svg-j6d0KBxb40N2yWrp .node polygon,#mermaid-svg-j6d0KBxb40N2yWrp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j6d0KBxb40N2yWrp .rough-node .label text,#mermaid-svg-j6d0KBxb40N2yWrp .node .label text,#mermaid-svg-j6d0KBxb40N2yWrp .image-shape .label,#mermaid-svg-j6d0KBxb40N2yWrp .icon-shape .label{text-anchor:middle;}#mermaid-svg-j6d0KBxb40N2yWrp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j6d0KBxb40N2yWrp .rough-node .label,#mermaid-svg-j6d0KBxb40N2yWrp .node .label,#mermaid-svg-j6d0KBxb40N2yWrp .image-shape .label,#mermaid-svg-j6d0KBxb40N2yWrp .icon-shape .label{text-align:center;}#mermaid-svg-j6d0KBxb40N2yWrp .node.clickable{cursor:pointer;}#mermaid-svg-j6d0KBxb40N2yWrp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j6d0KBxb40N2yWrp .arrowheadPath{fill:#333333;}#mermaid-svg-j6d0KBxb40N2yWrp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j6d0KBxb40N2yWrp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j6d0KBxb40N2yWrp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6d0KBxb40N2yWrp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j6d0KBxb40N2yWrp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6d0KBxb40N2yWrp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j6d0KBxb40N2yWrp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j6d0KBxb40N2yWrp .cluster text{fill:#333;}#mermaid-svg-j6d0KBxb40N2yWrp .cluster span{color:#333;}#mermaid-svg-j6d0KBxb40N2yWrp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j6d0KBxb40N2yWrp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j6d0KBxb40N2yWrp rect.text{fill:none;stroke-width:0;}#mermaid-svg-j6d0KBxb40N2yWrp .icon-shape,#mermaid-svg-j6d0KBxb40N2yWrp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6d0KBxb40N2yWrp .icon-shape p,#mermaid-svg-j6d0KBxb40N2yWrp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j6d0KBxb40N2yWrp .icon-shape .label rect,#mermaid-svg-j6d0KBxb40N2yWrp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6d0KBxb40N2yWrp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j6d0KBxb40N2yWrp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j6d0KBxb40N2yWrp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} v0.1.2 --- 最小干预
模型原始输出
11.65s
振幅阈值静音裁切
threshold: 0.006
纯净输出
9.51s,无加工伪影
v0.1.1 --- 过处理
模型原始输出
11.65s
全局 DC 偏移消除
RMS 窗口尾部检测
淡入淡出
!! 问题 !!
静音区偏移 → 嗡嗡声
尾部裁切 → 爆音
效果:尾部噪音被精准裁切,文件时长从 11.65s 精确到 9.51s,消除了约 1.4s 的冗余静音,且无任何加工伪影。
四、各模块改进一览
4.1 CLI 工具
diff
- 默认模型: models/kokoro-v1.0.onnx (325MB)
+ 默认模型: models/kokoro-v1.1-zh-m.onnx (79MB, int8)
+ 新增 -m/--model 参数,支持模型切换
+ 自动下载指向本项目 Release
4.2 HTTP Server
diff
- Web 页面: 仅 SSE 流式播放,无下载功能
+ Web 页面: SSE 流式播放 + 自动拼接完整 WAV + 下载链接
+ 音频后处理: 振幅阈值静音裁切
Server 端的 Web 演示页面现在支持流式播放的同时自动拼接完整音频,用户试听满意后可直接下载 WAV 文件,体验更完整。
4.3 WASM 模块
diff
- 模型: 固定 model_fp16.onnx
+ 模型: 三档切换 (S int4 / M int8 / L fp32)
+ 新增 phonemizeBopomofo() API
+ 新增 tokenizeV11() API
WASM 模块的 JavaScript API 同步升级,支持 Bopomofo 音素化接口,前端开发者可以更灵活地控制合成流程。
4.4 工程化改进
diff
+ 模型文件纳入 Git 管理 (S/M,<100MB)
+ 模型下载从本项目 Release 获取
+ 文档更新: README / README_EN / docs/wasm.md
+ Server/WASM 界面截图
+ 移除所有 tiandy 字样及外部仓库依赖
+ 移除开发环境私有路径
项目彻底清理了历史遗留的外部依赖和私有路径,现在完全自包含,开箱即用。
五、升级路径
从 v0.1.1 升级到 v0.1.2 非常简单:
bash
# 1. 下载新模型(以 M 档为例)
wget https://github.com/doiito/kokoroi-rs/releases/download/v0.1.2/kokoro-v1.1-zh-m.onnx \
-O models/kokoro-v1.1-zh-m.onnx
# 2. 更新配置文件 config.toml
# model_path = "models/kokoro-v1.1-zh-m.onnx"
# 3. 运行(CLI 自动使用新 G2P 流水线)
./koko --text "你好世界" --model models/kokoro-v1.1-zh-m.onnx
无需修改业务代码,新架构自动使用 Bopomofo G2P + ZH_VOCAB。
六、系统架构总览
#mermaid-svg-W9NncPsVFZ8lwfNZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W9NncPsVFZ8lwfNZ .error-icon{fill:#552222;}#mermaid-svg-W9NncPsVFZ8lwfNZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W9NncPsVFZ8lwfNZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .marker.cross{stroke:#333333;}#mermaid-svg-W9NncPsVFZ8lwfNZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W9NncPsVFZ8lwfNZ p{margin:0;}#mermaid-svg-W9NncPsVFZ8lwfNZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster-label text{fill:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster-label span{color:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster-label span p{background-color:transparent;}#mermaid-svg-W9NncPsVFZ8lwfNZ .label text,#mermaid-svg-W9NncPsVFZ8lwfNZ span{fill:#333;color:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .node rect,#mermaid-svg-W9NncPsVFZ8lwfNZ .node circle,#mermaid-svg-W9NncPsVFZ8lwfNZ .node ellipse,#mermaid-svg-W9NncPsVFZ8lwfNZ .node polygon,#mermaid-svg-W9NncPsVFZ8lwfNZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .rough-node .label text,#mermaid-svg-W9NncPsVFZ8lwfNZ .node .label text,#mermaid-svg-W9NncPsVFZ8lwfNZ .image-shape .label,#mermaid-svg-W9NncPsVFZ8lwfNZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-W9NncPsVFZ8lwfNZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .rough-node .label,#mermaid-svg-W9NncPsVFZ8lwfNZ .node .label,#mermaid-svg-W9NncPsVFZ8lwfNZ .image-shape .label,#mermaid-svg-W9NncPsVFZ8lwfNZ .icon-shape .label{text-align:center;}#mermaid-svg-W9NncPsVFZ8lwfNZ .node.clickable{cursor:pointer;}#mermaid-svg-W9NncPsVFZ8lwfNZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .arrowheadPath{fill:#333333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W9NncPsVFZ8lwfNZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W9NncPsVFZ8lwfNZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W9NncPsVFZ8lwfNZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster text{fill:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ .cluster span{color:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W9NncPsVFZ8lwfNZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W9NncPsVFZ8lwfNZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-W9NncPsVFZ8lwfNZ .icon-shape,#mermaid-svg-W9NncPsVFZ8lwfNZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W9NncPsVFZ8lwfNZ .icon-shape p,#mermaid-svg-W9NncPsVFZ8lwfNZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W9NncPsVFZ8lwfNZ .icon-shape .label rect,#mermaid-svg-W9NncPsVFZ8lwfNZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W9NncPsVFZ8lwfNZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W9NncPsVFZ8lwfNZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W9NncPsVFZ8lwfNZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模型数据
核心引擎
前端
CLI
koko-cli
REST API
kokoros-server
WASM Browser
kokoros_bg.wasm
G2P 引擎
ChineseG2P
音素化
Phonemizer
Bopomofo 模式
Tokenization
ZH_VOCAB
ONNX 推理
OrtKoko
后处理
振幅阈值裁切
ONNX 模型
v1.1-zh S/M/L
发音人嵌入
voices-v1.0.bin
七、总结与展望
v0.1.2 是一次从音素表示到模型体系到音频后处理的全链路升级:
- G2P 引擎:IPA → Bopomofo,消除"外国人味道"
- 模型体系:三档可选(int4/int8/fp32),默认模型体积缩小 75%
- 音频后处理:从过处理到最小干预,消除爆音和嗡嗡声
- 各模块同步升级:CLI、Server、WASM 全部适配新架构
如果你曾在 kokoroi-rs 上遇到过中文语音不够自然的问题,现在值得重新体验一次。
项目持续迭代中,欢迎到 GitHub 仓库体验、提 Issue 或贡献代码。
本文基于 kokoroi-rs v0.1.2 版本更新整理,欢迎交流讨论。