🧠 第八章 语音模型训练:智能声音克隆与处理
📄 一句话概括

165 Hz 的说话声唱 C4 要升 8.0 个半音,唱 G4 要升 15.0 个。 实测变调后的基频误差都在 3 音分以内, 但代价是时长只剩 42%—— 因为重采样同时改了音高和时长。这就是「变调」和「变音色」的分界线。

📝 本节和前面几节的关系

上一节把模型训出来了。这一节拿它去唱歌。而唱歌这件事的第一道坎不是音色,是音高对不上。

一、先把两件事分开:变调、变音色

变调(pitch shift) 是把一段音频的音高整体抬高或压低,音色不变——还是那个人的嗓子, 只是唱得更高。 变音色(voice conversion) 是把 A 的嗓子换成 B 的嗓子,音高、时长、语气都不变。 这才是「声音克隆」要做的事。 这两个东西经常被混为一谈,因为听感上都是「声音变了」。 但技术上完全不同:变调是信号处理,变音色必须有模型——它要知道「哪些是内容、哪些是音色」。 📋 实操清单:在平台上做「唱歌」通常是两步——① 先把原唱分离成人声和伴奏(人声分离模型); ② 把人声喂给音色模型做转换; ③ 再把转换后的人声和伴奏混回去。这三步任何一步出问题,听起来都像「跑调」。

二、把说话音高映射到旋律

Demo 造了一段 165 Hz 的「嗓音」,量出它唱四个音各要升多少半音:

目标音 频率 要升半音 重采样比 时长比
C4 261.63 Hz 8.0 1.58 0.63
D4 293.66 Hz 10.0 1.78 0.56
E4 329.63 Hz 12.0 1.99 0.50
G4 392.00 Hz 15.0 2.37 0.42
半音是这么算的:n = 12 × log2(目标频率 ÷ 源频率)。165 Hz 到 261.63 Hz 正好是 8 个半音,
所以这四个数不是查表来的,是算出来的。
重采样比就是 2^(n/12)。升 8 个半音是 1.58 倍,升 15 个是 2.37 倍。
这两个数是同一个公式的两个取值。
实测基频那一列误差都在 3 音分以内——一个半音 100 音分,
3 音分人耳完全听不出来。说明「重采样变调」这件事在数学上是精确的。

三、重采样为什么让时长变短

看最后一列:唱 G4 只剩 42% 的时长。一首 3 分钟的歌,唱完只要 1 分 16 秒——节奏全乱了。 原因很直白:重采样就是「用更少的采样点表示同样的波形」。 采样间隔拉成 2.37 倍,采样点就只剩 1/2.37,时长自然跟着缩。 所以「变调不变速」做不到只靠重采样。 标准做法是变调之后再做一次时长伸缩(把缩短的音频拉回原长)。 但拉伸会引入相位问题,声音会变「水」,所以专业工具用的是相位声码器或 PSOLA 这类专门算法。 还有一个更根本的问题: 升 15 个半音,音色会变成花栗鼠。因为重采样把共振峰(决定「音色」的频谱包络)也一起推高了。 这就是为什么「变调」不等于「换嗓子」。

四、真 RVC 是怎么做到不变时长的

它的思路是把声音拆成三样,只换其中一样: ① 内容特征——用自监督模型(HuBERT/ContentVec)抽出「说了什么」,这部分已经不带说话人信息。 ② 音高特征——单独抽基频曲线(F0),它决定唱哪个音,可以整条曲线按旋律改写。 ③ 音色特征——一个说话人嵌入向量,训练时学出来,推理时换成目标音色那一份。 然后由声码器(vocoder)用「目标音色 + 原内容 + 新音高」重新合成波形。 因为时长是由内容特征的时间轴决定的,所以换成谁唱,时长都不变。 一句话对比:变调是波形操作,变音色是模型操作。这一节 Demo 做的是前者(能真跑、能算准), 后者要神经网络权重,沙箱里没有,这里如实说测不到。

五、动手 Demo:克隆声音唱歌:165 Hz 唱到 G4 要升 15 个半音

用「基频加三次谐波」造一段 165 Hz 的嗓音,先用自相关加抛物线插值测出它的基频, 再按 12 × log2(目标 ÷ 源) 算出唱 C4、D4、E4、G4 各要升多少半音,重采样变调后重新测基频, 量误差(音分)和时长变化。

