概念科普

同一句话为什么不该重复生成 MP3:拆分语音资产与播放任务

本地语音平台最初每次发送相同文字都会重新调用 TTS,并生成一份重复 MP3。本文从一次真实重复文件问题出发,解释为什么“语音资产”和“播放任务”必须分开,以及缓存键、历史试听、再次发送和旧数据库迁移应该怎样配合。

本地语音平台最初能够完成一件事:输入文字,调用 TTS 生成 MP3,再让 ESP32 播放。但我很快遇到一个产品层面的浪费——同一句话每点一次“生成并发送”,后台就重新调用一次 TTS,并保存一份新的 MP3。

检查重复文件后,它们的 SHA-256 完全相同。这说明我不是得到了两段不同语音,而是为同一份内容付出了两次生成时间和调用成本。

问题不只是“没有缓存”,而是两个对象被混成了一个

原来的 speech_jobs 表同时承担两种职责:既保存生成的音频,又记录一次设备播放。asset_id 还有唯一约束,因此一份音频也不能自然对应多次播放任务。

对象它回答的问题生命周期
语音资产“这段文字和配置生成了哪份 MP3?”可以长期保存、试听和重复使用
播放任务“哪台设备在什么时候播放了哪份资产?”每次发送都新建,并记录 queued、playing、completed 等状态

一句话可以只生成一份音频资产,但被发送 10 次时,仍应该有 10 个播放任务。反过来,同一句文字如果换了音色或输出格式,也不应该误命中旧资产。

最终数据模型

调整后,平台把数据拆成三个层次:

  • audio_assets:保存文字、Provider、音色、文件路径、大小、缓存键和 SHA-256。

  • speech_jobs:每次发送建立一条独立任务,引用已有音频资产。

  • playback_events:记录设备下载、开始播放和完成等事件。

这种拆分让“生成一次”和“播放多次”成为两件可以独立统计和重试的事情。页面也因此可以展示历史语音、直接在浏览器试听,并把同一份资产再次发送给 ESP32。

缓存键应该包含什么

平台会把规范化后的完整文字、TTS Provider、音色和会改变音频文件的输出参数组合起来,生成稳定缓存键。Python 标准库的 hashlib 文档说明了 SHA-256 摘要的标准接口;在本项目里,摘要被用来生成稳定标识和比较文件内容,不承担密码存储职责。

是否进入缓存键字段原因
进入完整文字文字变化会改变语音内容
进入Provider 与音色同一句话可能生成不同声音
进入输出格式等音频参数参数可能改变最终文件
不进入API Key它是访问凭证,不是音频内容
不进入设备播放音量它只改变播放,不应生成新 MP3

这里的“相同文字”并不等于只比较一段输入字符串。真正应该复用的是“相同内容加相同生成配置”的输出。

命中缓存后仍然要创建任务

用户第二次提交相同文字时,后台跳过 TTS,直接引用已有 asset_id;但仍创建新的 speech_job。历史列表点击“再次发送”也一样:复用资产,不复用任务。

这样做保留了两类信息:

  • 成本和速度:相同内容不再重复生成。

  • 设备日志:每次实际发送和播放仍然可以独立追踪。

旧数据没有被简单删除

迁移前先备份真实 SQLite 数据库。迁移后保留了原有 9 个任务、27 个事件和 9 个资产,并移除了 speech_jobs.asset_id 的唯一约束。页面按 MP3 的 SHA-256 折叠展示历史重复文件,但旧任务和事件仍完整保留。

验证包括 4 个缓存回归测试、16 项完整 Python 测试、C++ 设备协议测试,以及本地页面 HTTP 200 和历史列表展示。用户也亲自测试了历史列表、浏览器试听、再次发送与相同文字重复提交,确认相同文字确实复用了已有语音。

这项设计带来的实际价值

对我来说,最重要的价值有两个:减少 TTS 调用费用,以及提升重复内容的响应速度。它同时为以后只允许其他用户使用已经生成好的语音包提供了基础,因为资产可以被管理和复用,播放任务则继续保留每次设备行为。

CONTINUE READING

继续阅读同一作品、元件或排查路径上的真实制作记录。

  1. 概念科普 · 2026年8月28日 · 10 分钟

    ESP32ST7789 屏幕:7 根线分别是做什么的?

    第一次连接 GMT130-V1.0 时,我只会照着接线表操作。本文把 VCCGNDBLKRESSCKSDADC 分成四类,沿完整显示链路解释它们怎样把 ESP32 代码变成可见画面,以及理解以后怎样安全地重新规划线路。

    阅读文章
  2. 问题解决 · 2026年9月2日 · 8 分钟

    YCA2109 电子骰子通电不亮、芯片发热:两颗 DIP 芯片装反后的排查过程

    一块刚焊完的 YCA2109 电子骰子通电后 LED 全灭,接入电路板后的电压只有约 2.2V,约 10 分钟后两颗芯片明显发热。本文记录我怎样从电源、按钮、万用表档位和输入二极管一路排查,最终发现两颗 DIP 芯片方向错误;换向后,电子骰子恢复运行并能随机停在不同点数。

    阅读文章