Qwen-Audio-3.0-TTS 读一段技术文档 / 数字和英文缩写该怎么预处理?

文章导读
把一份技术文档直接丢给 TTS,最容易翻车的往往不是长句,而是数字、单位符号和英文缩写:一句「重试间隔 0.25 秒,最多重试 3 次」里同时出现了小数读法、单位连读和量词搭配。处理顺序建议反过来——先从真实文档里抽句子做试听,把「听着别扭」拆成具体类型,再判断这句话该交给文本改写,还是交给语速、停顿这类合成参数。文本改写成本低、可复用;参数只调整节奏,改不了「3.5」被读成「三点五」还是「三点十
📋 目录
  1. 壹 从线上真实文档里抽出 20 条测试文本
  2. 贰 逐条试听并按错误类型归类记录
  3. 叁 一次只改一个变量做对照试听
  4. 肆 把有效改法固化成预处理规则清单
A A

把一份技术文档直接丢给 TTS,最容易翻车的往往不是长句,而是数字、单位符号和英文缩写:一句「重试间隔 0.25 秒,最多重试 3 次」里同时出现了小数读法、单位连读和量词搭配。处理顺序建议反过来——先从真实文档里抽句子做试听,把「听着别扭」拆成具体类型,再判断这句话该交给文本改写,还是交给语速、停顿这类合成参数。文本改写成本低、可复用;参数只调整节奏,改不了「3.5」被读成「三点五」还是「三点十五」这种文本层歧义。

数字与缩略语的读法问题,大多能在文本层解决:把 0.25 写成「零点二五」,把 % 写成「百分之」,把容易被整词吞掉的缩略语按字母分隔或展开,再送合成。只有停顿过长、语速偏快、句末语调这类节奏问题才值得动合成参数。判断依据是同一句只改一个变量重复试听;边界是发音词典与接口字段并不通用,不同版本参数名不一致,必须以你实际接入版本的返回和播放效果为准。

从线上真实文档里抽出 20 条测试文本

测试集不要凭空造句,直接从你手上会真正送进 TTS 的文档里摘:配置说明、API 参考、运维手册、发布公告、脚本注释。摘录时做脱敏(域名、内网地址、人名换成占位符),但保留原始写法——千分位、全角括号、单位大小写、函数名里的下划线,这些细节恰恰是读错的来源。下面这 20 条覆盖了常见写法,可以直接作为你第一轮试听的底稿。

序号测试文本(脱敏)取自文档的哪类位置关注点
1单节点最多承载 20000 个长连接容量说明段五位整数读成「两万」还是逐位念
2超时阈值设为 3.5 秒参数表一位小数与量词的连读
3重试间隔 0.25 秒,最多重试 3 次故障处理步骤两位小数是否读成「零点二十五」
4日志默认保留 180 天存储策略段三位整数的自然读法
5请升级到 v2.10.3 及以上升级说明点分版本号被当小数读
6单文件上限 512 MB,批量上传上限 20 GB限制说明单位字母逐字母念还是整词
7响应时间 P99 约 120 ms指标监控段P99 这种字母+数字混排
8内存峰值 1.2 GiB资源评估段GiB 与 GB 的区分
9CPU 使用率超过 75% 触发限流告警规则百分号紧贴数字
10机房温度不超过 35°C运维规范度符号是否被吞掉
11带宽 10 Gbps,抖动小于 5 ms网络指标多字母单位的连读
12通过 HTTPS 上传,证书使用 TLS 1.3安全说明缩写逐字母念的顺序
13接口默认开启 QPS 限流接口约束QPS 被当单词拼读
14该能力依赖 SDK 中的 RAM 角色授权权限配置SDK、RAM 两个缩写连用
15使用 CRUD 接口批量写入开发指南可发音缩写的读法
16日志出现 OOM 时先看 RSS 曲线排障手册OOM 与 RSS 的区分度
17默认值 30(单位:秒,范围 1-3600)参数说明全角括号与连接号的停顿
18需开通白名单(联系运维同学,工单类型选网络策略)前置条件括号内长句的断句
19调用 open_stream() 时传入 chunk_size 参数代码示例说明下划线、括号、驼峰
20返回 AsyncIterator 对象,需在 finally 中释放API 参考中英混排与关键字 finally

摘的时候顺手记一下每句来自哪一段。后续修规则时,你能判断某条规则是为「参数表」服务的还是为「排障步骤」服务的,不至于一刀切改写所有文本。

逐条试听并按错误类型归类记录

试听时不要只写「不好听」,要落到可改动的维度上:数字读法、缩略语处理方式、多音字、标点处停顿。下面给出分类维度与常见现象的对应关系,结果栏请边听边填,以你实际听到的为准——不同音色、不同参数下表现会有差异。

Qwen-Audio-3.0-TTS 读一段技术文档 / 数字和英文缩写该怎么预处理?
类型判断标准常见现象优先处理方向
数字读法小数、版本号、长整数0.25 读成「零点二十五」;v2.10.3 读成「二点十点三」文本改写
缩略语逐字母、整词、还是展开QPS 被拼成一个生造词;TLS 逐字母吞掉末尾文本改写或发音词典
多音字重、调、载、行、率、数重试读成 zhòng;调用读成 tiáo换词或加注音词典
标点停顿括号、顿号、连接号、斜杠括号内长句一口气念完;1-3600 停顿突兀标点替换,必要时调停顿参数

记录格式建议固定成四列,便于横向比对:

