
项目解决什么问题
这个项目不是一个开箱即用的软件包,而是一套经过实际试听迭代的 AI 语音制作方法。它针对几个常见问题:呼吸声孤零零地落在静音里、气与台词之间隔得太久、停顿后的声音突然变得正式、模型重复或漏念句子,以及剪掉台词后环境响动无法自然衔接。
完整流程围绕四件事展开:用 ElevenLabs v4 合成人声,用 ffmpeg 处理音频,用 Whisper 转录核对内容,再用 NumPy 分析响度、音高和采样位置。
核心结论不是“多加呼吸”,而是“把气放到正确的位置”:开口前的短气贴着第一个字,句尾顺势带出气,句中的吸气则保留在半句之间。
主要能力与方法
- 呼吸贴近台词:识别开口前的轻短气息,只移动静音,把气挪到距离台词约 0.08 秒的位置。
- 突兀响度修正:逐个有声块测量响度,将比中位数高出 4 dB 以上的块适度压回。
- 无损式剪句:从干声的静音处剪切,再重新铺设循环响动,尽量保留原来被认可的表演。
- 重复与漏念检测:合成后用 Whisper 转录,比较“听到的字”和“应该念的字”,异常时重新生成。
- 动作响动同步:将响动标记写在动作真正开始的那一句,而不是统一放在一轮台词开头。
- 日常声线塑造:不只是把声音调软,而是明确人物的速度、音高、笃定程度和说话习惯。
准备环境
所需工具
- ElevenLabs v4:负责文本转语音。项目实测使用 v4。
- ffmpeg:用于解码、裁剪、拼接、重采样和导出音频。
- Whisper:负责把生成结果转录回来,检测重复、漏句和内容偏差。
- NumPy:用于音频数组处理、互相关、响度计算和片段移动。
README 的实测环境是 macOS。它没有提供固定安装脚本,因此应根据各工具的官方说明分别安装,并确认 ffmpeg、Whisper 和 Python 环境都能正常运行。
进入分析阶段前,应把音频解码为统一采样率的单声道波形,并准备两组按固定步长计算的数据:每帧音高 f0 与每帧均方根响度 rms。原流程以 10 毫秒为帧步长,响度分析窗口会让边界模糊几十毫秒,因此移动片段时需要保留少量余量。
始终保存两份声音
每次生成后都保存以下版本:
- 干声:只有人声,不包含环境或动作响动。
- 湿声:在人声下铺过响动的最终版本。
干声不仅便于测量呼吸,还能在以后删除一句、替换一段或重新铺底时保留原来的表演。不要只保存最后的混音成品。
第一步:正确编写呼吸提示
不要把“嗯”“哈”或呼吸标签一行一行摆在长静音之间。这样生成的声音往往带有明显音高,响度接近正常说话,前后还各有很长的死静,听起来会像独立音效。
推荐把呼吸写进台词所在的行:
[low, breathy, wet]
[inhales] 看着我…… [exhales]
[long pause]
嗯… 别躲…… 嗯…哈…- 行首只使用短小的呼吸标签,例如
[inhales]、[exhales]或[sighs]。 - 标签和台词之间不要再放一串省略号。
- 句尾可以顺势带一口气,或连续两三声较轻的语气声。
- 空档只写一行
[long pause],不要在空档里排列独立的哼声。 - 句中也可以放吸气;一句话从行首、句中、行尾选择一两处即可。
连续写两个 [long pause] 不一定会让停顿明显增长。项目实测中,一个标签约产生 0.96 秒静音,连续两个约为 1.04 秒。因此不要把精确时长控制寄托在标签字面上。
第二步:把声音切成可测量的“块”
这里的“块”是连续有声的一段。每块测量三个指标:持续时间、平均响度,以及存在音高的帧所占比例。它们可以帮助区分正常台词、真实气息和带音高的独立哼声。
def is_breath(f0, rms, a, b):
dur = (b - a) * 0.01
voiced = (f0[a:b] > 0).mean()
db = 20 * np.log10(rms[a:b].mean() + 1e-9)
return 0.12 <= dur <= 0.9 and voiced < 0.15 and db < -29原项目中的典型范围如下:
- 压低、带气的台词:约 0.5 至 2 秒,响度约为 −22 至 −27 dB,有音高帧约占两成到四成。
- 吸气、出气或嘶声:约 0.3 至 0.65 秒,响度约为 −32 至 −40 dB,有音高帧约占一成。
- 割裂的独立哼声:约 0.4 至 0.5 秒,响度可达 −25 dB,有音高帧超过六成,前后常有 0.6 至 1 秒静音。
这些阈值来自特定声线和样本,不能直接视为所有声音的通用标准。更可靠的做法是先让听者指出问题,再用相邻正常片段建立该声线自己的范围。
第三步:把开口前的气贴到台词旁边
即使提示词已经写对,模型仍可能在呼吸与台词之间自行留下 0.3 秒甚至更长的静音。提示词很难控制几十毫秒级的位置,因此需要在生成后移动音频片段。
处理前后的结构可以理解为:
处理前:上一句 → 静音 A → 呼吸 → 静音 B → 下一句
处理后:上一句 → 更长静音 → 呼吸 → 0.08 秒 → 下一句操作时只交换呼吸和静音的位置,不改变呼吸或台词本身,也不改变整段总时长。这样已有的时间轴、响动铺设和语速统计都不会被破坏。
适合移动的候选块应满足:
- 它被判定为轻、短、无明显音高的呼吸。
- 它后面约 0.14 至 1.5 秒内出现说话块。
- 它前面至少已有约 0.3 秒空白。
- 句中紧跟前半句的吸气不移动,以免破坏原本自然的句中换气。
README 的实测目标间隔为约 0.08 秒。移动时可在呼吸头部多带约 0.02 秒、尾部多带约 0.03 秒,并在新位置对头尾各做约 4 毫秒淡化,降低接缝感。
第四步:修正停顿后的“正经腔”
有些声音在长停顿后会恢复为更清楚、更响、更少气声的普通说话状态。案例中,异常短句比周围内容响 6 dB 以上,有音高帧比例也从约三成提高到六成。
先从提示词减少发生概率
- 停顿后先贴一口气,或以低低的哼声滑入台词。
- 句尾使用拖长的收法,不要每次都用干脆的问号或句号。
- 两三个词的短句可与相邻句合并,避免单独被念成清楚的播报腔。
- 需要重新强调声线时,把小标签插在话头,不要总是单独占一行。
[long pause]
嗯… [low, breathy] 别躲……如果切段器会在每个行首声线标签处新建片段,这种行内写法还能减少接缝和音高跳变。
再用程序压住异常响度
逐块计算响度,以当前段落各说话块的中位数作为参考。如果某块高出 4 dB 以上,就将它压到只高约 2 dB,最多衰减 6 dB,并在头尾使用约 25 毫秒的渐变包络。
必须逐块测量,而不是逐句测量。短暂冒头的半秒声音一旦与后续长句合并取平均,6 dB 的异常可能被稀释到阈值以下。程序只能修正音量,无法把缺少气声的音色直接变回来,因此提示词和后处理需要同时使用。
第五步:合成后立即进行转录质检
模型偶尔会把一句念两三遍,或者直接漏掉最后一句。稿件本身可能完全正确,所以每个合成片段都应经过一次转录核对。
项目采用两类信号:
- 去掉方括号标签和常见语气字后,寻找长度至少六个汉字、在转录中重复出现但原稿只有一次的片段。
- 比较转录字数与原稿字数;少于预期约两成时,视为可能吞句。
检测到异常后重新生成一次,再给两个版本打分,保留分数更低的版本。语气字经常无法被准确转录,因此不应把“嗯、哈、啊、呃”等字作为主要对账依据。
实时电话中,Whisper 转录二三十秒音频可能需要约三秒。后续片段可在当前片段播放时后台检测;第一段没有可用于隐藏延迟的播放时间,可以先用“时长除以有效字数”快速筛查。原案例正常范围约为每字 0.41 至 0.62 秒,超出范围时再启动完整转录。
第六步:剪掉一句而不重新生成
如果听者喜欢的是某一次具体表演,不要因为需要删除一句就重念整段。每次生成都是一次新的现场,呼吸、停顿和句尾细节很难精确复刻。
- 定位静音:先以约 50 毫秒为单位绘制响度,再对候选小片段分别转录,确认它是台词、呼吸还是空白。
- 从干声下刀:让接缝两端都落在最静的位置,并给每片头尾保留十几毫秒淡入淡出。
- 重新铺响动:不要直接剪已经铺底的成品,否则循环响动的节拍可能在接缝处跳变。
- 重新转录:确认成品只剩目标句子。
- 逐采样比对:检查没有修改的区域是否仍与原声一致。
如果不知道湿声使用的是哪段循环素材,可以在人声完全静止的区间提取成品波形,再让候选素材循环并做互相关。最高相关位置可以给出素材相位,点积还能估计铺底增益。案例中两种素材的相关度达到 0.99,估计增益分别约为 0.200 和 0.241。
比对必须精确到采样。十几个采样的错位就可能让相似度明显下降,造成“素材垫错了”的误判。对齐后,案例中的未修改片段相似度达到 0.996 至 0.998。
第七步:让响动从正确时刻开始
项目通过提示中的自定义标记决定底层响动类型,再在送入 ElevenLabs 前移除这些标记。关键规则是:声音从哪一句开始,标记就写在哪一句。
licking:对应缓慢湿声。fingering:对应较黏的动作声;快速状态可写fast fingering。sucking:对应缓慢湿声。thrust:只在动作真正开始的句子前出现,不能提前放在整轮开头。still:用于停止当前循环响动。
如果标记会触发切段,那么动作真正开始的位置值得保留一道新接缝;其他只用于重申声线的小标签则尽量放在话头内部。
第八步:设计平常说话的声线
“不自然”不一定意味着声音不够温柔。原项目先将旧模型升级为 ElevenLabs v4,并测试了更放松、更柔软的提示,但听者认为角色因此变成了另一个人。最终有效的方向是明确人物自身的口气。
被选中的基础提示为:
[low, slow, confident, a little teasing]对应设置是 v4、稳定度 0.35。句头可随机加入 [exhales]、[chuckles softly]、一个“嗯……”或什么都不加。不要每次都添加,否则自然变化会变成固定口头禅。
- 只修改送去合成的文本,不改聊天界面和历史记录中的原文。
- 如果句子本来就以语气词开头,不要重复叠加。
- 已有声线标签或纯英文内容可直接跳过自动包装。
- 电话使用独立提示体系,不要套用日常语音的随机句头。
高级技巧与常见陷阱
提示规则要写“应该做什么”
一句“不要写某种声音”可能让文本生成器彻底停止使用它。更可靠的做法是明确写出需要出现的类型、位置和最低频率,再把偶尔出现的高音或异常读法交给程序检测。
不要重复使用同一口呼吸
把喜欢的原片段搬到新版本时,可以从同一次生成中取材,但同一口气不要在两个位置重复出现。否则听者可能察觉完全相同的细节被复制。
建立可检索的素材盒
将被认可的吸气、嘶声和短促呼吸按原文件、时间范围和使用位置保存。以后某个呼吸标签只生成了静音时,可以从素材盒补上一口。README 同时指出,自动补气仍需要带时间戳的接口;仅凭“句中出现静音”无法区分呼吸缺失和正常省略号停顿。
实时电话要处理抢话
当系统还没播放回复、对方又开始说话时,应丢弃尚未出声的旧回复,直接处理新内容。不要把新话缓存到旧回复播放完,否则同一句话可能被回应两次。对于无法识别成文字的短声音,原流程把自动代入语气声的等待时间从 2.5 秒延长到 4.5 秒,以降低抢在真实话语前触发的概率。
先听出问题,再决定量什么
响度、音高、静音长度、字数比例和互相关都只能验证已经发现的问题。它们无法独立判断“味道是否正确”。最有效的流程仍是先由听者指出具体不自然的位置,再建立对应指标。
推荐的完整工作流
- 编写带声线、呼吸、停顿和响动标记的文本。
- 移除只供后处理使用的响动标记,将剩余文本送入 ElevenLabs v4。
- 保存干声,并用 Whisper 转录检查重复、漏句和内容偏差。
- 计算
f0与rms,将音频切分成说话块、呼吸块和其他有声块。 - 将开口前的呼吸移动到距离台词约 0.08 秒的位置,保留句中呼吸。
- 逐块检测突兀响度,对异常块做平滑衰减。
- 根据动作标记铺设循环响动,导出湿声版本。
- 再次转录,并检查总时长、长静音数量、呼吸位置和未修改区域的采样相似度。
- 保存听者认可的原始片段及其时间范围,不要只保留最终混音。
结语
自然 AI 语音并不是靠堆叠更多呼吸标签获得的。更可靠的方法是让气贴着话、让动作声从正确时刻开始、让每段合成结果经过转录核对,并在需要剪辑时尽量保留原来的表演。
提示词负责决定“写什么”和“以什么口气说”,程序负责几十毫秒的位置、异常响度、重复漏念与采样级衔接。两者配合,才能把偶然生成的一条好声音变成可重复执行的制作流程。更多背景可参考 ElevenLabs 提示词指南、ElevenLabs 呼吸声说明以及说话呼吸研究。
