文本转语音服务

一个纯JVM的文本转语音服务器,基于Kokoro-82M神经网络模型,支持单语音合成、多语音对话、语音混合和WAV/MP3输出。

  • by alexsobolev
  • Kotlin
  • 本地部署
  • 10 星标
  • 更新于
在 GitHub 查看 alexsobolev/kokoro-tts-kotlin

使用说明

Kokoro TTS Kotlin

一个纯JVM文本到语音服务器,由 科科罗-82M 神经TTS模型。在JVM上本机运行ONNX推理——没有Python,没有外部服务。通过Ktor和MCP端点为人工智能辅助集成提供REST API服务。

支持单语音合成、具有自然轮距的多语音对话、语音混合、外来词的内联音素注释以及24 kHz的WAV/MP3输出。

构建这个项目的整个过程——从模型研究和G2P工程到干净的架构、部署和性能调优——在 如何构建听起来不错的自托管TTS.

安装

先决条件

  • JDK 25+ --下载自 甲骨文 或通过安装 SDKMAN!:
    sdk install java 25-open
    
  • 卷曲 --数据下载脚本所需(预装在macOS/Linux上)
  • AWS凭据 (可选)——仅S3存储模式需要;本地模式不需要AWS设置

克隆和设置

git clone https://github.com/alexsobolev/kokoro-tts-kotlin.git
cd kokoro-tts-kotlin

下载模型文件

TTS管道需要模型权重、语音嵌入和发音词典(总共约400 MB)。该脚本跳过已存在的文件。

./scripts/download-data.sh

此下载到 data/ 目录:

文件 大小 来源 描述
kokoro-v1.0.int8.onnx 92.3毫巴 kokoro onnx 量化ONNX TTS模型
voices-v1.0.bin 28.2毫巴 kokoro onnx 语音风格嵌入
config.json 2.3 KB 科科罗-82M 标记器词汇
us_gold.json 3.0 MB 美咲 美式英语黄金发音格言
us_silver.json 3.0 MB 美咲 美式英语银色发音格言
gb_gold.json 2.8 MB 美咲 GB英语黄金发音格言
gb_silver.json 3.6 MB 美咲 GB英语银色发音词典
en-pos-perceptron.bin 3.9毫巴 Apache OpenNLP OpenNLP POS标签模型
lexicon_fixes.json 0.1 KB 本地 自定义发音覆盖

配置环境

默认情况下,服务器使用 本地存储 --音频文件被写入 output/ 目录,并通过HTTP提供服务。无需AWS凭据。

对于S3存储,设置:

export STORAGE_MODE=s3
export AWS_REGION=eu-central-1
export S3_BUCKET=my-tts-bucket

看 配置 对于所有可用设置。

构建和验证

./gradlew build    # Compile, lint, static analysis, and tests

快速开始

下载模型和词典文件(需要一次;请参阅 下载模型文件):

./scripts/download-data.sh

然后运行服务器:

./gradlew :app:run

服务器在端口上启动 8080 使用本地文件存储(不需要AWS)。音频文件保存到 output/ 并在 http://localhost:8080/audio/....Swagger用户界面位于 /swaggerOpenAPI规范 /openapi.

API

合成语音

POST /v1/tts

单一声音:

{
  "turns": [
    { "voice": "af_heart", "text": "Hello, world!" }
  ],
  "speed": 1.0,
  "format": "wav"
}

多语音对话 (与随机250-500ms静默间隙连接):

{
  "turns": [
    { "voice": "af_heart", "text": "How are you today?" },
    { "voice": "am_adam", "text": "I am doing great, thanks for asking!" }
  ],
  "speed": 1.2,
  "format": "mp3"
}

语音混合 (样式嵌入的加权平均值,权重之和必须为1.0):

{
  "turns": [
    { "voice": "af_heart:0.6+af_bella:0.4", "text": "A blended voice." }
  ]
}

内联音素注释 对于外来词和专有名词:

{
  "turns": [
    { "voice": "af_heart", "text": "We visited (Machu Picchu)[mˈɑːtʃuː pˈiːtʃuː] in (Peru)[pəɹˈuː]." }
  ]
}

响应(本地模式):

{
  "url": "http://localhost:8080/audio/af_heart/uuid.wav",
  "key": "af_heart/uuid.wav",
  "expiresInSeconds": 0,
  "sizeBytes": 48044,
  "format": "wav",
  "voice": "af_heart"
}

默认值: speed = 1.0, format =“wav”, voice =“af_heart”。

