概述

音视频倍速 是内容类APP非常重要的功能,其内部包含了 视频流音频流 的倍速,其中视频倍速原理相对简单,即在解码视频帧时提升帧率即可。

音频倍速 相对复杂,众所周知,声音的本质其实是 物体振动时产生的声波,因此音频的倍速是 将语音信号在时域上拉长或缩短,考虑到用户的体验,在保证声音变速的同时,语音的采样率、基频以及共振峰都不能发生变化,以此达到 变速不变调 的目的。

对于 Android 平台的应用而言,音频倍速通常有3种实现方式:

实现方案应用简介
Android AudioTrackAndroid 原生音视频架构原生支持,但存在兼容性问题,倍速效果不佳
Sonic LibraryGoogle ExoPlayer基于 WSOLA 算法,使用寻找相关峰法寻找相似帧
SoundTouch LibraryBilibili ijkPlayer基于 WSOLA 算法,使用定位基因周期法寻找相似帧

对于原生的 AudioTrack 而言,其本身提供了处理PCM音频流的功能,但由于其默认被系统的 MediaPlayer 绑定,而后者本身在不同平台上的兼容性就不佳,因此很少被使用。

Sonic 库则被 Google 大名鼎鼎的开源播放器 ExoPlayer 所使用,其内部基于 时域压扩算法 (Time-scale modificatio,下简称TSM算法), 通过定位 基音周期 的方式,对输入的语音信号进行不断的分帧与合帧的处理,最终合成新的信号以达到倍速的效果。

SoundTouch 则被 Bilibili 开源的 ijkPlayer 所内置,内部仍然基于TSM算法,和Sonic不同的是使用了 寻找相关峰 进行语音信号的合成。

不可否认,信号的合成必然会造成原始音频的失真,区别在于Sonic是基于 基音周期 的,因此变速后的语音信号对人声音影响较小;而 SoundTouch 倍速效果更适用于综合性的场景。

但在实际应用中,问题却不断涌现:

    1. 从上述结论来看,既然对人声音影响较小,那么针对歌曲的倍速播放,Sonic效果应该更好,但实际中,Sonic在高倍速下听感失真明显,和SoundTouch的效果有显著差距,导致该现象的原因是什么?
    1. 如何解决该问题,两种倍速方案各自适用的场景又是什么?

要搞明白这些疑惑,就需要从原理和具体的算法实现进行分析。

音频倍速原理

1、TSM基本原理

音频倍速的实现思路分为针对 时域 信号或者 频域 信号分析,但由于频域的复杂度过高,因此实践中通常从时域信号着手。

时域压扩(TSM) 也正是基于时域信号的处理中的典型算法,其提供了 变速不变调 的音频处理实现。

音频信号的处理过程中,不可避免的要进行 分帧analysis fames)操作,帧的长度大多选取是 20ms50ms 之间,并进行加窗操作,而由于加窗操作本身会对一帧信号的两端进行抑制,因此分帧不能按长度分段截取,而是相互重叠一部分(overlap),之后再进行 合帧 (synthesis frames)。如果分帧以 50%overlap,而合帧(synthesis frames)时以 75%,那么就实现了慢放,反过来则是快放。

一言蔽之,对每个帧进行一系列处理比如拉伸或者压缩,最后在将这些帧重新叠加成合成信号实现倍速:

p1.png

2、暴力的OLA

OLA(Overlap-and-Add, OLA) 重叠相加算法是音频变速算法中最简单的时域算法,它是后续时域算法(SOLA, SOLA-FS, TD-PSOLA, WSOLA)的基础。

首先,音频信号分帧处理后,暴力的将处理后的信号首位拼接起来,思路非常简单,但劣势显而易见,它会造成拼接后信号的不连续,相邻帧重叠区域产生基频失真:

p2.png

为了减轻这种波形不连续的影响,我们对信号进行了分帧加窗处理,OLA中通常使用汉宁窗对帧进行加窗叠加(如下图 b ):

p3.png

加窗的处理保证了信号两端被抑制,保证后续的傅里叶变换,减轻频谱泄漏;这之后,通过固定间隔 Ha 取到下一个帧(如上图 c ),加窗后与前一帧叠加(如上图 d ),以缓解波形不连续(基音断裂)问题。

即便如此,在帧裁剪的过程中,仍然无法保证每一个帧都能覆盖完整周期并保证其相位对齐,这种失真也叫相位跳跃失真(phase jump artifacts),对于音频的听感仍然不佳:

p4.jpg

如图,两个周期信号帧通过 OLA 合成后变得 “不周期” 了。

3.波形相似叠加(WSOLA)

如何解决这样的问题,WSOLA (Waveform similarity Overlap-Add, 波形相似叠加) 算法提出这样一种思路,通过寻找当前帧下一个最相似的信号帧,并对两帧进行叠加,这样合成后的语音便会非常自然:

p5.png

上图很清晰表述了该算法的核心思想:

1.在原音频中截取一个帧,加窗;
2.在一个范围内(蓝色虚线框)选取第二个帧,这个帧的相位参数应该和第一个帧相位对齐;
3.在另一个范围内(蓝色实线框)中查找第三个帧,这个帧和第二个帧应该最相似;
4.最后把它们叠加在一块。

问题很自然转换成为了 “ 如何找到最相似的帧 ”,在Android中,对于音频 变速不变调 处理的问题,SoundTouch使用 寻找相关峰 的算法来实现,而Sonic则使用的是另外一种 AMDF 的基音提取算法。

对于 寻找相关峰 ,顾名思义,当第一帧数据到达时,会将数据依次传入Buffer,并在固定长度之后的位置开始,寻找与第一帧信号相关性最大的位置,并对两帧信号进行合成;

对于 Sonic 中使用的是 AMDF (平均幅度差函数法)方法,该方法极其简单,在一定范围内,分别计算每个帧与起始帧的 AMDF 值,幅度差最小的帧与第一帧的距离便是基音周期,寻找到基因周期后,根据基音周期进行变速变调。

阶段性小结

文章最初有提到,使用 ExoPlayer 播放音乐时, Sonic的倍速效果失真明显,和 ijkPlayer 对应的 SoundTouch 倍速效果有 明显差距

这似乎有违常理,既然 Sonic 是基于定位基音周期的算法实现,那么对于人声这种周期性强的音频信号而言,倍速效果应该更好才对。

经过对比与思考,我们做出以下推测,诚然,对于纯粹的人声,Sonic 的倍速效果较佳,但对于绝大多数音乐,听众对于声音节奏的听感更多是由背景乐所提供的,而背景乐通常是由多种乐器组合演奏,Sonic对这种包含较多谐波冲击和瞬态分量的音频信号处理起来则更棘手。

因此,在具体的音频倍速实现中,不妨对具体的业务场景进行不同的决断,对于常规音乐——尤其是背景乐、打击感比较强的音乐,我们可以选择SoundTouch, 而对于人声更纯粹的音频类型(比如相声、评书、歌手清唱)而言,Sonic也是不错的选择。

参考资料

本文部分文案节选自下述资料,有兴趣的读者可以进行针对性深入了解。

关于我

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