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