限制: 速度为[0.5,2.0],每圈文字<=5000个字符,至少一圈。

其他终点

端点 描述
GET /health 健康检查(退货 OK)
GET /v1/voices 列出带有ID和语言的可用语音
/mcp 用于AI辅助集成(SSE传输)的MCP端点
/swagger 交互式API文档

错误

状态 条件
400 文本太长,速度超出范围,对话为空,JSON格式错误
404 找不到语音
500 推断或存储故障

MCP集成

Ktor服务器和Lambda都公开了MCP(模型上下文协议)端点,使Claude Desktop等AI助手能够将语音合成为工具调用。

工具:

  • list_voices --返回所有语音ID和语言
  • synthesize_speech --具有文本、语音、速度、格式参数的单语音合成
  • synthesize_dialogue --每回合有不同声音的多回合对话

工具描述指导LLM将外来专有名词包装在 (word)[IPA] 正确发音的注释。

MCP检验员测试: 连接到 http://localhost:8080/mcp (苏格兰和南方能源公司运输)。

克劳德桌面版 (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "kokoro-tts": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:8080/mcp"]
    }
  }
}

建筑

五个Gradle模块的简洁架构:

  • 领域 --纯值类型(VoiceId, SpeechRate, AudioFormat, SynthesisException),零依赖性
  • 核心 --端口接口(PhonemeGenerator, InferenceEngine, AudioEncoder, VoiceRepository, AudioStorage)DTO、用例、TTS服务编排
  • 基础设施 --适配器:ONNX推理、POS感知G2P(OpenNLP+misaki字典)、WAV/MP3编码、本地/S3存储、MCP服务器工厂
  • 应用 --Ktor HTTP层与Koin DI组合
  • λ --带有单例冷启动初始化的AWS Lambda处理程序

TTS管道

Text --> EnglishPhonemeGenerator --> KokoroTokenizer --> OnnxKokoroEngine --> SentencePostProcessor --> LocalAudioEncoder --> AudioStorage
         (POS-aware G2P)            (IPA -> tokens)     (ONNX @ 24kHz)      (volume envelopes)        (WAV/MP3)            (local disk or S3)

G2P(从字素到音素)

EnglishPhonemeGenerator 使用POS感知混合方法匹配将英语文本转换为IPA音素 美咲的逻辑。四个misaki JSON字典在启动时合并为一个(US gold>US silver>GB gold>GB silver) PosAwareLexicon 保留每个POS的发音变体(例如,“live”作为形容词 lˈIv vs动词 lˈɪv“record”作为名词 ɹˈɛkəɹd vs动词 ɹəkˈɔɹd).A. lexicon_fixes.json 更正文件以最高优先级深度合并,修复了3个上游misaki VBP错误(读取、重读、伤口将现在时态VBP映射到过去时发音)。

双通道管道:

  1. 第一次通过 --POS标记所有代币 Apache OpenNLP (感知器模型、Penn Treebank标签、保留原始大小写以提高标签准确性),通过具有形态词干和字母规则回退的POS感知词典查找来解析音素
  2. 第二遍 --反向扫描音素进行计算 futureVowel 上下文,应用虚词覆盖(“the”→ ði/ðə“to”→ tʊ/tə/tu),重新查找带有重音的句子末尾单词 None-关键变体

单词分辨率 (按优先顺序):

  1. 内联音素注释 — (word)[IPA] 语法绕过了整个G2P管道
  2. 收缩扩张 --例如,在音素化之前,“I'm”被扩展为“I'am”
  3. 上下文相关虚词 --“the”使用 ði 元音之前/ ðə 辅音之前;“to”使用 tʊ 元音之前/ tə 辅音之前/ tu 句末
  4. 缩写 --包含2+个大写字母的单词逐字母拼写
  5. 数量扩展 --整数、小数、前导零序列扩展为单词
  6. POS感知字典查找 --基于父标签规范化(VBD)的POS标签选择正确的变体→动词,NN→名词,JJ→ADJ,RB→ADV)和句子最终重音形式 None 钥匙
  7. 形态堵塞 --复数、过去时、进行式、状语、agent、带有美式英语T-flaping的私有后缀(词干结尾为“T”)→ 'ɾ在-ed/-ing元音之前)
  8. 复合分词 --尝试所有拆分位置(最少3个字符),降低第二部分的压力
  9. 字母到音素回退 --约100个英语字形模式贪婪地与上下文敏感的元音规则相匹配

语调后处理

