「莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。」
苏轼被贬黄州,路上遇上大雨。同行的人都狼狈躲雨,他却「吟啸且徐行」。雨点打树叶可以忽急忽缓,「任平生」那个调门从头到尾没变。
900 多年后我们做音频处理,碰上的就是同一道题:Time Stretch(变速不变调),和它反过来的孪生兄弟:Pitch Shift(变调不变速)。
打开变声 App,对着手机说一句话,出来变成了萝莉音、大叔音、机器人音。很多人觉得这不就是「调高调低频率」嘛,有什么难的。
真去做,你会发现一个诡异的现象:你想把声音变尖一点,于是把采样率调高,声音确实变尖了,但同时播放速度也变快了,一句话半秒钟就讲完了,像花栗鼠。
变尖和变快,被绑死在了一起。
这就是变声要解决的核心难题:怎么只改变音高(Pitch),不改变时长? 或者反过来,只改变时长,不改变音高? 这两个操作在音频处理里叫 Pitch Shift(变调)和 Time Stretch(变速),合起来叫 TSM(Time-Scale Modification,时间尺度修改)。
这篇把这两个操作的原理讲明白,再告诉你变声 App 到底在做什么。
为什么简单调采样率不行
先理解最基础的:如果你把一段音频的采样率从 44100Hz 播放成 22050Hz,会发生什么?
所有频率都减半——男声变女声,音高降一个八度。但同时,播放速度也减半了,原来 1 秒的音频变成 2 秒。
反过来,22050Hz 当成 44100Hz 播放,频率翻倍,时长减半。
音高和时长是绑定的。 这是物理决定的:波形被整体拉伸或压缩,频率和时间是一回事的两面。
但人耳对音高和时长是分别感知的。所以我们需要把它们解耦——这就是 TSM 算法要干的事。
TSM 的核心思想:分帧 + 重叠相加
所有 TSM 算法的基本套路都是一样的,记住这一段就懂了一大半:
第一步:分帧。 把音频切成一小段一小段,每段比如 50ms,相邻段之间有重叠(比如重叠 25ms)。
第二步:调整帧间距。 如果你想要变速(放慢),就把帧之间的间距拉大;想要变快,就把间距缩小。
第三步:重叠相加(OLA,Overlap-Add)。 把调整间距后的帧重新拼起来。重叠的部分加在一起。
听起来很简单对吧?但直接这么干,效果一塌糊涂。为什么?因为你在拉伸的时候,帧之间的相位对不上了。原来波形是一个连续的正弦,你把帧拉开之后,第 2 帧的波峰和第 1 帧的波谷对上了,拼起来波形全是毛刺,听起来就是「滋滋」的金属噪声。
这就是最原始的 OLA 算法。它的问题就是相位不连续。所有后续的改进算法,都是在解决这个相位问题。

