我把 ESP32 小喇叭升级成了 TTS 播报版:网页输入文字,设备直接出声

ESP32我把小喇叭升级成了TTS播报版
\n

之前我做过一个本地版的小喇叭:网页里输入一个音频地址,ESP32 收到 MQTT 命令后去拉取 MP3,然后通过 MAX98357A 播放出来。

那个版本把最基础的链路跑通了:

  • 网页能发命令

  • PHP 能转 MQTT

  • ESP32 能收命令

  • ESP32 能播放 HTTP 音频

但后来我发现,只能播固定音频还不够。真正更有意思的,是让它能直接播报文本。

所以我继续往下做,把这个项目升级成了一个 TTS 播报版。

现在这版已经能做到:

  • 网页输入任意文本

  • PHP 负责分段和编排

  • Python 对接火山引擎 TTS

  • MQTT 下发播放命令

  • ESP32 拉取音频流并播放

也就是说,现在不是“网页点一下播一个 MP3”,而是“网页输入一句话,设备直接开口”。


这次我做成了什么

先说最终效果。

我现在这版的主链路是:

网页输入文本    ↓PHP 编排请求    ↓Python 调用火山引擎 TTS    ↓生成可播放 stream_url    ↓MQTT 下发给 ESP32    ↓ESP32 拉取 HTTP MP3 流    ↓MAX98357A 播放

整体上看,它已经不是一个单点功能,而是一条完整的链路:

  • 前端负责输入和触发

  • PHP 负责调度

  • Python 负责生成 TTS 流

  • MQTT 负责下发控制命令

  • ESP32 负责播放

这版我现在把它定义成:

v1.0.0-mqtt


这次用到的东西

硬件还是比较简单:

  • ESP32 开发板

  • MAX98357A I2S 功放模块

  • 小喇叭

  • 杜邦线

软件这边主要是:

  • 网页

  • PHP

  • Python

  • Mosquitto MQTT

  • 火山引擎 TTS

其实真正复杂的不是某一个点,而是把这些东西串起来以后,每一层都得对上。


为什么这个版本比之前复杂很多

之前那个本地版本,逻辑相对简单:

网页输入一个 MP3 地址    ↓PHP 发布 MQTT 命令    ↓ESP32 收到命令    ↓ESP32 播放音频

但 TTS 不一样。

因为这次不是提前就有一个音频文件,而是:

  • 我先输入一段文本

  • 服务端要先把它变成语音

  • 然后设备才能去播

看起来只多了一步“TTS”,但实际上整个稳定性难度一下就上来了。


我这次踩到的几个关键坑

1. 长文本整段播,很容易中途断

一开始我当然想的是最直接的方式:

  • 一整段文本

  • 一次 TTS

  • ESP32 一次性拉流播完

但真做了之后很快发现,长文本比短文本难太多。

不是因为 TTS 生成不了,而是因为:

  • 文本越长,播放时间越长

  • 播放时间越长,ESP32 越容易撞上 WiFi 抖动

  • 一旦 WiFi 抖动,HTTP 音频流就断

  • 最后就会出现播放到一半停掉

所以我后来意识到:

长文本真正的问题,不是“能不能生成”,而是“单条流拉得太久,网络一抖就容易断”。


2. MQTT 控制链和 HTTP 播放链不是一回事

这个也是我中间绕了很久的一件事。

一开始如果设备没播出来,很容易下意识觉得:

  • 是不是 TTS 有问题

  • 是不是 Python 没返回

  • 是不是页面没发出去

但后来我发现一定要把链路拆开看:

  • MQTT 是控制链

  • HTTP 是音频链

也就是说:

  • MQTT 负责告诉 ESP32 去播哪个地址

  • 真正的声音,是 ESP32 自己去拉那个 HTTP 流

只要这两条链混在一起想,就很容易判断错问题到底出在哪。


3. 插播没有我一开始想的那么简单

我本来很想做一个效果:

  • 第一条正在播

  • 第二条来了,马上插进去

  • 立刻切到下一条

但实际调试的时候发现,事情没那么顺。

ESP32 在播放 HTTP 流的时候,MQTT 并不总是稳定。也就是说,第二条命令并不一定能在播放过程中稳稳送到设备。

所以我后来没有把“强插播”当成这版的核心目标,而是先把“顺序播报稳定”做扎实。


我最后怎么收敛这个方案

既然整段长流容易断,那我后面就不再坚持“一整段一次性播完”。

最后我改成了:

1. PHP 自动断句分段

文本来了以后,先按这些标点优先断句:

  • 换行

如果某一句还是太长,再按这些继续切:

最后再用长度兜底。

也就是说,我不再让 ESP32 一次扛整段长文本,而是让它一段一段播。


2. PHP 提前预生成每一段 TTS

我最开始做顺序播报时,是这样的:

