苏州话语音合成批量落地:翻译层的上海话污染、参考音选型与18秒红线

2026-09-30 · AI智工坊 · 方言语音工程

做方言语音合成,很多人一上手就去找"最像苏州话的模型"。我们把一批问答音频跑通之后得到的结论恰好相反:决定成品像不像苏州话的,第一是文本层,第二是参考音选型,第三才是模型本身。声学模型选错,出来的是一口"带苏州味的普通话";文本层选错,出来的是一口标准的上海话——后者更糟,因为听感上"很吴语",但你给苏州人听,他会说这不是我们的话。

这篇把我们实际跑批用到的结构、参数与判定规则完整写出来,只写我们真跑过的部分。

一、为什么必须拆成两层:翻译层 + 合成层

直接拿普通话文案丢给 TTS,是行不通的。方言 TTS 的输入需要是"方言口语的正确书面形式"——不是普通话的拼音化,而是换词。普通话的"你吃饭了吗",苏州话的口语形式是"倷饭阿曾吃"这类结构;如果只做音素映射,读出来就是普通话改口音。

所以第一层是一个纯文本改写层:普通话文案 → 苏州白话文本。这一层的产出必须是可审阅的纯文本,而不是直接进合成,原因是它需要被约束词表校验。

约束方向要求典型例子
必须使用苏州特征词倷、吾、哉、匣、搿、格、铜钿、讲张
必须替换上海话特征词逐一出局侬→倷、阿拉→伲、伊→俚
输出格式只输出译文,不带解释、不带标记避免把"以下是译文:"读进音频

关键工程点:先把译文落成文本、跑一遍词表校验,再进合成。如果直接调 API 拿结果就送合成,你事后无法区分"模型翻错了"还是"合成读错了",返工成本翻倍。我们在苏州话 TTS 的批量生产里,文本层由大模型负责粗翻,词表负责兜底——这也是站内那份 3550 条苏州话标准词库的真实用途:它是文本层的白名单与校验器,不是拿来背的。

二、参考音选型:短句必须锁定真苏州参考音

这是最容易吃暗亏的一处。同一个 CosyVoice2-Wu 服务,接口上通常提供多个 voice 选项。我们实测的结论是:

也就是说,参考音是一个短句场景下的硬约束,不是"哪个好听选哪个"。展览会数字人的预置问答几乎全是短句,这一条不满足,后面所有参数调优都是白费。

三、可复现性:随机采样 + 固定 seed,而不是 greedy

批量生产必须可复现——今天生成的音频和明天重新生成的,要能对得上;出问题时要能复现同一条。

我们走的路是:保留随机采样,但把种子固定下来。推理前对随机源设置随机种子,同一段文本 + 同一个 seed,输出的音频文件 MD5 完全一致。这样既保住了采样带来的自然度,又拿到了确定性。

反过来的做法——把采样改成 argmax(greedy 解码)以追求确定性——我们实测是行不通的:模型会崩,输出表现为一段十几秒的静音加破碎语音。方言这类训练数据相对稀疏的情况下,随机采样不是锦上添花,是模型能正常出声的前提。

由此得到一个很实用的技巧:同一个 seed 遍历一次,挑苏州腔最正的那条固化下来,再用这个 seed 产出整批。挑样的成本一次性付清。

四、18 秒红线与指令段裁切

第二个硬约束:单段音频不超过 18 秒。超长会"跑飞"——韵律垮掉、后半段加速或含糊。超过就按句切分多段,最后再拼接,不要指望模型自己收住。

另一个细节是"指令段"。合成接口支持 instruct 形式(给模型一句"用苏州话说这句话"的指令),产出的音频前面会带一段指令播放。服务侧提供的 instruct_cut 参数可以取完整音频,此时需要自己切掉指令段。

用静音检测切的做法是:在 3 秒到 9 秒的区间内,找第一个持续时间 ≥0.3 秒的静音,作为指令段与正文的分界;同时排除结尾静音(分界点必须小于总时长减 1 秒)。为什么要限定 3–9 秒区间,而不是简单取"全局最长静音"?因为正文里本身就可能出现比指令段更长的停顿,全局最长静音经常切在正文中间,把一句话劈成两半——这种错误在批量跑的时候极难发现,直到有人听。

