SparkAudio 1.0 输出音频断在半句 / 是切分太碎还是拼接没对齐?

文章导读
听到「一句话断在半截」,先别急着改切分脚本,也别立刻换音色。SparkAudio 1.0 这类 TTS 链路里,断句通常来自三处:单段合成返回的音频尾部本身就被截断、请求参数与写进容器的采样率或声道不一致、以及切分和拼接时把尾巴裁掉或补了过长静音。可行的排查顺序是先用一句完整短句单独合成并原样保存返回文件,再用 ffprobe 读容器参数,最后才去调切分粒度。
📋 目录
  1. A 先确认单段返回的音频本身是否完整
  2. B 用命令行读出声卡参数与文件真实采样率
  3. C 把切分粒度调粗再看断点是否消失
  4. D 检查拼接处的静音长度与重叠裁切
  5. E 把定位到的那一步固化成默认参数
A A

听到「一句话断在半截」,先别急着改切分脚本,也别立刻换音色。SparkAudio 1.0 这类 TTS 链路里,断句通常来自三处:单段合成返回的音频尾部本身就被截断、请求参数与写进容器的采样率或声道不一致、以及切分和拼接时把尾巴裁掉或补了过长静音。可行的排查顺序是先用一句完整短句单独合成并原样保存返回文件,再用 ffprobe 读容器参数,最后才去调切分粒度。

判断顺序建议是:单段完整性 → 容器格式 → 切分粒度 → 拼接裁切。每一步只改一个变量,并保留原始返回文件做对照。如果单段合成就不完整,问题在请求侧或模型输出,调切分粒度基本无效;如果单段完整,断点才可能出在自己写的那段拼接代码里。下面的命令和阈值需结合本机 ffmpeg 版本、音色配置和文本长度确认。

先确认单段返回的音频本身是否完整

先准备一句 15 到 25 字、带标点的完整短句,只合成这一句,不要附带上下文。保存返回时要拿到原始字节,不要在中间做解码、重采样或重写 WAV 头,否则后面无法区分是服务端截断还是本地处理截断。下面是一段通用骨架,语音调用部分按你实际的 SDK 或 HTTP 请求替换:

# 只保存服务端返回的原始字节,先不解码、不重采样
resp = client.synthesize(text=single_sentence, voice=VOICE, sample_rate=REQ_SR, format="wav")
raw = getattr(resp, "audio_bytes", None) or resp.content
open("single_raw.wav", "wb").write(raw)

接着核对时长。中文播报语速通常在每秒四到六个字之间,可以拿这个区间做粗比:把 ffprobe 读出的时长除以字数,若结果明显低于这个区间,就要怀疑尾部被裁;若明显偏高,更可能是采样率被写错导致整段被拉长。这一步只是粗比,不作为判定标准,真正的依据还是听最后半秒有没有突然静音或削字。

ffprobe -v error -show_entries format=duration -of csv=p=0 single_raw.wav

用命令行读出声卡参数与文件真实采样率

很多时候请求里写的是 24000 Hz,而服务端或本地写进容器的却是别的值,听感上就表现为语句被「压扁」或某个音节消失,容易被误判成切分太碎。用下面的命令一次读出容器、采样率、声道、码率和时长:

SparkAudio 1.0 输出音频断在半句 / 是切分太碎还是拼接没对齐?
ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,channels,bit_rate,duration -of default=noprint_wrappers=1 single_raw.wav

把输出结果和你发出请求时的参数逐项对照,建议按下面这张表记录一次:

对照项请求参数容器读出不一致时的常见听感
采样率如 24000如 16000 或 44100语速整体变慢或变快,句中像吞字,时长与字数之比失真
声道数单声道双声道拼接后左右声道错位,听起来像两次发音叠在一起
容器格式wavmp3 或带编码头的包装段尾自带 padding 静音,拼起来断点处空白偏长
时长按字数估算明显偏短最后半句被削掉,常见于长文本单次请求

