多语言音频合成出来不理想,常见的困境是说不清问题出在哪:是分句把句子切断了、标点被吞掉了,还是语音包和语言码没对上。可行的做法是把合成链路拆成文本侧和语音侧两条线:每个阶段只固定一个变量,让另一条线保持不动,各自出一份输出,最后再合起来做一次完整合成。这样即使最终结果仍不理想,也至少能指出问题落在哪一侧,而不必反复调参数碰运气。
当多语言音频合成结果不理想、又判断不出是分句写法写错还是语音包选错时,建议先把语音配置固定,只改分句方式出一版;再固定文本,只换语音包出一版。两版都保留输出文件,并核对日志里记录的输入文本、语言码和语音包名。适用场景是单条或小批量合成调试;验证方式是同一段原文出两份音频,并比对日志中实际生效的配置;边界在于,如果当前日志没有打印生效配置,结论只能算推断,需要先补一条日志再下判断。
固定语音配置,只改文本分句方式
这一步把语音包、语言码、采样率、说话人固定住,只让分句规则变化。判断依据不是“哪种听感更好”,而是把两份输出文件放在一起听,看差异是否集中在句子的边界上。如果两版音频的差异只出现在停顿位置、句首吞字、标点处理上,问题就更可能属于文本侧。
下面是一份可替换的配置骨架,字段名以本地 YoVoice 的实际配置为准,重点是输出目录分开写,避免覆盖:
# 写法 A:按标点断句
voice:
pack: zh_female_a
lang: zh-CN
sample_rate: 24000
text:
splitter: punctuation
max_chars: 60
keep_punct: false
output_dir: ./out/punct
# 写法 B:按固定长度切分
voice:
pack: zh_female_a
lang: zh-CN
sample_rate: 24000
text:
splitter: fixed_length
max_chars: 30
keep_punct: false
output_dir: ./out/len30
两次运行之间只允许 splitter、max_chars、output_dir 三个字段不同,其余原样复制。产出对照可以这样记录:
- 写法 A 产出
./out/punct/*.wav,标记为 punct 组; - 写法 B 产出
./out/len30/*.wav,标记为 len30 组; - 把同一段原文的两份音频按句序号并排试听,记录差异出现在第几句、是吞字还是停顿;
- 如果两份文件数量不同(分句数不一样),先确认文本侧的切分结果,不要急着认为语音出错。
需要留意的边界是:分句方式改了,句子数量就会变化,音频总时长自然不同,这不属于异常。真正需要关注的是单个句子内部的发音是否完整。
固定文本,只切换语音包
这一步反过来,文本侧使用同一份已经确认过的分句结果,只替换语音侧配置。常用的改动点有四个:语音包标识、语言码、采样率、说话人或风格标识。其中语言码和语音包是最容易配错的一对——语音包和语言码不匹配时,有的实现会直接报找不到语音包或语言不支持,有的实现不会报错,而是用默认发音读出来,听感上像是“发音有点怪”,很容易被误判为文本问题。
# 同一份文本,只改 voice 段
voice:
pack: zh_female_a # 换成 zh_male_b、en_female_a 等
lang: zh-CN # 与语音包支持的语种保持一致
sample_rate: 24000 # 与语音包声明的采样率一致
speaker: default
切换前先确认语音包自身声明的支持范围(语种、采样率、是否支持多说话人),再看配置里写的值是否落在范围内。预期差异可以按现象归类:
- 换成同语种的另一个语音包:音色明显改变,断句和停顿应基本一致;
- 换成不支持当前语言码的语音包:可能出现加载失败提示,或用默认音色朗读,音色变化不明显但发音规则变了;
- 采样率与语音包不一致:容易出现语速变化或杂音,这类现象通常和文本无关;
- 多说话人语音包里 speaker 未指定:可能回落到默认说话人,听感与预期的角色不符。
如果切换语音包后,句首吞字、断句错乱的现象照旧出现,说明这些现象大概率不是语音包造成的。
在日志里找合成任务的输入文本与语音包记录
前面两步都建立在“我改的配置确实生效了”这个假设上,而实际调试中最常见的坑是改了文件但进程读的是另一份配置。合成任务的日志通常能回答三个问题:这次任务用的输入文本是什么、语音包和语言码是什么、文本在送入合成前被切成了几句。
建议在日志里确认存在这些字段,缺失就补打:任务号、输入文本长度与前若干字符、文本摘要(便于比对同一段原文)、语音包名、语言码、采样率、分句数量。用关键字检索:
# 找最近的合成任务记录
grep -nE 'task_id|voice_pack|lang=|sample_rate|chars=|split_count' logs/yovoice.log | tail -n 50
# 按任务号追一整条链路
grep -n 'task_id=abc123' logs/yovoice.log
# 只看送进合成的文本片段
grep -nE 'input_text|text_preview' logs/yovoice.log | tail -n 20
对照方法很直接:把日志里记录的语音包名和语言码,与你以为在用的配置文件逐字核对;把 split_count 与实际产出的音频文件段落数量核对。两处只要有一处对不上,说明这次调试的变量不止一个,结论要作废重跑。如果日志只记录任务成功而没记录入参,建议先把入参打印补上再做二分,否则后续判断都缺少依据。
把两条线合并成固定流程
单次调试之所以混乱,通常是因为一次同时动了文本和语音。把顺序固定下来,每次只推进一格:
| 步骤 | 动作 | 固定不动 | 允许变更 | 产出 |
|---|---|---|---|---|
| 1 | 清洗原文:去多余空白、统一标点 | — | 文本内容 | 干净原文一份 |
| 2 | 确认分句结果:打印分句数与前若干字 | 语音配置 | 分句规则 | 分句清单 |
| 3 | 确认语音侧:语音包、语言码、采样率 | 分句结果 | 语音配置 | 配置快照 |
| 4 | 核对日志:入参是否与配置快照一致 | — | — | 日志片段 |
| 5 | 执行合成,输出到带标记的目录 | 上述全部 | 无 | 音频文件 |
| 6 | 试听并记录差异位置 | — | — | 试听记录 |
顺序表的价值在于:任何一步发现不一致,就退回上一步重来,而不是在结果不理想时继续叠加新改动。配置快照可以用复制文件的方式留存,文件名带上日期和变量名,方便两次运行之间做差异比对。
用交叉对照样本复测
前面的判断是否成立,需要用一小批特征明确的样本来复测。样本不必多,但每一类要能指向一个可能的原因。建议准备下面这样一组,用同一套配置跑两遍(一遍固定语音改分句,一遍固定文本改语音包):
- 单个短句,无标点:验证最小单元是否能正常合成;
- 一句里带两个以上逗号的长句:观察停顿位置是否符合预期;
- 中英混排的短句:观察外语片段是否被读成字母或跳过;
- 含数字与单位的句子:观察数字是逐位读还是按数值读;
- 含括号、引号、破折号的句子:观察符号是否被读出来;
- 含表情符号或生僻符号的句子:观察是否被过滤或导致整句异常;
- 带首尾空格与连续空白符的句子:观察清洗是否生效。
判定标准按现象归类,落点写清楚再下结论:
- 现象只在某个分句规则下出现,换语音包不消失 → 判定在文本侧,优先查分句与标点处理;
- 现象只在某个语音包下出现,换分句规则不消失 → 判定在语音侧,优先查语言码、采样率、语音包支持范围;
- 现象在所有样本、所有配置下都出现 → 可能是链路之外的环节(音频播放、文件写入、编码),需要单独排查;
- 现象只出现在个别样本上,与配置无关 → 更像是文本内容或符号处理问题,把该样本单独拿出来做最小复现。
如果复测结果与先前判断不一致,以复测为准,并回到日志重新确认实际生效的配置。没有日志佐证的判断,建议只作为下一步排查方向,不作为结论使用。