WSOLA:解决相位问题的经典方案
WSOLA(Waveform Similarity OLA) 是目前用得最广的 TSM 算法之一,SoundTouch 库用的就是它的变种。
WSOLA 的聪明之处在于:它不是傻傻地把帧按固定间距排好,而是在允许的范围内,给每一帧找一个「最相似」的位置来重叠。
具体来说,第 N 帧本来应该和第 N-1 帧的尾部重叠。WSOLA 会在第 N 帧附近的一个小窗口里搜索,找到一个和第 N-1 帧尾部波形最像的位置,用这个位置来重叠。这样波形的连续性就保住了,相位也基本对得上。
WSOLA 的优点:实现相对简单,计算量适中,对语音和音乐效果都不错。缺点:对打击乐这种瞬态信号处理不好——鼓声会被拉成「嗡嗡」的拖音,因为 WSOLA 会把瞬态也当连续波形去匹配。
PSOLA:语音变调的王者
如果你专门做语音变调(不是音乐),PSOLA(Pitch Synchronous Overlap-Add,基音同步重叠相加) 是更专业的选择。
PSOLA 的核心是「基音同步」。人说话的声音有一组基音周期(声带振动的周期),PSOLA 会先检测出这些基音标记点,然后以基音周期为单位来分帧和重叠,而不是用固定长度。
这样做变调特别自然:你要升高音高,就把基音周期缩短(相当于声带振动变快);要降低,就拉长。因为操作的是基音周期,相位天然就是对齐的,出来的声音非常干净。
PSOLA 的缺点:它需要准确的基音检测(pitch detection),而基音检测本身就是一个难题——尤其是清辅音(如 s、sh)和静音段没有基音。所以 PSOLA 实现里要区分浊音段(有声带振动,有基音)和清音段(用别的方法处理)。
变声 App 里的「萝莉音」「大叔音」,基本都用 PSOLA 或其变种(比如 TD-PSOLA)做基音移频。
Phase Vocoder:频域方案,音乐更合适
还有一个流派是在频域做:Phase Vocoder(相位声码器)。
它先把每帧做 FFT 变到频域,调整频域的相位(让相邻帧的相位保持连续),再做 IFFT 变回来。因为是逐频点处理相位的,所以可以做到非常精细的相位对齐。
Phase Vocoder 的优点:对持续音(如钢琴长音、人声拖音)效果非常好,可以做到大比例的拉伸(2倍、4倍)而不破。缺点:计算量大,而且会有「相位散漫」问题——处理后的声音会带一点机械感/颤抖感,俗称 phasiness。
Rubber Band 库用的就是 Phase Vocoder 加上瞬态检测的混合方案,是目前公认效果最好的开源 TSM 库之一,很多商业音乐软件都在用。
Pitch Shift = Time Stretch + 重采样
讲清楚了 Time Stretch,Pitch Shift 就简单了。
Pitch Shift(变调不变速)的实现,是一个巧妙的小技巧:
先用 Time Stretch 把音频拉长或缩短(比如拉长 2 倍)。
然后用重采样把它压回原来的长度(采样率减半)。
第 1 步改变了时长没改变音高,第 2 步同时改变了音高和时长。两步叠加:时长抵消了,只剩下音高变化。
这就是变调的标准做法,几乎所有 Pitch Shift 实现都是这个套路。
真正的变声,不只是变调
如果你只做 Pitch Shift,把男声升高,听起来确实「尖」了,但不像女声——更像一个捏着嗓子说话的男的。
为什么?因为 音高只是声音特征的一部分,还有「共振峰」(Formant)。
共振峰是声道(口腔、鼻腔)形状决定的。男声声道长,共振峰频率低;女声声道短,共振峰频率高。如果你只移频,把基音和共振峰一起升高,声音是变尖了,但共振峰比例不对,听起来就不自然。
真正的变声要做 共振峰保留(Formant Preservation):变调的时候只移动基音,保持共振峰的位置不变。这样男声变女声才会自然——基音高了,但声道「形状」没变,听起来就是同一个人换了性别,而不是变了物种。
更高级的变声(如机器音、电音)会加别的处理,比如声码器(Vocoder)、Ring Modulator,但这些就超出入门范围了。

实战:开源库怎么选
如果你要快速做一个变声功能,不要从零写 PSOLA。直接用现成的库:
| 库 | 算法 | 适用场景 | 特点 |
|---|---|---|---|
| SoundTouch | WSOLA 变种 | 语音变速变调 | 轻量、跨平台、移动端友好 |
| Rubber Band | Phase Vocoder + 瞬态检测 | 音乐变速变调 | 效果最好,LGPL 协议 |
| sonic | 采样级插值 | 简单变速 | 极简,嵌入式可用 |
| librosa/effects | Phase Vocoder | Python 原型 | 适合算法验证 |
选型建议:
做语音变声(App、游戏语音):SoundTouch,够用且省 CPU。
做音乐处理(DJ 软件、卡拉 OK):Rubber Band,效果明显更好。
只是做个 demo:librosa 几行代码搞定。
嵌入式极致精简:sonic。
几个坑
坑一:变速比例太大,质量崩坏。 TSM 算法都有个「舒适区间」。变速比例在 0.5~2.0 倍之间效果都不错。超过这个范围, artifacts(伪影)会越来越明显。如果需要 4 倍变速,建议分两次做(2 倍 × 2 倍),比一次 4 倍效果好。
坑二:变调后人声「颤抖」。 这是 Phase Vocoder 的 phasiness 问题,PSOLA 一般没这个问题。如果你用频域方案变调语音,听到颤抖,换 PSOLA 系的库。
坑三:实时变声的延迟。 变声需要缓冲一帧(通常 20-50ms)才能处理,这会引入延迟。实时通话场景下这个延迟很敏感,要么用极短帧(牺牲质量),要么干脆不在通话链路里做变声,只做离线/录音回放的变声。
小结
变声看着玄乎,拆开就两件事:变调(Pitch Shift)和变速(Time Stretch),本质都是 TSM。
TSM 的核心是「分帧 + 调整间距 + 重叠相加」,所有算法的差别都在于怎么解决重叠时的相位连续性问题——OLA 不管相位、WSOLA 找相似波形、PSOLA 用基音同步、Phase Vocoder 在频域修相位。
而自然的变声,除了变调,还要做共振峰保留,否则出来的就是「捏嗓子」的声音。
下一篇是这个阶段(音频编解码)的最后一篇,讲音质怎么打分——你做了这么多音频处理,怎么知道效果好还是不好?PESQ、POLQA、MOS 体系登场。
如果觉得有用,欢迎转发给身边搞音视频的朋友。有问题可以在评论区留言,我会及时回复。
夜雨聆风