播完一段    ↓再去生成下一段 TTS    ↓再下发下一段

这样虽然稳了,但段与段之间停顿很明显。

所以我后来改成:

先把所有分段都生成好    ↓先发第一段    ↓ESP32 播完一段    ↓立刻发下一段

这样就把“播完后再临时生成”的等待时间省掉了。

虽然现在还不是完全无缝,但比之前那种“现播现做现等”顺很多。


3. ESP32 侧加本地音频缓冲

这个优化也很关键。

我最后保留的是:

  • HTTP 实时流

  • ESP32 本地缓冲

  • MQTT 只负责控制

具体来说,就是 ESP32 在拉取 HTTP 音频流时,不是直接把每个字节立刻喂给解码器,而是先用:

AudioFileSourceBuffer

做一层本地缓冲。

我现在这版缓冲用的是:

32KB

这一步的意义很明显:

  • 网络稍微抖一下,不至于立刻断

  • 播放会更稳

  • 长文本体验会明显好很多


我现在这版的最终结构

最后保留下来的结构就是:

用户文本    ↓PHP 自动断句分段    ↓PHP 预生成每一段 TTS stream_url    ↓MQTT 下发 tts + url    ↓ESP32 打开 HTTP stream    ↓AudioFileSourceBuffer 本地缓冲    ↓I2S 播放    ↓MAX98357A 输出声音

我现在对这套结构的判断是:

  • 它已经能工作

  • 它已经比最开始稳定很多

  • 它不是最极致的方案

  • 但它已经是一套完整跑通的链路


这版现在适合做什么

我觉得它现在比较适合这几类用途:

  • 本地语音提示

  • 设备播报

  • 物联网语音输出

  • 定时提醒

  • 一些简单的文字转语音硬件联动

尤其如果你本来就在做:

  • ESP32

  • MQTT

  • I2S 音频

  • 本地控制页面

那这套项目会很适合参考。


这版我还保留了哪些边界

这次我没有想把文章写成“什么都完美解决了”,因为真实情况不是这样。

1. 段与段之间还是会有空档

虽然我已经做了预生成,但本质上现在还是:

  • 一段一个流

  • 一段一个命令

  • 一段播完再切下一段

所以它不是完全无缝。

2. WiFi 环境还是很关键

我这次调下来感受很深的一点是:

播放稳定性不只是代码问题,也很吃现场网络环境。

如果 ESP32 到路由器这一层本身有抖动,再好的逻辑也会受影响。

3. 这版不主打强插播

这版我现在更看重的是:

  • 顺序播报稳定

  • 长文本可用

  • 整体链路清晰

而不是复杂的抢占式插播。


下一版我想继续做什么

我现在已经把这版定义成:

v1.0.0-mqtt

后面我还想继续往下迭代。

下一步我比较想试的是:

1. 继续优化传输层

当前这版还是:

  • MQTT 做控制

  • HTTP 做音频拉流

这套已经能工作,但播放稳定性还是比较依赖单条 HTTP 流和 WiFi 环境。

所以我后面想继续试试看:

如果部分链路改成 UDP 方向,会不会让实时性和稳定性再往前走一点。

这里我不会现在就说 UDP 一定更稳,因为它也会带来新的问题,比如:

  • 丢包

  • 重组

  • 缓冲策略

但它确实是我下一版想认真尝试的方向。

2. 看能不能把段间停顿再压短一点

现在已经比之前顺不少了,但我还想继续往“更自然一点”的方向做。


开源地址

这个项目我已经整理成仓库了:

GitHub:

https://github.com/Kaiii-create/kai-sound-tts

当前这个版本分支:

https://github.com/Kaiii-create/kai-sound-tts/tree/v1.0.0-mqtt

演示小视频:

 
 
 

 

 

最后

这次我最大的感受,不是“我又做了一个功能”,而是:

一个硬件项目真正难的地方,往往不是代码本身,而是把整条链路一层一层确认清楚。

比如:

  • 页面有没有把请求发出去

  • PHP 有没有真的下发 MQTT

  • Python 有没有真的生成 TTS

  • ESP32 有没有收到命令

  • ESP32 有没有真的打开流

  • WiFi 有没有中途抖动

  • I2S 和功放有没有真的在播

这些问题只要有一层没确认清楚,最后表面现象就可能完全像是另外一层出了问题。

但也正因为把这些问题一层一层拆开,我最后才把这个版本慢慢收敛出来。

对我来说,这个版本的意义不在于“它已经完美了”,而在于:

它已经不是一个想法了,而是一条真正跑通、而且已经能工作的链路。

如果你也在做类似的 ESP32 播报、语音提示、物联网语音交互项目,欢迎交流。后面我大概率还会继续往下做。

 

暂无评论,快来发表第一条评论吧!

📮 需求咨询