多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

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

纯局域网排队叫号,语音怎么在本地合成:从阿里云 API 到无显卡可跑的轻量 TTS 医院、政务大厅、企事业单位的排队叫号经常要求系统整套放在局域网里业务服务器不能访问公网。语音播报如果还调用阿里云的在线合成接口取号和叫号会在这一步失败接口超时音箱没有声音窗口人员只能口头喊号。我们在海之心排队叫号系统的一次私有化部署里碰到的就是这个问题。最后留下来的做法不复杂合成服务放在内网业务系统只认本地已经生成好的音频文件机器没有独显时换一个能在 CPU 上跑的轻量模型。下面按当时的取舍写方便遇到同样限制的人直接对照。现场限制其实就三条客户机房的条件可以概括成三句话。叫号服务器和音箱、大屏都在局域网业务机没有出公网的权限。这台机器没有独立显卡内存也不宽裕不能为了语音再加一台 GPU 服务器。叫号是实时的。患者已经站在窗口前不能等模型冷启动几十秒才出声。所以问题不是“哪家云 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 }formatfile时响应体就是 wav。业务侧拿到二进制后写入uploads/speech/日期/数据库只存相对路径。叫号、大屏、音箱都只认这个路径。这样更换合成引擎时业务表和播放逻辑不用改。飞桨、后面的轻量模型甚至以后再换别的引擎都是同一个 HTTP 契约文本进去音频出来。飞桨能在 CPU 上跑音质也够叫号。问题出在占用。客户这台机器没有独显飞桨模型加载之后内存和 CPU 都偏高和数据库、Web、打印、大屏推送挤在一起高峰时不稳。语音是刚需但不能为了语音把叫号主服务拖慢。第二版没有独显就换参数更小的模型第二步换成 Hojo-TTS-Light-40M。它是开源的轻量中文语音模型走 ONNX Runtime在 CPU 上推理不要求独立显卡。官方模型体积大约两百多 MB加载后内存大约在 1GB 这一量级比飞桨那套小一截适合和业务系统放在同一台局域网服务器上。服务方式保持不变只是换端口避免和飞桨抢19080。我们用的是19081POST http://127.0.0.1:19081/api/tts请求字段仍然是text、voice、formatfile。业务配置里用一个开关切换合成源即可例如合成源适用情况本机服务阿里云在线服务器能出公网或需要粤语无百度飞桨内网机器资源比较宽裕19080Hojo-TTS-Light-40M内网无独显要省内存19081普通话叫号用 Hojo 的内置中文女声就够。粤语不要硬用这个模型它没有粤语音色外网可用时粤语仍走原来的阿里云接口。切换之后有一条容易忽略已经合成过的句子会继续用旧文件。只有新文案或数据库里音频路径还是空的记录才会走新引擎。否则会误以为“改了配置却没生效”。预先生成比当场合成更重要模型再轻叫号当口现算仍有风险第一次加载慢CPU 忙时一句要等好几秒窗口体验就是“点了没声音”。海之心排队叫号系统里把合成从播放里拆开分成三步。取号时只存文案。写一条语音记录文本是“请1号张三到1号诊室就诊”文件路径先空着。这一步失败也不能影响取号。空闲时批量合成。定时任务扫描路径为空的记录逐条请求本地 TTS成功后把 wav 路径写回同一行。患者还在候诊时文件通常已经在磁盘上。叫号时只取文件。有文件就直接播放。没有文件才当场向本地服务补一次当作兜底而不是主路径。定时任务要按记录主键回写。如果合成函数先查缓存、命中了别的句子就返回成功这条空记录的路径仍然是空的任务日志却显示成功本地 TTS 服务窗口里看不到请求。我们踩过这个坑页面提示合成了 1 条数据库是空的飞桨日志也没有新请求。预生成还有一个好处同一句文案只合成一次。重复叫号、过号重呼都是在放磁盘上的文件不再占用 CPU。模型能出声之后还要处理“听着不对”局域网打通只是第一步。叫号文本短、数字多轻量模型会在几个固定点上听着别扭。这三处都是文本和后处理能解决的不必换模型。数字按位读。阿拉伯数字1会被读成“十”患者听到的是“请10号”。合成前把数字改成中文按位读1读“一”10读“一零”A1读“诶一”。诊室号和排队号就分开了。句尾多一个“啊”。句子没有句号时模型常自己补一个语气词。文案末尾补上“。”即可不要在业务文案里手写语气词。人名和后半句之间停太久。“请1号张三”和“到1号诊室就诊”之间模型会留大约半秒到一秒的空白听起来像逗号。这是它自己的气口不是程序拆成了两句。后处理只压缩过长静音把气口收到大约 0.12 秒语音波形本身不动。不要用变速把整段音频拉慢音质会明显变差。怎样算这套方案已经可用可以按下面四条自测都通过就可以交付。拔掉业务服务器的外网后取号、叫号仍然成功。本地 TTS 进程在POST /api/tts能返回 wav文件头是RIFF。定时任务跑完对应记录的文件路径不再为空音箱播放的是这个文件。把 TTS 进程停掉再叫一个已经生成过的号仍然有声音。这一条说明播放不再依赖合成服务活着。第 4 条最容易被漏掉。预生成的价值就在这里合成服务重启、模型加载的那几十秒里已经取过号的患者仍然可以被叫到。哪些场景不要照搬必须粤语、英语多音色而且机器能出公网继续用在线接口更省事。只有一台极小的工控机连 1GB 内存都挤不出来先把 TTS 放到局域网里的另一台普通 PC业务机只访问它的 HTTP不要和数据库挤在一起。文本每次都完全不同、又要求句句现场生成预生成帮助有限要单独评估 CPU 能否在叫号间隙算完。排队叫号的文本其实高度重复号码、姓名、诊室或窗口都是有限组合。这种文本最适合“先落库、再预生成、叫号只播放”。常见问题纯局域网还能不能继续用阿里云语音不能。在线合成必须访问阿里云。密钥留在配置里也出不了声。要么给这台机器开通出网要么改本地合成。没有显卡能不能做叫号语音可以。飞桨在 CPU 上能跑但占用较高。Hojo-TTS-Light-40M 用 ONNX 在 CPU 上推理更适合和无独显的业务服务器放在一起。叫号当时现合成和预先生成怎么选预先生成做主路径现合成只做兜底。患者取号到被叫之间通常有等待时间足够把 wav 写好。换了合成引擎旧的叫号语音会自动重做吗不会。已有音频路径的句子会继续播放旧文件。需要新引擎时只对路径为空的记录重新合成。这次改造用在海之心排队叫号系统的局域网部署上阿里云接口负责能出公网的环境飞桨和 Hojo 负责纯内网预生成负责把播放和推理分开。若你的叫号或排队系统也卡在“内网不能访问云 TTS”可以按同一条边界处理不必把模型绑进业务进程。
返回列表