Kokoro模型并不能通过标点符号很好地区分语调。 TtsService 和 SentencePostProcessor 补偿:

  • 提问(?) --0.92倍速度;最后600ms的音量上升斜坡(1.0->1.15x,二次)。在最后一个子句边界处拆分的多子句问题
  • 感叹词(!) --前400ms增益提升(1.20x->1.0,线性衰减)
  • 声明 --未修改

RMS窗口语音边界检测可确保音量效果目标语音内容,而不是模型生成的静音。

语音混合

混合语音(例如。, af_heart:0.6+af_bella:0.4)通过对256维样式嵌入进行加权平均来创建。混合后,结果向量被L2重整化为输入范数的加权平均值——否则,混合向量的幅度较小,产生的音频质量下降。

ONNX推断

  • 第一次调用时加载延迟会话,而不是在启动时加载
  • 音素序列截断为510个令牌(模型的512个上下文窗口减去BOS/EOS)
  • 每段10ms淡入/淡出,消除边界处的点击伪影
  • 所有可用的CPU线程通过 setIntraOpNumThreads()

部署

Docker(Ktor服务器)

docker build -t kokoro-tts .
docker run -p 8080:8080 kokoro-tts                          # local storage (default)
docker run -p 8080:8080 \
  -e STORAGE_MODE=s3 \
  -e AWS_REGION=eu-central-1 \
  -e S3_BUCKET=my-tts-bucket \
  kokoro-tts                                                # S3 storage

非root用户,3GB堆, ExitOnOutOfMemoryError。使用Gradle依赖缓存的多阶段构建。

拉姆达

docker build -f Dockerfile.lambda -t kokoro-tts-lambda .

自定义JDK 25运行时(超出AWS管理的运行时),ONNX模型的8 GB堆。Koin DI在每个容器冷启动时初始化一次。自动检测和解码来自Lambda函数URL的base64请求体。

配置

app/src/main/resources/application.yaml:

tts:
  tokenizer:
    configPath: "data/config.json"
  voices:
    path: "data/voices-v1.0.bin"
  phonemizer:
    goldDictPath: "data/us_gold.json"
    silverDictPath: "data/us_silver.json"
    gbGoldDictPath: "data/gb_gold.json"
    gbSilverDictPath: "data/gb_silver.json"
    fixesDictPath: "data/lexicon_fixes.json"
  pos:
    modelPath: "data/en-pos-perceptron.bin"
  model:
    onnxPath: "data/kokoro-v1.0.int8.onnx"
  aws:
    region: "$AWS_REGION:"
    s3Bucket: "$S3_BUCKET:"
  storage:
    mode: "$STORAGE_MODE:local"
    prefix: "$STORAGE_PREFIX:tts-audio"
    localOutputDir: "$LOCAL_OUTPUT_DIR:output"
    baseUrl: "$BASE_URL:http://localhost:8080"

所有设置都支持使用Ktor的环境变量覆盖 $ENV_VAR:default 语法。Lambda处理程序直接从环境变量中读取相同的设置。

变量 默认值 描述
STORAGE_MODE local 存储后端: local (磁盘+HTTP)或 s3
LOCAL_OUTPUT_DIR output 本地音频文件目录
BASE_URL http://localhost:8080 本地音频下载链接的公共基础URL
AWS_REGION -- AWS区域(需要 s3 模式)
S3_BUCKET -- S3铲斗(需要 s3 模式)
STORAGE_PREFIX tts-audio S3对象密钥前缀

建筑

./gradlew build              # Compile + ktlint + detekt + tests
./gradlew :app:run           # Dev server on port 8080
./gradlew buildFatJar        # Fat JAR at app/build/libs/app-all.jar
./gradlew ktlintFormat       # Auto-format code
./gradlew koverHtmlReport    # Merged code coverage report → build/reports/kover/html/
./gradlew koverXmlReport     # Merged XML coverage report (for CI)

代码质量

构建运行 ktlint (格式化), 检测 (静态分析),所有测试都作为一个门进行。 Kover 强制执行最低限度 85% 所有五个模块的行覆盖率,并在根级别进行合并报告。所有测试都遵循给定的when-then模式 // given, // when, // then 部分评论。

看 指南.md 了解详细的编码约定、架构规则和设计决策。

技术栈

Kotlin 2.3.0、JDK 25、Ktor 3.4.0、Koin 4.1.1、ONNX Runtime 1.23.2、Apache OpenNLP 2.5.3、kotlinx.serialization 1.8.1、AWS Kotlin SDK 1.6.12、MCP SDK 0.8.4、jump3r(LAME MP3编码器)。