TTS 服务对接文档
本文说明 apps/callflow-esl 如何对接本地 sherpa-tts-server,以及业务侧 ctx.speak({ kind: "tts" }) 到 FreeSWITCH 播放之间的实际调用链。
当前 callflow-esl 不直接调用 FreeSWITCH 的 speak application。普通 TTS 先请求 sherpa-tts-server,拿到可播放的 WAV URL、本地 WAV 路径或 PCM / MP3 流式 URL 后,再通过 ESL 下发 FreeSWITCH playback。
调用链概览
text
业务代码
ctx.speak({ kind: "tts", text, speakerId?, speed? })
|
v
callflow-esl runtime
resolvePlaybackPlan()
|
+-- tts profile = wav --> POST /tts
| 返回 wavUrl/fileName
|
+-- tts profile = stream --> POST /tts-stream
返回 streamUrl
|
v
FreeSWITCH playback
wav-url : playback http://.../wav/tts-xxx.wav
file-path : playback /path/to/tts-xxx.wav
stream : playback audio_fork://http://host:port/tts-stream/xxx.pcm?rate=16000&channels=1相关实现入口:
src/runtime/tts/playback-planner.ts:把SpeakInput转成 FreeSWITCH playback 计划src/runtime/tts/tts-client.ts:请求sherpa-tts-server,校验响应并生成 playback 目标src/types/types.ts:TtsConfig、TtsSpeakInput等公开类型
callflow-esl 配置
字段、当前示例值和路径说明以 配置参考 为准。tts.use.call 选择单呼使用的 wav 或 stream profile;tts.use.conference 在 audio_fork:// 驱动下可使用 stream,若为 shout:// 则必须使用 wav。stream 是流式 profile 名称,实际播放协议由 tts.profiles.stream.playbackPrefix 决定:当前推荐为 audio_fork://,也兼容 shout://。
普通 WAV 模式
当当前场景选择 wav profile 时, runtime 会调用:
text
POST {tts.profiles.wav.endpoint}
Content-Type: application/json请求体:
json
{
"text": "欢迎测试语音合成",
"speakerId": 0,
"speed": 1.1
}字段来源:
text来自业务侧ctx.speak({ kind: "tts", text })speakerId优先使用业务入参,未传则使用config.tts.defaults.speakerIdspeed优先使用业务入参,未传则使用config.tts.defaults.speed
callflow-esl 期望响应:
json
{
"wavUrl": "http://127.0.0.1:9080/wav/tts-3a1c7b9eaf0c1d72.wav",
"fileName": "tts-3a1c7b9eaf0c1d72.wav",
"sampleRate": 16000,
"cached": false
}必需字段:
wavUrl:不能为空。playbackTarget=wav-url时会直接交给 FreeSWITCHplaybackfileName:不能为空。playbackTarget=file-path时会和fsPlaybackBaseDir拼接
诊断字段:
sampleRate:只写入日志,不参与播放决策cached:只写入日志,用于判断是否命中 TTS 服务端缓存
普通 WAV 播放目标
playbackTarget=wav-url 时:
text
FreeSWITCH playback http://127.0.0.1:9080/wav/tts-xxx.wav适用场景:
- FreeSWITCH 能直接访问
wavUrl sherpa-tts-server的config.toml中的server.publicBaseUrl已配置成 FreeSWITCH 可达地址- 不希望 callflow-esl 与 FreeSWITCH 共享文件目录
playbackTarget=file-path 时:
text
fsPlaybackBaseDir = "/usr/local/freeswitch/sounds/tts"
fileName = "tts-xxx.wav"
FreeSWITCH playback /usr/local/freeswitch/sounds/tts/tts-xxx.wav适用场景:
sherpa-tts-server生成的 WAV 文件目录与 FreeSWITCH 同机或共享卷- FreeSWITCH 通过本地文件系统播放比 HTTP 拉取更稳定
注意事项:
playbackTarget=file-path时fsPlaybackBaseDir不能为空,否则 runtime 会抛错- 拼接后的路径会统一使用
/,FreeSWITCH 在 Windows 上也可接受正斜杠路径 callflow-esl不校验文件是否真实存在,FreeSWITCH 必须能访问该路径
流式 TTS 模式
当当前场景选择 stream profile 时, runtime 不等待完整 WAV 合成,而是调用:
text
POST {tts.profiles.stream.endpoint}
Content-Type: application/json请求体与 /tts 相同:
json
{
"text": "欢迎测试语音合成",
"speakerId": 0,
"speed": 1.1,
"format": "pcm"
}callflow-esl 期望响应:
json
{
"streamUrl": "http://127.0.0.1:9080/tts-stream/3a1c7b9eaf0c1d72.pcm",
"streamPath": "/tts-stream/3a1c7b9eaf0c1d72.pcm",
"sampleRate": 16000,
"cached": false
}必需字段:
streamUrl:不能为空,会被转换成 FreeSWITCHplayback目标
诊断字段:
streamPath:只写入日志,便于关联 TTS 服务端请求sampleRate:写入日志;使用audio_fork://时也用于播放目标中的rate参数cached:服务端可能返回,但当前callflow-esl不依赖该字段
当 profiles.stream.playbackPrefix 配置为 "audio_fork://" 时(强力推荐,原生 HTTP Chunked PCM16 流平替 mod_shout):
- 请求端自动协商:
callflow-esl在调用POST /tts-stream时会自动传入{"format": "pcm"},请求sherpa-tts-server输出原生裸 PCM16 HTTP Chunked 流链接(如/tts-stream/abc.pcm); - 零编解码与极速起播:服务端直接在内存中将 TTS 浮点音频量化为 PCM16 输出,彻底消除 ffmpeg 进程拉起与 MP3 压缩/解压延迟,首包延迟进一步压降 200~300ms,CPU 消耗降为零;
- 播放目标自动组装:runtime 自动组装目标字符串为
audio_fork://${streamUrl}?rate=${sampleRate}&channels=1; - 底层安全驱动:FreeSWITCH 通过
mod_audio_fork原生SWITCH_FILE_INTERFACE文件驱动播放 PCM16 流,安全打断且无阻塞,服务端零 ffmpeg 依赖、无需 MP3 编解码。
配置 profiles.stream.playbackPrefix="shout://" 时(MP3 模式):
- runtime 会把 HTTP URL 转成 FreeSWITCH
mod_shout可播放的 URL:
text
http://127.0.0.1:9080/tts-stream/abc.mp3
|
v
shout://127.0.0.1:9080/tts-stream/abc.mp3如果 playbackPrefix 为空,则直接把 streamUrl 交给 FreeSWITCH。如果配置成其他非空前缀,则按字符串前缀拼接。
流式模式依赖:
- 使用
audio_fork://前缀时:FreeSWITCH 需加载mod_audio_fork,并能访问streamUrl;sherpa-tts-server零ffmpeg依赖; - 使用
shout://前缀时:FreeSWITCH 需加载mod_shout,sherpa-tts-server需要能找到ffmpeg。
sherpa-tts-server 侧要求
TTS 服务的监听地址、编码器和预缓冲配置以 sherpa-tts-server README 及其 config.toml 为准。server.publicBaseUrl 必须是 FreeSWITCH 可访问的地址。
POST /tts 生成 WAV 并返回播放 URL;POST /tts-stream 创建流式链接,FreeSWITCH 随后拉取 .pcm 或 .mp3。使用 audio_fork:// 时需加载 mod_audio_fork;使用 shout:// 时需加载 mod_shout,TTS 服务还需可用的 ffmpeg。
会议播放推荐配合 audio_fork:// 原生 PCM16 流使用 stream profile,安全打断且无阻塞;若流式驱动仍为 shout://,则会议必须使用 wav profile,避免 mod_shout 关闭流时阻塞会议混音线程。单呼场景无论何种驱动均可自由选择 stream 或 wav profile。
业务侧用法
独立播报:
ts
await ctx.speak({
kind: "tts",
text: "欢迎致电,请说出您的需求。"
});覆盖说话人和语速:
ts
await ctx.speak({
kind: "tts",
text: "语速稍快的提示音。",
speakerId: 0,
speed: 1.2
});允许被 barge-in 打断:
ts
await ctx.speak({
kind: "tts",
text: "您可以在播报过程中直接说话。",
interruptible: true
});作为 hear() 的提示音:
ts
const result = await ctx.hear({
prompt: {
kind: "tts",
text: "请说出您的需求。"
},
interruptible: true,
timeoutMs: 30000
});输入约束:
text去空白后不能为空,否则 runtime 会抛SpeakInput.text must not be emptyrepeat必须是正整数speakerId、speed仅影响本次请求,不会修改全局配置
日志与错误
普通 /tts 成功时,callflow-esl 写入:
text
tts_http_resolved常见字段包括:
endpointtextLengthspeakerIdspeedwavUrlfileNamesampleRatecachedelapsedMs
普通 /tts 失败时,写入:
text
tts_http_failed并向上抛出:
text
sherpa-http tts 失败: <message>流式 /tts-stream 成功时,写入:
text
tts_stream_link_resolved流式 /tts-stream 失败时,写入:
text
tts_stream_link_failed并向上抛出:
text
sherpa-http tts stream 失败: <message>常见失败原因:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
<时长>ms 后超时 | TTS 服务未响应或合成耗时超过 requestTimeoutMs | 确认服务健康、调大 requestTimeoutMs、缩短单次文本 |
HTTP 400 Bad Request | 请求体不合法,例如文本为空或 speed <= 0 | 检查业务传入的 text、speed |
HTTP 503 Service Unavailable | TTS 服务端队列已满 | 降低并发或调大 maxQueuedRequests / ttsPoolSize |
响应缺少 wavUrl | /tts 响应缺少 wavUrl | 检查 TTS 服务端响应结构 |
响应缺少 fileName | /tts 响应缺少 fileName | 检查 TTS 服务端响应结构 |
stream 响应缺少 streamUrl | /tts-stream 响应缺少 streamUrl | 检查流式接口响应结构 |
| FreeSWITCH 播放 HTTP WAV 失败 | publicBaseUrl 对 FreeSWITCH 不可达 | 改成 FreeSWITCH 可访问的 IP/域名 |
| FreeSWITCH 播放流式 MP3 失败 | 未启用 mod_shout 或 ffmpeg 不可用 | 启用 mod_shout,确认 TTS 服务端 [mp3].encoderPath |
联调检查清单
- 启动
sherpa-tts-server,确认GET /health返回status=ok - 直接请求
/tts,确认返回wavUrl、fileName,并且浏览器或 curl 能访问wavUrl - 若启用流式模式,直接请求
/tts-stream,确认返回streamUrl - 在 FreeSWITCH 所在机器上访问
wavUrl或streamUrl,确认网络可达 - 流式模式按播放前缀确认 FreeSWITCH 已加载
mod_audio_fork或mod_shout - 启动
callflow-esl后通过测试业务执行ctx.speak({ kind: "tts" }) - 查看
callflow-esl日志中的tts_http_resolved或tts_stream_link_resolved - 若任务中启动了任何服务,联调结束后停止对应服务进程