qingmei2 / blogs

📝 The Android programing blogs(简体中文).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 倍速效果更适用于综合性的场景。

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

    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