若采样率和声道都对得上,断点又只在长文本里出现,问题范围就基本落到切分和拼接这一侧了。

把切分粒度调粗再看断点是否消失

这一步的目的是验证切得太碎是否切断了语义。取同一段 300 到 500 字的文稿,做两组对照:A 组优先按句号、问号等句末标点切,单段上限放宽;B 组按逗号或固定字符数切,单段上限收紧。其余参数(音色、采样率、输出格式)保持完全一致,分别合成后拼接,再逐段听。

记录表建议至少包含断点位置前后各三个字,这样下次出现同样听感时可以直接比对,而不是凭印象说「这里断了」:

SparkAudio 1.0 输出音频断在半句 / 是切分太碎还是拼接没对齐?
组别切分规则段数断点前三个字断点后三个字断点处听感是否复现
A按句末标点,单段上限放宽待填待填待填待填待填
B按逗号或固定字数待填待填待填待填待填

如果 A 组断点消失、B 组集中在逗号处出现断口,说明这份文稿确实被切得过碎,尤其是把「但是」「因此」这类连接词切到段首时更容易听出截断感。需要注意的是,这个结论只针对本次测试文本,换成长句多、数字多的稿件需要重新做一次对照,不能直接套用。

检查拼接处的静音长度与重叠裁切

如果单段文件单独播放完整,拼起来才有断口,问题就在拼接脚本。两类常见错误:一是段间补的静音过长,让正常停顿变成空白;二是切分时为了保上下文让相邻段重叠了若干字,拼接时却没有裁掉重叠部分,导致重复发音或音节错位。下面是一段可替换的拼接骨架:

from pydub import AudioSegment

GAP_MS = 120        # 段间静音,先用小值试,不要一上来就补 500ms
OVERLAP_MS = 0      # 切分时保留的重叠时长,没有重叠就保持 0

out = AudioSegment.empty()
for i, path in enumerate(seg_files):
    seg = AudioSegment.from_file(path)
    if OVERLAP_MS and i > 0:
        seg = seg[OVERLAP_MS:]   # 裁掉与前一段重叠的部分
    out += seg
    if i != len(seg_files) - 1:
        out += AudioSegment.silent(duration=GAP_MS)

out.export("joined.wav", format="wav")

试听对比建议分三轮:第一轮正常速度整体听一遍,第二轮用 0.75 倍速播放重点听断点,第三轮把断点前后两段单独播放,确认单段尾部本身没有缺字。三轮结论不一致时,以单段播放结果为准,因为拼接会掩盖段内问题。段间静音一般可以先从 80 到 150 毫秒试起,句子之间的停顿靠文本标点表达,不要靠补静音硬凑。

SparkAudio 1.0 输出音频断在半句 / 是切分太碎还是拼接没对齐?

把定位到的那一步固化成默认参数

定位到具体环节后,把参数写回配置,而不是每次出问题再手改脚本。至少要固化下面几项:

  • 切分阈值:优先按句末标点切,单段字符上限固定,遇到超长句再按逗号兜底;
  • 段间静音长度:写成一个常量,与重叠裁切时长分开配置;
  • 采样率与声道:请求参数和导出参数保持同一值,不要一处 24000 一处 44100;
  • 输出格式:统一为 wav 或统一为同一种编码,避免不同容器尾部 padding 长度不一致。

固化后需要一条能重复运行的验证命令,用来检查产物是否都符合预期,可以放在每次生成后执行:

for f in out/seg_*.wav out/joined.wav; do
  ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate,channels,duration -of csv=p=0 "$f" | sed "s|^|$f,|"
done

回归时比对的文件建议固定为三份:单段原始返回 single_raw.wav、拼接产物 joined.wav、以及上面那张记录断点前后三个字的对照表。下次同一类文稿再出现断在半句,先用这三份文件对照,通常几分钟内就能判断是切分阈值不合适,还是拼接处的静音与重叠没对齐。