我在一家做智能门锁的公司负责售后知识库。今年客服部想把一百多条故障排查步骤做成语音:一部分放进客服热线的自助语音菜单,用户按键就能听「指纹识别不了怎么办」;另一部分配进安装教程短视频里当旁白。以前这些都是客服组长抽空录,一条念错就整条重录,产品一改型号又要再录一遍。我被拉去想办法,结论是用 AI配音:文字改了重新生成就行,声音也能保持统一。下面是我写的几段脚本:用开源库 edge-tts 批量生成语音和字幕,用 Python 预处理读音,再用 ffmpeg 转成热线系统要的格式、给视频旁白混上背景音乐。最后也说说不写代码的选择,客服组的同事临时改一两条,我一般让他们直接用小马配音。
一、先选声音:列出可用的中文音色
edge-tts 是一个开源 Python 库,调用的是微软 Edge 浏览器「大声朗读」背后的在线语音服务,装好就有命令行工具。先看看有哪些中文声音:
pip install edge-tts
# 列出全部声音,再筛出普通话的
edge-tts --list-voices | grep zh-CN
# 用指定声音生成一条试听
edge-tts --voice zh-CN-XiaoxiaoNeural \
--text "您好,指纹识别不了时,请先用干净的软布擦拭指纹头。" \
--write-media test.mp3
我们试听了七八个,最后热线用了一个语气平稳的女声,视频旁白换了一个男声,以后所有条目都固定用这两个,用户听多了会形成「这是官方语音」的印象。
需要注意:它走的是在线服务,必须联网;这个服务也不是微软正式对外提供的开发接口,接口哪天变了库就可能失效,商用授权也不明确。我们内部测试、做原型用它,正式上线的热线语音,后来改用了云厂商的正式语音合成接口,接法类似,下面的预处理和格式转换代码可以照用。
二、调语速、出字幕:命令行参数
edge-tts 提供 --rate、--volume、--pitch 三个参数,分别调语速、音量和音调,默认都是 0。热线电话的音质差,语速放慢一点用户更容易听清;视频旁白要跟画面对口型,语速保持默认。--write-subtitles 会同时写出一份 SRT 字幕,视频剪辑时直接导入:
# 语速放慢 10%,同时输出字幕
edge-tts --voice zh-CN-XiaoxiaoNeural --rate=-10% \
--file step_012.txt \
--write-media step_012.mp3 --write-subtitles step_012.srt
负数一定要写成 --rate=-10% 这种带等号的形式,写成 --rate -10% 会被命令行当成另一个参数而报错。还有一点:老版本支持传自定义 SSML 标记来控制停顿和读音,5.0 以后已经去掉了,现在只能靠文字本身和上面这几个参数控制,所以下一节的文本预处理就很重要。
三、读音不对:先做文本预处理
AI配音读错,大多不是声音的问题,而是文字本身有歧义。我们的稿子里有三类高发问题:产品型号(「K20」被读成「K 二十」,客服习惯说「K 二零」),英文缩写(「App」「NFC」),以及多音字(「重置」的「重」)。既然不能用 SSML,我就在生成前做一遍替换,把容易读错的写法换成确定的读法:
import re
# 读音替换表:左边是稿子里的写法,右边是希望读出来的写法
READ_AS = {
"K20": "K二零",
"NFC": "N F C",
"App": "A P P",
"重置": "从置", # 用同音字强制读 chóng
}
def to_spoken(text: str) -> str:
for src, dst in READ_AS.items():
text = text.replace(src, dst)
# 电话号码、验证码这类数字,逐位读更清楚:每个数字之间加空格
text = re.sub(r"\d{4,}", lambda m: " ".join(m.group()), text)
# 长句在逗号后多一个顿号,听起来停顿更明显
return text.replace(",", ",、")
同音字替换只用在送进语音合成的文本里,字幕要用原文,不然观众会看到「从置」这种错别字。所以我在批量脚本里分开处理:语音用 to_spoken 之后的文本,字幕用原文重新对时间。最后那条加顿号的规则效果因声音而异,建议先试听几条再决定是否保留。
四、批量生成:从表格到一百多条音频
知识库条目放在一张 CSV 里,三列:编号、标题、正文。脚本逐行读取,预处理后调用 edge-tts 的 Python 接口生成音频,已经生成过的跳过,改了稿子的条目删掉旧文件重跑即可:
import asyncio
import csv
from pathlib import Path
import edge_tts
VOICE = "zh-CN-XiaoxiaoNeural"
OUT = Path("voice")
async def make_one(no: str, text: str) -> None:
target = OUT / f"{no}.mp3"
if target.exists():
return
tts = edge_tts.Communicate(to_spoken(text), VOICE, rate="-10%")
await tts.save(str(target))
print("ok", no)
async def main(csv_path: str) -> None:
OUT.mkdir(exist_ok=True)
with open(csv_path, encoding="utf-8-sig") as f:
rows = list(csv.DictReader(f))
for row in rows: # 逐条生成,避免并发太高被服务端拒绝
try:
await make_one(row["编号"], row["正文"])
except Exception as e:
print("fail", row["编号"], e)
asyncio.run(main("steps.csv"))
to_spoken 就是上一节的函数。我刻意没有做并发:一百多条逐条跑也就几分钟,并发太高反而容易被在线服务限流,失败了还得重跑。
五、ffmpeg 后处理:热线格式和背景音乐
生成的是 MP3,热线系统要的是单声道、低采样率的 WAV。具体采样率和编码各家系统不一样,我们的要求是 8000Hz、16 位 PCM,转换和统一响度一起做:
# 响度统一后转成热线要的格式(参数以你们热线系统的要求为准)
ffmpeg -i voice/012.mp3 -af loudnorm -ar 8000 -ac 1 -c:a pcm_s16le ivr/012.wav
# 视频旁白:背景音乐压低音量后和人声混合,时长跟人声一致
ffmpeg -i voice/012.mp3 -i bgm.mp3 -filter_complex \
"[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=first[out]" \
-map "[out]" narration_012.mp3
loudnorm 是 ffmpeg 自带的响度标准化滤镜,一百多条音频音量忽大忽小的问题,过一遍就统一了。amix 的 duration=first 表示以第一路(人声)的时长为准,背景音乐比人声长也不会拖尾。背景音乐的音量我从 0.15 开始试,人声清楚、音乐若有若无就合适。
六、不想写代码:客服组在用的工具
脚本解决的是一次性批量生成和格式转换。日常更多的是零散需求:某条步骤改了一句话,客服组长想马上换掉热线里的那条语音。让他们改 CSV、跑脚本不现实,我给客服组推荐了下面几个:
- 小马配音:做 AI 配音的小程序,手机上打开就能用。粘贴文字,选择主播,调好语速和音量,在关键信息前加停顿,多音字读错了可以在多音字设置里手动改读音,生成后先试听,满意再导出音频。客服组长改一条语音,在小马配音里从粘贴文字到导出几分钟就能做完。限制是需要联网,按字数和音色计算,额度和价格以小程序里的说明为准。
- 剪映的文本朗读:适合做安装教程这类带字幕的短视频,在时间线上加好文字,选中后用文本朗读生成配音,字幕和声音一起出来;单独要一段音频文件就没那么方便。
- 手机自带的朗读屏幕:辅助功能里开启后能把屏幕上的文字读出来。它没法导出音频,但写完稿子让手机读一遍,哪里拗口、哪里数字容易听错,一下就能发现。
七、几种做法怎么选
- 一次生成几十上百条、要批量转格式:edge-tts 批量脚本加 ffmpeg 后处理,正式商用换成云厂商的正式语音合成接口。
- 需要和视频画面对齐的字幕:edge-tts 的
--write-subtitles直接出 SRT。 - 零散改一两条、要多音字和停顿调得细:小马配音,改完导出替换即可。
- 本来就在剪视频:剪映的文本朗读。
- 只是检查稿子读起来顺不顺:手机朗读屏幕。
八、常见问题
问:不会写代码,手机上怎么把一段文字做成 AI配音?
答:可以用小马配音。把文字粘贴进去,选一个主播,调好语速,生成后试听,没问题就导出音频文件,可以直接放进视频或者传到播放设备里。
问:配音里的多音字总是读错,怎么改?
答:在小马配音里可以用多音字设置手动指定读音,改一次之后生成就不会再错;写代码的话,按第三节在送进合成之前用同音字替换掉。
问:想在时间、型号这些关键信息前停顿一下?
答:小马配音里可以在关键信息前直接加停顿。用 edge-tts 的话,新版本不支持自定义 SSML,只能通过标点控制,比如在关键信息前加逗号或句号,效果因声音而异,要试听确认。
问:--rate -10% 报错,提示参数不对?
答:负数参数要写成 --rate=-10%,带上等号,否则命令行会把 -10% 当成一个新的参数。
问:edge-tts 之前好好的,突然报错生成不了?
答:它依赖的在线服务偶尔会调整,常见的处理是先把 edge-tts 升级到新版本:pip install -U edge-tts。如果是正式业务,建议不要依赖它,换成有服务保障的正式接口。
问:用 amix 混完背景音乐,人声变小了?
答:amix 混合时会自动平衡各路音量,人声听起来会比原来小。可以把背景音乐的 volume 再调低,或者混合后再加一道 volume 或 loudnorm 把整体响度提上来。
问:热线系统里播放有杂音,或者干脆不认文件?
答:多半是格式不符合要求。确认系统要的采样率、声道数和编码,按第五节的命令转成对应的 WAV;转完先在电脑上播放一遍,确认没有破音再上传。
我的做法
AI配音我现在分两条线:一百多条知识库语音和视频旁白,用 edge-tts 脚本批量生成原型,正式版换成云厂商的语音合成接口,读音问题统一靠替换表解决;客服组日常改一两条,就用小马配音调好停顿和多音字再导出。热线上线以后,用户按键就能听到排查步骤,客服组长再也不用每次改型号都对着话筒重录了。