序号 | 原文 | 实际听到的关键片段 | 归类
3    | 0.25 秒 | 听到『零点二十五秒』 | 数字读法
13   | QPS 限流 | 听到一个整词,像『qups』 | 缩略语
2    | 重试间隔 | 听到『zhòng 试』 | 多音字
17   | 1-3600 | 连接号处无停顿,数字连成一串 | 标点停顿

归类完成后再回头看:落在「数字读法」和「缩略语」上的问题,基本都靠改文本;落在「标点停顿」且改写后仍然别扭的,才需要动参数。「多音字」介于两者之间——优先换词(把「重试」写成「再次尝试」),换不掉再考虑发音词典。

一次只改一个变量做对照试听

同一句文本合成三遍:A 原文 + 默认参数,B 改写文本 + 默认参数,C 原文 + 调整参数。三遍用同一个音色、同一段文本、同一套输出格式,只动一个变量,差异才有意义。如果三遍同时改,你无法判断好转来自哪一步。

Qwen-Audio-3.0-TTS 读一段技术文档 / 数字和英文缩写该怎么预处理?
# 通用请求骨架,字段名请按你接入的版本核对
{
  'model': '<tts_model_name>',
  'input': '重试间隔 0.25 秒,最多重试 3 次',
  'voice': '<voice_id>',
  'format': 'wav',
  'speed': 1.0
}

# B 组只改 input
'input': '重试间隔零点二五秒,最多重试三次。'

# C 组只改参数,文本回到原文
'speed': 0.9  # 或按版本支持的停顿/韵律字段调整

对照结论按下面这种方式记,不要写成「效果不错」:

  • A 组:小数读法错误,句子节奏正常 → 问题在文本层。
  • B 组:小数读法正确,但数字与量词之间听起来黏连 → 需要补停顿或加量词。
  • C 组:文本未改,只放慢语速,小数读法依然错误 → 说明参数不解决读法问题。

一个常见误区是把语速调慢当成万能药。语速慢下来只会让错误读法听得更清楚,不会把「三点十五」变成「三点一五」。凡是 A 组就听出错读的句子,先改写再考虑参数。

把有效改法固化成预处理规则清单

试听结论要落成规则,否则每接一份新文档都要重听一遍。规则写在送合成之前,输入文本、输出文本一对一可 diff,误伤才查得出来。下面这份清单按保守方向写,按你实际文档的写法增减。

Qwen-Audio-3.0-TTS 读一段技术文档 / 数字和英文缩写该怎么预处理?
规则处理方式对照例句
万级以上整数改写成「两万」「十二万」这类数量词,或补量词20000 个 → 两万个
小数阿拉伯数字保留,但两位以上小数写成中文数字,消除歧义0.25 秒 → 零点二五秒
版本号用「点」显式分隔,或写成「版本二,一零,三」逐段念v2.10.3 → 版本 2 点 10 点 3
百分号、度符号替换成中文词,数字与词之间留空格75% → 百分之 75;35°C → 35 摄氏度
常用单位保留字母,确认数字与单位之间有空格;易混的写全称120ms → 120 毫秒;1.2 GiB → 1.2 吉字节
范围连接号替换成「到」,两侧留空格1-3600 → 1 到 3600
斜杠按语义换成「每」或「或」,不要留着让模型猜次/秒 → 次每秒

缩略语要不要展开,给一个可操作的判断条件:如果试听时它被读成一个生造词,或逐字母念吞掉了末尾字母,并且你的接入方式没有可用的自定义发音词典,就展开或按字母加分隔;如果缩写本身是可发音的常见词(如 CRUD),且试听时整词读法正确,就保留不展开。展开会拉长朗读时间,长文档里不必全量展开,只处理高频出现的那几个。

需要删除或替换的符号,建议单独维护一张表:` 和 ** 这类 Markdown 标记直接去掉;竖线 | 换成逗号;下划线在函数名中改成空格或按语义拆词,例如 open_stream 写成 open stream;全角括号保留但括号内的长句单独断句;emoji、制表符、连续空格统一清理。占位符 <xxx> 在朗读版里替换成可读名称,不要原样念出尖括号。

import re

REPLACE = [
    (r'~', '约'),
    (r'(\d)\s*[–—-]\s*(\d)', r'\1 到 \2'),
    (r'(\d)\s*/\s*([^\s,,。]+)', r'\1 每 \2'),
    (r'(\d)\s*%', r'百分之 \1'),
    (r'(\d+(?:\.\d+)?)\s*°C', r'\1 摄氏度'),
    (r'(\d+(?:\.\d+)?)\s*ms\b', r'\1 毫秒'),
    (r'[`*|]', ''),
    (r'_(?!\w)', ' '),
]

def preprocess(text: str) -> str:
    for pat, rep in REPLACE:
        text = re.sub(pat, rep, text)
    return re.sub(r'[ \t]{2,}', ' ', text).strip()

# 上线前先跑一遍差异检查,确认没有改掉原意
if __name__ == '__main__':
    samples = ['单文件上限 512 MB,批量上传上限 20 GB', '范围 1-3600']
    for s in samples:
        print(preprocess(s))

规则上线后仍要留一段人工核对:随机抽若干句比较改写前后,重点看两件事——数字有没有被改错量级,术语有没有因为拆词而变味。这套规则的价值不在一次到位,而在于让后续接入方按同一份清单处理文本,试听工作量从每份文档重来,变成只在新增写法时补一条规则。