拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

纯局域网排队叫号,语音怎么在本地合成:从阿里云 API 到无显卡可跑的轻量 TTS

纯局域网排队叫号,语音怎么在本地合成:从阿里云 API 到无显卡可跑的轻量 TTS

医院、政务大厅、企事业单位的排队叫号,经常要求系统整套放在局域网里,业务服务器不能访问公网。语音播报如果还调用阿里云的在线合成接口,取号和叫号会在这一步失败:接口超时,音箱没有声音,窗口人员只能口头喊号。

我们在海之心排队叫号系统的一次私有化部署里碰到的就是这个问题。最后留下来的做法不复杂:合成服务放在内网,业务系统只认本地已经生成好的音频文件;机器没有独显时,换一个能在 CPU 上跑的轻量模型。下面按当时的取舍写,方便遇到同样限制的人直接对照。

现场限制其实就三条

客户机房的条件可以概括成三句话。

  1. 叫号服务器和音箱、大屏都在局域网,业务机没有出公网的权限。
  2. 这台机器没有独立显卡,内存也不宽裕,不能为了语音再加一台 GPU 服务器。
  3. 叫号是实时的。患者已经站在窗口前,不能等模型冷启动几十秒才出声。

所以问题不是“哪家云 TTS 效果更好”,而是:断网之后,这句话还能不能在几秒内变成一个可播放的音频文件。

阿里云接口为什么一进局域网就失效

原来的链路是业务系统把叫号文案发给阿里云语音合成,拿回 mp3,再交给大屏或音频转发器播放。这条链路依赖两件事:服务器能访问阿里云,以及 AccessKey 能换到 Token。

纯局域网里第一件事就不成立。域名解析、HTTPS 出网、Token 接口都会失败。把密钥配进内网机器也没有用,密钥解决的是鉴权,不是网络。

这时不要在业务代码里加重试。重试只会把叫号接口拖慢,窗口点一次叫号要空等十几秒。正确的分界是:播放只读本地文件;合成若要发生,就发生在内网,并且尽量发生在叫号之前。

第一版:把合成收成内网 HTTP 服务

我们先接了百度飞桨 PaddleSpeech,但没有把飞桨嵌进 PHP 进程。叫号系统是 Web 服务,模型加载重、占用高,塞进每次请求里会把站点拖死。

做法是单独起一个 TTS HTTP 服务,只暴露一个合成接口,例如本机19080端口:

POST /api/tts Content-Type: application/json { "text": "请1号张三到1号诊室就诊", "voice": "call-female", "format": "file" }

format=file时,响应体就是 wav。业务侧拿到二进制后写入uploads/speech/日期/,数据库只存相对路径。叫号、大屏、音箱都只认这个路径。

这样更换合成引擎时,业务表和播放逻辑不用改。飞桨、后面的轻量模型,甚至以后再换别的引擎,都是同一个 HTTP 契约:文本进去,音频出来。

飞桨能在 CPU 上跑,音质也够叫号。问题出在占用。客户这台机器没有独显,飞桨模型加载之后内存和 CPU 都偏高,和数据库、Web、打印、大屏推送挤在一起,高峰时不稳。语音是刚需,但不能为了语音把叫号主服务拖慢。

第二版:没有独显,就换参数更小的模型

第二步换成 Hojo-TTS-Light-40M。它是开源的轻量中文语音模型,走 ONNX Runtime,在 CPU 上推理,不要求独立显卡。官方模型体积大约两百多 MB,加载后内存大约在 1GB 这一量级,比飞桨那套小一截,适合和业务系统放在同一台局域网服务器上。

服务方式保持不变,只是换端口,避免和飞桨抢19080。我们用的是19081:

POST http://127.0.0.1:19081/api/tts

请求字段仍然是text、voice、format=file。业务配置里用一个开关切换合成源即可,例如:

合成源适用情况本机服务
阿里云在线服务器能出公网,或需要粤语无
百度飞桨内网,机器资源比较宽裕19080
Hojo-TTS-Light-40M内网,无独显,要省内存19081

普通话叫号用 Hojo 的内置中文女声就够。粤语不要硬用这个模型,它没有粤语音色;外网可用时,粤语仍走原来的阿里云接口。

切换之后有一条容易忽略:已经合成过的句子会继续用旧文件。只有新文案,或数据库里音频路径还是空的记录,才会走新引擎。否则会误以为“改了配置却没生效”。

预先生成,比当场合成更重要

