场景与痛点
接 TTS(语音合成)服务的时候,经常碰到一个硬性限制:单次请求最多处理 50 个字符(或者是其他的长度限制)。
但现在的 AI 回复动不动就几百上千字,肯定得拆。
最开始的做法很简单粗暴:每 50 个字符切一刀,分批调接口。
结果合成出来的语音听着像结巴:
"今天我们去商场买了衣..." (停顿 1 秒)"...服,之后去吃了晚餐。"
这就很难受了,"衣服"这个词被硬生生劈开,用户体验极差。
怎么解决?核心思路其实就一句话:让机器模拟人的自然换气。
核心矛盾
这其实是一个"规则冲突"问题:
- TTS 的规则:长度必须 < 50。
- 语言的规则:停顿必须在"语义边界"(标点、词组空隙)。
我们的目标是:在满足 TTS 硬性限制的前提下,尽量贴合语言的自然停顿。
优化策略
我们验证下来,这三个策略组合使用效果最好:
1. 标点边界优先(Priority Split)
思路:宁可少切,不能乱切。
只要没超长,就一直往后找,直到遇到句号、逗号、问号这些天然停顿点再切。
比如这段话:
"今天天气很好,我们决定去公园散步,顺便买点水果回来。"
-
硬切模式:切在"水果"中间。
-
标点优先模式 :
text片段1: "今天天气很好,我们决定去公园散步," (20字符) 片段2: "顺便买点水果回来。" (10字符)
虽然每个片段都远没到 50 字符,但保证了语义完整。
2. 缓冲回退(Buffer Backtrack)
如果一句话特别长,中间完全没有标点,或者标点在 50 字符之外,怎么办?
不要在第 50 个字符硬切,而是往回退。
比如累积到 50 字了,发现卡在单词中间。这时候往回退 5-10 个字符,看看有没有空格、逗号或者其他稍微适合停顿的地方。
流程如下:
text
累积到上限 → 能切吗?(是标点/空格)
↓
不能 → 往回倒车 10 步 → 找到标点了吗?
↓ ↓
没找到 (认命硬切) 找到了 (切分)
3. 短片段合并(Merge Short)
切分过细会带来另一个问题:请求太碎,网络开销大,且声音断断续续。
比如切成了:"好的。"、"没问题。"。 这两个完全可以合并成 "好的。没问题。" 一起发过去,只要合并后不超过 50 字符。
方案实现
处理流程图
这个逻辑用状态图描述最清晰:
关键代码 (Python)
python
def split_for_tts(text, max_len=50, buffer_size=10):
"""
智能切分文本,优先保持语义完整
"""
# 定义标点优先级:句子结束符 > 短句分隔符
# 实际场景中可以配置权重
stops = set('。,!?;:、.!?,;:')
segments = []
current_idx = 0
while current_idx < len(text):
# 剩余部分直接打包
if len(text) - current_idx <= max_len:
segments.append(text[current_idx:])
break
# 预设切分点为最大长度处
cut_pos = current_idx + max_len
found_stop = False
# 1. 策略:缓冲回退
# 从 max_len 处往回找 buffer_size 个字符
for i in range(cut_pos, cut_pos - buffer_size, -1):
if i < len(text) and text[i] in stops:
cut_pos = i + 1 # 包含标点
found_stop = True
break
# 2. 策略:兜底
# 如果缓冲区没找到标点,就只能硬切了
# (实际生产中这里可以再优化,比如尝试找空格)
segments.append(text[current_idx:cut_pos])
current_idx = cut_pos
return merge_short_segments(segments, max_len)
def merge_short_segments(segments, max_len, min_len=10):
"""
合并碎片段,减少请求次数
"""
if not segments: return []
merged = [segments[0]]
for seg in segments[1:]:
last = merged[-1]
# 如果合并后不超长,且前一个太短,就合并
if len(last) + len(seg) <= max_len and len(last) < min_len:
merged[-1] += seg
else:
merged.append(seg)
return merged
避坑指南
在实际上线过程中,还踩过几个坑,提个醒:
- 保护语义实体 :虽然发给 TTS 的通常是清洗后的纯文本,但文本里常有金额 (
1,000)、书名号 (《...》)或特定短语 。如果你在逗号处把1,000切开了,TTS 可能会把1,读成"一,",剩下的000读成"零零零"。建议对这类实体做正则识别,确保它们完整地留在同一个片段里。 - 英文单词边界 :中文按字算,英文按词算。切分的时候一定要判断
text[cut_pos]是不是字母,如果是,千万别切,继续往回退找空格。 - 表情包过滤 :很多 TTS 模型不支持 emoji,直接把
😂这种符号喂进去可能会报错或者读出乱码,建议预处理直接干掉。
总结
TTS 优化的本质不是算法问题,而是体验问题。
不需要多高深的 NLP 模型,只需要几行简单的规则代码:多找标点,少硬切,适度合并。就能把"机器味"去掉一大半。
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
全栈项目(适合学习现代技术栈):
- prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
- chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB