跳到正文

集成与运行

调用示例

curl

bash
curl -s -X POST http://127.0.0.1:9080/tts \
  -H "Content-Type: application/json" \
  -d '{"text":"测试语音合成","speakerId":0,"speed":1.0}'

Node / Bun

仓库内提供了 request-tts.js,兼容 Node 18+ 和 Bun:

powershell
bun run .\request-tts.js
bun run .\request-tts.js "测试语音合成"
bun run .\request-tts.js "测试语音合成" http://127.0.0.1:9080/tts

脚本会打印完整 JSON 响应。等价的 fetch 调用:

js
const res = await fetch("http://127.0.0.1:9080/tts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text: "测试语音合成" }),
});
const result = await res.json();
console.log(result.wavUrl);

FreeSWITCH 集成(callflow-esl)

callflow-esl 通过 tts.defaultstts.usetts.profiles 选择本服务的普通 WAV 或 流式 MP3 端点:

json
{
  "tts": {
    "defaults": {
      "speakerId": 0,
      "speed": 1,
      "requestTimeoutMs": 10000
    },
    "use": {
      "call": "shout",
      "conference": "wav"
    },
    "profiles": {
      "wav": {
        "endpoint": "http://127.0.0.1:9080/tts",
        "playbackTarget": "wav-url",
        "fsPlaybackBaseDir": ""
      },
      "shout": {
        "endpoint": "http://127.0.0.1:9080/tts-stream",
        "playbackPrefix": "shout://"
      }
    }
  }
}

业务调用 ctx.speak({ kind: "tts", text: "..." }) 时,runtime 会:

  1. runtime 按通话场景从 tts.use 解析 profile
  2. wav profile 向本服务 POST /tts 获取 wavUrl
  3. shout profile 向本服务 POST /tts-stream 获取 streamUrl
  4. 通过 ESL 给 FreeSWITCH 下发 playback,流式模式默认使用 shout://<host>/<path> 交给 mod_shout 拉取

输出

text
target\win_x64\public\wav\

所有生成的 wav 文件按 tts-<hash>.wav 命名落盘到这里。publicBaseUrl 对外暴露的 /wav/... 实际就读取这个目录。

并发模型

  • accept 线程在主线程,单线程接受连接
  • 入队 queue_,由 workerThreads 个工作线程消费
  • 工作线程解析 HTTP、调用 TtsService::Synthesize
  • 合成时通过 OfflineTtsPool::Acquire() 拿到一个 OfflineTts 实例 独占完成 Generate + WriteWave,结束后归还
  • 缓存命中或复用进行中的合成时不占用 TTS 实例,仅做 I/O

并发吞吐主要取决于:

  • ttsPoolSize:实际并行合成度
  • ttsModelNumThreads:单条请求的推理线程数,通常更直接影响该请求的 RTF
  • 模型本身的 RTF(输出 rtf= 日志可对比)
  • workerThreads:HTTP 处理线程数,通常 >= ttsPoolSize 即可

降低单条请求 RTF 时,优先调高 ttsModelNumThreads 并相应降低 ttsPoolSize, 避免 ttsPoolSize * ttsModelNumThreads 明显超过可用 CPU 核心数。建议从 ttsModelNumThreads=2, ttsPoolSize=2 开始压测;如果机器核心数充足,再试 ttsModelNumThreads=4, ttsPoolSize=1。若业务文本可复用,命中 cached=true 后不会再进入模型推理,RTF 成本为 0。

日志

服务通过 stdout 输出运行日志,所有日志以 [sherpa_tts_server] 前缀开头。 关键事件:

  • ffmpeg MP3 encoder available encoderPath=<path>:启动探测成功,流式 MP3 编码器可用
  • ffmpeg MP3 encoder unavailable encoderPath=<path> reason=<reason>; ...:启动探测失败;服务仍继续启动,但 /tts-stream MP3 播放不可用,普通 /tts WAV 合成不受影响
  • loaded TTS worker in <ms> ms:单个 TTS 实例加载完成,包含 model_num_threads=<n>
  • listening on http://<host>:<port>:服务监听就绪
  • request start method=<method> path=<path> activeStreams=<n> queueSize=<n>:HTTP 请求开始处理
  • request end method=<method> path=<path> status=<code> elapsedMs=<n> activeStreams=<n> queueSize=<n> reason=<reason>:HTTP 请求处理结束
  • generated audio in <ms> ms (rtf=<n>) modelId=<id> modelBackend=<backend> modelDir=<dir> modelNumThreads=<n> modelFile=<file>:本次合成耗时、实时倍率和模型信息
  • request textLength=<n> cached=<bool> elapsedMs=<n> modelId=<id> modelBackend=<backend> modelDir=<dir> modelNumThreads=<n> modelFile=<file>:一次 /tts 请求摘要
  • streamed textLength=<n> cached=<bool> elapsedMs=<n> modelId=<id> modelBackend=<backend> modelDir=<dir> modelNumThreads=<n> modelFile=<file>:一次 /tts-stream 播放摘要
  • stream busy textLength=<n> activeStreams=<n> maxActiveStreams=<n>:流式 GET 超过并发上限,已返回 503
  • MP3 streaming encoder watchdog closed stream after streamMaxDurationMs=<n>:流式 GET 达到最长连接时间,服务端主动断开
  • ffmpeg MP3 ... exit timed out:ffmpeg 退出超时,服务已 kill 子进程并标记本次流失败
  • startup cache scanned <n> wav files:启动扫描结果

注意事项

  • 配置文件使用完整 JSON parser;HTTP 请求/响应仍保留轻量 JSON 解析和拼接
  • 缺少任何模型文件会在启动时抛 missing TTS model file: ...missing TTS model directory: ... 并退出
  • 服务加载配置后会用 mp3.encoderPath 完成一次最小 MP3 编码,并使用 timeouts.ffmpegExitMs 限制等待时间。程序无法启动、编码返回非零或超时只会 输出错误级别的功能受限告警,不阻止服务监听;此时 /tts-stream 的 MP3 播放链路不可用,普通 /tts WAV 合成不受影响
  • kokoro-multi-lang-v1_1 是 103 个说话人的多音色模型,切到对应 profile 后 speakerId 直接对应 /health 返回的 model.speakers[].id
  • 启动扫描无法可靠恢复原始 cache_key,但新的 /tts/tts-stream 请求会按相同 hash 查找磁盘上的同名 wav,命中时会补回索引并复用缓存
  • 服务没有平滑关闭:Ctrl+C 会直接终止 accept 与工作线程
  • 当前没有鉴权、没有限流(除 maxQueuedRequests),建议仅在内网/可信网络运行

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。