YoVoice 输出音频和文本对不上——是分句规则还是语音包的问题?

文章导读
先给判断方向:YoVoice 合成出现漏句、串句或停顿怪异,多数情况不是音色“变差”,而是文本在进入合成引擎之前就被改过——要么是分句/断句规则把一句话切碎或合并,要么是当前语音包与语言码对数字、标点、中英混排、特殊字符的处理和你预期不一致。二分排查的做法是固定其他变量:先把送进引擎的最终文本落盘确认没被改字,再用同一段文本切换分句方式合成一次,最后换语音包再合成一次,看错位位置跟着哪个变量移动。
📋 目录
  1. 一 保存送进合成的最终文本副本
  2. 二 同一文本换不同分句方式各合成一次
  3. 三 换语音包重复同一文本
  4. 四 在日志中定位一次合成任务的输入与输出
  5. 五 把结论写成可复用的判定顺序
A A

先给判断方向:YoVoice 合成出现漏句、串句或停顿怪异,多数情况不是音色“变差”,而是文本在进入合成引擎之前就被改过——要么是分句/断句规则把一句话切碎或合并,要么是当前语音包与语言码对数字、标点、中英混排、特殊字符的处理和你预期不一致。二分排查的做法是固定其他变量:先把送进引擎的最终文本落盘确认没被改字,再用同一段文本切换分句方式合成一次,最后换语音包再合成一次,看错位位置跟着哪个变量移动。

可摘录判断:文本副本确认预处理没改字;同一文本换分句方式后错位位置若跟着断句点移动,问题在分句规则;若错位位置固定不变、换语音包后仍复现,则倾向语音包、语言码或文本编码侧。每一步都要留下输入文本、实际生效参数和输出音频文件,结论才立得住;具体字段名与日志格式需要结合你的接入环境确认。

保存送进合成的最终文本副本

排查先从“文本有没有被改”开始。页面上看到的文本、程序里传入的字符串、分句之后交给引擎的句子列表,往往是三份不同的内容:前端可能做了全角半角转换,后端可能去掉了表情或做了繁简转换,分句模块又可能合并了短句。建议在调用合成前把这几份内容分别写文件,带上任务 ID,后面每步对照都靠这些副本。

# 通用接入骨架,字段名以实际接入为准
payload = {
    "text": raw_text,
    "voice": voice_id,
    "lang": lang_code,
    "split": split_mode,
}
sentences = split_text(raw_text, mode=split_mode)  # 你本地或引擎侧的分句实现

with open(f"debug/{task_id}_raw.txt", "w", encoding="utf-8") as f:
    f.write(raw_text)
with open(f"debug/{task_id}_sent.txt", "w", encoding="utf-8") as f:
    f.write("\n---分句---\n".join(sentences))
with open(f"debug/{task_id}_body.json", "w", encoding="utf-8") as f:
    json.dump(payload, f, ensure_ascii=False, indent=2)

需要检查的落点有四个:页面输入框原文、程序内的字符串变量、分句后的句子列表、实际发出的报文体。逐字比对 raw 与页面原文,能排除清洗规则;比对 sent 与 raw,能看到分句模块是否吞掉了空白、引号内的标点或整段短句。这一步做完再听音频,问题就已经被切掉一半范围。

同一文本换不同分句方式各合成一次

固定语音包和语言码,只改分句方式,观察错位位置是否跟着断句点走。常见可切换的维度:整段一次送入由引擎自行断句、按句末标点切、按标点加最大字数切、人工在指定位置切分。若第一版漏掉的那半句,在第二种切法下被完整念出,说明之前那句话被切在了不合适的位置;若换了切法错位仍在原字符附近,分句规则的嫌疑就下降。

YoVoice 输出音频和文本对不上——是分句规则还是语音包的问题?

建议每一步记录断句的字符偏移,而不是只写“按句号切”:

轮次分句模式断句位置(字符偏移)输出时长错位表现
R1整段送入引擎自定记录实际值如:第 3 句只念了前半句
R2按句末标点列出切开的位置记录实际值错位是否移动、是否消失
R3标点 + 最大字数列出切开的位置记录实际值停顿是否变正常

对照重点是“错位位置”和“断句位置”的相对关系,而不是音频时长本身;时长只用来辅助判断是否真的漏读了内容。

换语音包重复同一文本

第三轮只换语音包,文本和分句方式保持不变。切换方式通常有两类:调用侧改 voice 或 speaker 参数,页面侧在音色下拉里换一个再合成。若你的接入支持指定语言码,建议顺带把语言码对同一文本各试一次,尤其是中英混排、含数字和单位、含书名号与省略号的句子——这类文本在不同语音包上的处理差异最容易暴露。

YoVoice 输出音频和文本对不上——是分句规则还是语音包的问题?
payload["voice"] = "voice_b"      # 换成另一个语音包
payload["lang"] = "zh-CN"         # 语言码按需对照,取值以实际支持为准

两次输出的差异建议从三个角度看:把两段音频分别做时长统计、做静音段检测、再人耳定位错位句的位置。若只有某一个语音包出现漏读或怪停顿,另一个正常,问题基本落在语音包或语言码侧;若两个语音包都在同一句出错,回到分句与文本副本继续查。

ffprobe -v error -show_entries format=duration -of default=nw=1 out_a.wav
ffmpeg -i out_a.wav -af silencedetect=noise=-35dB:d=0.3 -f null - 2>&1 | grep silence

在日志中定位一次合成任务的输入与输出

前面的对照都依赖“实际生效的参数”,而不是你以为传进去的参数。配置项被默认值覆盖、页面缓存了上一次的音色、环境变量里的语言码没生效,都会让对照失真。抓一次完整任务日志,用任务 ID 串起来看。

YoVoice 输出音频和文本对不上——是分句规则还是语音包的问题?
grep -n "task_id=abc123" app.log
grep -n "task_id=abc123" app.log | grep -Ei "text_len|split|voice|lang|synth_start|synth_end|error"
awk "/task_id=abc123/{print}" app.log | tail -50

需要确认的字段:文本长度是否与落盘副本一致、分句模式是否是这次想测的值、语音包与语言码是否是实际生效值、合成开始与结束是否正常配对、有没有被截断或重试的记录。日志关键字因项目而异,先用一次成功任务和一次异常任务各 grep 一遍,把实际字段名记下来,之后按这套关键字查。

把结论写成可复用的判定顺序

同样的现象下次还会遇到,把上面四步固化成顺序表,按顺序走,避免每次都从“是不是模型不好”开始猜。

顺序检查动作判定依据倾向结论
1比对页面原文与送合成文本副本字符是否一致不一致:预处理或清洗规则改动文本
2换分句模式合成同一文本错位位置是否随断句点移动移动:分句规则问题
3换语音包或语言码合成同一文本错位是否只在某一语音包出现只在该包出现:语音包或语言码问题
4查日志中实际生效的参数参数是否与预期一致不一致:配置未生效或被默认值覆盖

每次排查建议记录的字段固定为:时间、任务 ID、输入文本摘要或哈希、分句模式与断句位置、语音包、语言码、输出文件路径、输出时长、错位类型(漏句/串句/停顿异常)、结论、处理动作。记录里保留输入副本和输出文件,比只写一句“调了分句就好了”有用得多,下次同类问题可以直接对照前面几轮的结果判断,而不必重跑全部组合。