五、不起眼但必踩的三个工程细节

1. 服务只回路径,不提供下载端点

合成服务返回的是服务器上的文件路径字符串,不是一个可直接下载的 URL。批量取回需要走 SFTP 把 wav 拉回本地。这一点决定了你的跑批脚本必须同时具备"调用推理"和"远程取文件"两种能力,不能只写一个 HTTP 客户端。

2. Git Bash 下的路径转义

在 Windows 上,Git Bash 的 /c/... 路径写法,本地 ls/cp 认,但 Windows 版的 ffmpeg / ffprobe / Python 不认——它们会把它当作字面量 C:\c\... 从而报文件不存在。规则很简单:传给 Windows 程序的路径用 C:/... 或 C:\...;只有本地 shell 命令才用 /c/...;给 SFTP 的自然永远是远端 Linux 路径。三种路径混在一个脚本里是常态,出错时优先怀疑这一条。

3. 密钥文件的复制方式

需要把 SSH 私钥放到工作目录时,用 cp 复制。不要用"读出来再写回去"的方式——私钥是二进制/多行格式,经一次文本转写就可能损坏,报错信息通常还是"认证失败",会让你在错误的方向上排查很久。

六、转码与交付形态

合成服务产出的是 wav,交付给前端或第三方时统一转成 mp3:采样率 24000、单声道、64 kbps。语音内容在这个参数下体积和清晰度是划算的;校验时用 ffprobe 读时长做一次自检,确认没有截断。

成本结构上提一句:这条流水线里唯一的外部按次调用是文本翻译层,声学合成本身跑在自建 GPU 上。对方言这种长尾需求,本地推理的边际成本优势非常明显——这也是我们把整条链路放在自有节点而不是全云上的原因。

七、把顺序定下来

  1. 先定文本层约束(必须用的词 / 必须替换的词),产出可审阅的纯文本;
  2. 再定参考音(短句场景直接锁真苏州参考音,不要用底层是上海话的音色);
  3. 再定随机种子,遍历挑样并固化;
  4. 按 18 秒切段,用区间内静音检测裁掉指令段;
  5. 最后统一转码、自检时长、回传。

这个顺序的价值在于:每一步的错误都能被单独定位。方言语音项目的返工,九成不是因为模型不行,而是因为这五步被揉成了一次性的黑盒调用。

如果你的项目也需要方言语音的批量生产,或者需要先把"文本层不跑偏"的底座搭起来——我们把自己在用的 3550 条苏州话标准词库(JSON + CSV 双格式,含字段说明与零依赖读取示例)放在了站内商品页,¥99 起,可以先下载 100 条免费样本确认格式。

查看《苏州话标准词典词库 3550 条》商品页 →

常见问题

方言 TTS 为什么不能直接把普通话文本喂进去?

因为方言的差异首先在词,不在音。普通话文本做音素映射只会得到一口"带口音的普通话"。必须先把普通话改写成方言口语的正确书面形式,再进合成层。

为什么短句要换参考音、长句却好像没那么明显?

短句里可供模型参考的上下文太少,腔调基本由参考音的音色与韵律决定;长句里模型的韵律模型有更多发挥空间,会把腔调拉回来一些。所以短问答场景下参考音选型是硬约束。

为什么不用贪心解码保证输出一致?

实测会崩:输出表现为长时间静音加破碎语音。正确做法是保留随机采样、固定随机种子——同文本同种子输出文件 MD5 一致,既确定又自然。

单段音频为什么要限制在 18 秒以内?

超长后韵律会跑飞,后半段出现加速、含糊或断句错误。超过 18 秒应按句切分多段,再拼接。

指令段怎么切才不会切错?

在 3–9 秒区间内找第一个 ≥0.3 秒的静音作分界,并排除结尾静音(分界点需小于总时长减 1 秒)。直接取全局最长静音会切到正文中间。