模型再轻,叫号当口现算仍有风险:第一次加载慢,CPU 忙时一句要等好几秒,窗口体验就是“点了没声音”。

海之心排队叫号系统里把合成从播放里拆开,分成三步。

  1. 取号时只存文案。写一条语音记录,文本是“请1号张三到1号诊室就诊”,文件路径先空着。这一步失败也不能影响取号。
  2. 空闲时批量合成。定时任务扫描路径为空的记录,逐条请求本地 TTS,成功后把 wav 路径写回同一行。患者还在候诊时,文件通常已经在磁盘上。
  3. 叫号时只取文件。有文件就直接播放。没有文件才当场向本地服务补一次,当作兜底,而不是主路径。

定时任务要按记录主键回写。如果合成函数先查缓存、命中了别的句子就返回成功,这条空记录的路径仍然是空的,任务日志却显示成功,本地 TTS 服务窗口里看不到请求。我们踩过这个坑:页面提示合成了 1 条,数据库是空的,飞桨日志也没有新请求。

预生成还有一个好处:同一句文案只合成一次。重复叫号、过号重呼,都是在放磁盘上的文件,不再占用 CPU。

模型能出声之后,还要处理“听着不对”

局域网打通只是第一步。叫号文本短、数字多,轻量模型会在几个固定点上听着别扭。这三处都是文本和后处理能解决的,不必换模型。

数字按位读。阿拉伯数字1会被读成“十”,患者听到的是“请10号”。合成前把数字改成中文按位读:1读“一”,10读“一零”,A1读“诶一”。诊室号和排队号就分开了。

句尾多一个“啊”。句子没有句号时,模型常自己补一个语气词。文案末尾补上“。”即可,不要在业务文案里手写语气词。

人名和后半句之间停太久。“请1号张三”和“到1号诊室就诊”之间,模型会留大约半秒到一秒的空白,听起来像逗号。这是它自己的气口,不是程序拆成了两句。后处理只压缩过长静音,把气口收到大约 0.12 秒,语音波形本身不动。不要用变速把整段音频拉慢,音质会明显变差。

怎样算这套方案已经可用

可以按下面四条自测,都通过就可以交付。

  1. 拔掉业务服务器的外网后,取号、叫号仍然成功。
  2. 本地 TTS 进程在,POST /api/tts能返回 wav,文件头是RIFF。
  3. 定时任务跑完,对应记录的文件路径不再为空,音箱播放的是这个文件。
  4. 把 TTS 进程停掉再叫一个已经生成过的号,仍然有声音。这一条说明播放不再依赖合成服务活着。

第 4 条最容易被漏掉。预生成的价值就在这里:合成服务重启、模型加载的那几十秒里,已经取过号的患者仍然可以被叫到。

哪些场景不要照搬

  • 必须粤语、英语多音色,而且机器能出公网:继续用在线接口更省事。
  • 只有一台极小的工控机,连 1GB 内存都挤不出来:先把 TTS 放到局域网里的另一台普通 PC,业务机只访问它的 HTTP,不要和数据库挤在一起。
  • 文本每次都完全不同、又要求句句现场生成:预生成帮助有限,要单独评估 CPU 能否在叫号间隙算完。

排队叫号的文本其实高度重复,号码、姓名、诊室或窗口,都是有限组合。这种文本最适合“先落库、再预生成、叫号只播放”。

常见问题

纯局域网还能不能继续用阿里云语音?
不能。在线合成必须访问阿里云。密钥留在配置里也出不了声。要么给这台机器开通出网,要么改本地合成。

没有显卡能不能做叫号语音?
可以。飞桨在 CPU 上能跑,但占用较高。Hojo-TTS-Light-40M 用 ONNX 在 CPU 上推理,更适合和无独显的业务服务器放在一起。

叫号当时现合成和预先生成怎么选?
预先生成做主路径,现合成只做兜底。患者取号到被叫之间通常有等待时间,足够把 wav 写好。

换了合成引擎,旧的叫号语音会自动重做吗?
不会。已有音频路径的句子会继续播放旧文件。需要新引擎时,只对路径为空的记录重新合成。


这次改造用在海之心排队叫号系统的局域网部署上:阿里云接口负责能出公网的环境,飞桨和 Hojo 负责纯内网,预生成负责把播放和推理分开。若你的叫号或排队系统也卡在“内网不能访问云 TTS”,可以按同一条边界处理,不必把模型绑进业务进程。

返回列表