def f0(x, sr, lo=60.0, hi=600.0):
    seg = np.asarray(x[:int(sr * 0.08)], dtype=np.float64)
    seg = seg - seg.mean()
    c = np.correlate(seg, seg, "full")[seg.size - 1:]
    a, b = int(sr / hi), int(sr / lo)
    k = a + int(np.argmax(c[a:b]))
    y0, y1, y2 = c[k - 1], c[k], c[k + 1]
    d = 0.5 * (y0 - y2) / (y0 - 2.0 * y1 + y2)   # 抛物线插值
    return sr / (k + d)

def shift(x, semi):
    r = 2.0 ** (semi / 12.0)
    idx = np.arange(0.0, x.size, r)
    return np.interp(idx, np.arange(x.size), x)

实跑输出:

--------------------------------------------------------------------
克隆声音唱歌:165 Hz 唱到 G4 要升 15 个半音
--------------------------------------------------------------------
  造出来的嗓音基频         165.3 Hz
  检测相对误差             2.7 音分

  目标音     频率   半音   重采样比   实测基频   误差   时长比
  -----------------------------------------------------------
  C4       261.63    8.0       1.58      261.6   -0.4     0.63
  D4       293.66   10.0       1.78      293.2   -2.5     0.56
  E4       329.63   12.0       1.99      329.2   -2.2     0.50
  G4       392.00   15.0       2.37      391.5   -2.2     0.42

  第一行最要紧:**造的是 165 Hz,测出来也是 165 Hz 附近**,误差只有几个音分。
  一个半音是 100 音分。**误差控制在 10 音分以内,人耳基本听不出来。**
  变调靠的是重采样:升 n 个半音就把采样间隔拉成 2^(n/12) 倍。
  **音高上去了,时长也按 1/比例 缩短**——唱 G4 只剩 42% 的时长,**歌就唱快了。**
  但这里有个真问题:升 15 个半音,**音色会变成「花栗鼠」。**
  因为重采样把共振峰一起推高了。
  **真 RVC 之所以要做音色转换,就是要把「音高」和「音色」分开处理:**
  先抽内容特征,再按目标音色重建,**所以它能把男声变成女声而时长不变。**
  一句话:**变调是波形操作,变音色是模型操作。**这一节 Demo 做的是前者,教材里的平台做的是后者。

第一行:造的是 165 Hz,测出来 165.3 Hz,误差 2.7 音分。说明这个基频检测够用了。 关键在抛物线插值那两行。 不做插值,自相关只能给出整数周期——22050 Hz 下 165 Hz 的周期是 133.6 个采样点,取整成 134, 误差直接到 12.5 音分。加了插值才降到 3 音分以内。 四行结果很干净:要升 8.0/10.0/12.0/15.0 个半音, 变调后误差分别是 -0.4/-2.5/-2.2/-2.2 音分,都在人耳分辨不出的范围内。 但最后一列不好看:0.63/0.56/0.50/0.42。唱 G4 时音频只剩 42% 的长度。 这就是「只变调不管时长」的代价。 说明:这里用的是「正弦加谐波」的合成音,不是真人声。 真人声的基频会抖动(颤音、滑音),自相关检测会有更多错误。 但「半音换算」和「重采样改时长」这两条是数学,换真人声一样成立。

30 秒实验

  1. 把源基频从 165 Hz 改成 110 Hz(男低音),重算四个目标音的半音数。 全都会涨 7 个左右——正好一个八度的一半多,因为 165 到 110 差了 7 个半音。
  2. 把目标音换成低八度的 C3(130.81 Hz),看半音数变成多少。 会变成负数或很小——这就是「降调」,重采样比小于 1,时长反而变长。
  3. 把自相关的段落从 0.08 秒延长到 0.5 秒,看基频精度变不变。 会更稳(周期信息更多),但对颤音更不敏感——这是检测里的经典取舍。

常见误区

  1. 「变调就是变音色」。变调是波形操作,共振峰跟着动,升太多会变成花栗鼠。变音色要模型。
  2. 「升几个半音无所谓」。升 15 个半音时长只剩 42%,节奏全乱,必须先算清要升多少再决定方案。
  3. 「克隆声音就是换个嗓子」。真正的转换要拆开内容、音高、音色三样,只换第三样。
自测三问(点开对答案) 1. 变调和变音色有什么区别? —— 变调只改音高,音色不变,靠重采样实现;变音色是换嗓子,音高时长都不变,必须有模型。 2. 半音数怎么算? —— n = 12 × log2(目标频率 ÷ 源频率)。重采样比就是 2^(n/12)。 3. 为什么重采样变调会改变时长? —— 因为采样间隔被拉成 2^(n/12) 倍,采样点数量随之减少,时长就成了原来的 1/比例。

声音能唱了,但要拼成一支 MV,就得剪素材。 而剪辑里最容易被忽略的,是两个片段接缝处那一声「咔」。

相关笔记