Android 音频倍速的原理与算法分析
qingmei2 opened this issue · comments
概述
音视频倍速 是内容类APP
非常重要的功能,其内部包含了 视频流 和 音频流 的倍速,其中视频倍速原理相对简单,即在解码视频帧时提升帧率即可。
音频倍速 相对复杂,众所周知,声音的本质其实是 物体振动时产生的声波,因此音频的倍速是 将语音信号在时域上拉长或缩短,考虑到用户的体验,在保证声音变速的同时,语音的采样率、基频以及共振峰都不能发生变化,以此达到 变速不变调 的目的。
对于 Android
平台的应用而言,音频倍速通常有3种实现方式:
实现方案 | 应用 | 简介 |
---|---|---|
Android AudioTrack | Android 原生音视频架构 | 原生支持,但存在兼容性问题,倍速效果不佳 |
Sonic Library | Google ExoPlayer | 基于 WSOLA 算法, 使用定位基因周期法寻找相似帧 |
SoundTouch Library | Bilibili ijkPlayer | 基于 WSOLA 算法,使用寻找相关峰法寻找相似帧 |
对于原生的 AudioTrack
而言,其本身提供了处理PCM
音频流的功能,但由于其默认被系统的 MediaPlayer
绑定,而后者本身在不同平台上的兼容性就不佳,因此很少被使用。
Sonic
库则被 Google
大名鼎鼎的开源播放器 ExoPlayer
所使用,其内部基于 时域压扩算法 (Time-scale modificatio,下简称TSM算法
), 通过定位 基音周期 的方式,对输入的语音信号进行不断的分帧与合帧的处理,最终合成新的信号以达到倍速的效果。
SoundTouch
则被 Bilibili
开源的 ijkPlayer
所内置,内部仍然基于TSM
算法,和Sonic
不同的是使用了 寻找相关峰 进行语音信号的合成。
不可否认,信号的合成必然会造成原始音频的失真,区别在于Sonic
是基于 基音周期 的,因此变速后的语音信号对人声音影响较小;而 SoundTouch
倍速效果更适用于综合性的场景。
但在实际应用中,问题却不断涌现:
-
- 从上述结论来看,既然对人声音影响较小,那么针对歌曲的倍速播放,
Sonic
效果应该更好,但实际中,Sonic
在高倍速下听感失真明显,和SoundTouch
的效果有显著差距,导致该现象的原因是什么?
- 从上述结论来看,既然对人声音影响较小,那么针对歌曲的倍速播放,
-
- 如何解决该问题,两种倍速方案各自适用的场景又是什么?
要搞明白这些疑惑,就需要从原理和具体的算法实现进行分析。
音频倍速原理
1、TSM基本原理
音频倍速的实现思路分为针对 时域 信号或者 频域 信号分析,但由于频域的复杂度过高,因此实践中通常从时域信号着手。
时域压扩(TSM) 也正是基于时域信号的处理中的典型算法,其提供了 变速不变调 的音频处理实现。
音频信号的处理过程中,不可避免的要进行 分帧 (analysis fames
)操作,帧的长度大多选取是 20ms
到 50ms
之间,并进行加窗操作,而由于加窗操作本身会对一帧信号的两端进行抑制,因此分帧不能按长度分段截取,而是相互重叠一部分(overlap
),之后再进行 合帧 (synthesis frames
)。如果分帧以 50%
的 overlap
,而合帧(synthesis frames
)时以 75%
,那么就实现了慢放,反过来则是快放。
一言蔽之,对每个帧进行一系列处理比如拉伸或者压缩,最后在将这些帧重新叠加成合成信号实现倍速:
2、暴力的OLA
OLA(Overlap-and-Add, OLA)
重叠相加算法是音频变速算法中最简单的时域算法,它是后续时域算法(SOLA, SOLA-FS, TD-PSOLA, WSOLA)
的基础。
首先,音频信号分帧处理后,暴力的将处理后的信号首位拼接起来,思路非常简单,但劣势显而易见,它会造成拼接后信号的不连续,相邻帧重叠区域产生基频失真:
为了减轻这种波形不连续的影响,我们对信号进行了分帧加窗处理,OLA中通常使用汉宁窗对帧进行加窗叠加(如下图 b
):
加窗的处理保证了信号两端被抑制,保证后续的傅里叶变换,减轻频谱泄漏;这之后,通过固定间隔 Ha
取到下一个帧(如上图 c
),加窗后与前一帧叠加(如上图 d
),以缓解波形不连续(基音断裂)问题。
即便如此,在帧裁剪的过程中,仍然无法保证每一个帧都能覆盖完整周期并保证其相位对齐,这种失真也叫相位跳跃失真(phase jump artifacts
),对于音频的听感仍然不佳:
如图,两个周期信号帧通过
OLA
合成后变得 “不周期” 了。
3.波形相似叠加(WSOLA)
如何解决这样的问题,WSOLA
(Waveform similarity Overlap-Add, 波形相似叠加) 算法提出这样一种思路,通过寻找当前帧下一个最相似的信号帧,并对两帧进行叠加,这样合成后的语音便会非常自然:
上图很清晰表述了该算法的核心**:
1.在原音频中截取一个帧,加窗;
2.在一个范围内(蓝色虚线框)选取第二个帧,这个帧的相位参数应该和第一个帧相位对齐;
3.在另一个范围内(蓝色实线框)中查找第三个帧,这个帧和第二个帧应该最相似;
4.最后把它们叠加在一块。
问题很自然转换成为了 “ 如何找到最相似的帧 ”,在Android
中,对于音频 变速不变调 处理的问题,SoundTouch
使用 寻找相关峰 的算法来实现,而Sonic
则使用的是另外一种 AMDF
的基音提取算法。
对于 寻找相关峰 ,顾名思义,当第一帧数据到达时,会将数据依次传入Buffer
,并在固定长度之后的位置开始,寻找与第一帧信号相关性最大的位置,并对两帧信号进行合成;
对于 Sonic
中使用的是 AMDF
(平均幅度差函数法)方法,该方法极其简单,在一定范围内,分别计算每个帧与起始帧的 AMDF
值,幅度差最小的帧与第一帧的距离便是基音周期,寻找到基因周期后,根据基音周期进行变速变调。
阶段性小结
文章最初有提到,使用 ExoPlayer
播放音乐时, Sonic
的倍速效果失真明显,和 ijkPlayer
对应的 SoundTouch
倍速效果有 明显差距。
这似乎有违常理,既然 Sonic
是基于定位基音周期的算法实现,那么对于人声这种周期性强的音频信号而言,倍速效果应该更好才对。
经过对比与思考,我们做出以下推测,诚然,对于纯粹的人声,Sonic
的倍速效果较佳,但对于绝大多数音乐,听众对于声音节奏的听感更多是由背景乐所提供的,而背景乐通常是由多种乐器组合演奏,Sonic
对这种包含较多谐波冲击和瞬态分量的音频信号处理起来则更棘手。
因此,在具体的音频倍速实现中,不妨对具体的业务场景进行不同的决断,对于常规音乐——尤其是背景乐、打击感比较强的音乐,我们可以选择SoundTouch
, 而对于人声更纯粹的音频类型(比如相声、评书、歌手清唱)而言,Sonic
也是不错的选择。
参考资料
本文部分文案节选自下述资料,有兴趣的读者可以进行针对性深入了解。