当前位置: 首页 > 科技观察

AndroidVisualizer的自定义实现

时间:2023-03-20 13:58:28 科技观察

听音乐时,您有时会看到那些视觉上令人愉悦的跳跃条,音量越大跳得越高。通常,左边的条对应较低的频率(低音),而右边的条对应较高的频率(高音):这些跳跃的条通常被称为视觉均衡器或可视化器,如果你想显示类似的可视化效果,您可以使用Android的原生Visualizer类,它是Android框架的一部分,可以附加到您的AudioTrack。有效,但有一个重要缺陷:需要申请麦克风权限,从官方文档来看,肯定是这样考虑的:Toprotectprivacyofcertainaudiodata(e.g.voicemail)theuseofthevisualizerrequiresthepermission.为了保护某些音频数据(例如语音邮件)的隐私,使用Visualizer需要权限。问题是用户不允许音乐应用程序请求使用他们的麦克风的权限(毫无疑问)。而我翻遍了Android官方提供的API或者其他三方库,也没有找到可以实现这种可视化效果的方案。所以我想自己造轮子。第一个问题是,我需要思考如何将正在播放的音乐转换成每个跳跃条对应的高度。可视化工具的工作原理首先,让我们从输入开始。在数字化音频时,我们通常会非常频繁地对信号幅度进行采样,这称为脉冲编码调制(PCM)。然后振幅被量化,我们将其表示在我们自己的数字标尺上。例如,如果编码是PCM-16,那么比率就是16位,我们可以表示一个2的16次方范围内的幅度,即65536个不同的幅度值。如果您在多个通道上采样(如在立体声中,分别记录左右通道),振幅会相互跟随,因此首先是通道0的振幅,然后是通道1的振幅,然后是通道0,依此类推。一旦我们有了这些量级值作为原始数据,我们就可以继续下一步了。为此,我们需要了解声音到底是什么:我们听到的声音是物体振动的结果。示例包括人的声带、吉他的金属弦和木琴的琴身。一般来说,如果不受特定声音振动的影响,空气分子会随机移动。来自《 Digital Sound and Music 》当音叉被敲击时,它会以非常特定的每秒440次(Hz)的频率振动,这种振动会通过空气传播到耳膜,在那里它以相同的频率产生共振,大脑将其解释为音符A.在PCM中,这可以表示为正弦波,每秒重复440次。这些波浪的高度不会改变音符,但它们代表振幅;通俗地说,当你听到它时,它在你耳朵里的声音有多大。但是在听音乐的时候,经常听到的不仅仅是音符A(虽然我希望如此),还有太多的乐器和声音,导致PCM图形对人眼来说没有意义。实际上它是大量不同频率和振幅的不同正弦波振动的组合。即使是非常简单的PCM信号(例如方波)在解构为不同的正弦波时也非常复杂:方波解构为近似正弦波和余弦波。幸运的是,我们有算法来进行这种解构,我们称之为傅里叶变换。正如上面的可视化工具所展示的,它实际上是从正弦波和余弦波的组合中解构出来的。余弦基本上是一个延迟的正弦波,但在这个算法中加入它们非常有用,否则我们将无法为点0创建一个值,因为每个正弦波都从0开始,乘法仍然会得到0。执行傅立叶变换的算法之一是快速傅立叶变换(FFT)。在我们的PCM声音数据上运行此FFT算法时,我们将获得每个正弦波的振幅列表。这些波是声音的频率。在列表的开头,我们可以找到低频(低音),最后是高频(高音)。这样,我们通过绘制这样的条形图来获得我们想要的可视化工具,条形图的高度由每个频率的大小决定。技术实现现在回到了Android。首先,我们需要音频的PCM数据。为此,我们可以为我们的ExoPlayer实例配置一个AudioProcessor,它将在转发之前接收每个音频字节。您还可以进行修改,例如更改振幅或过滤通道,但现在还不能。privatevalfftAudioProcessor=FFTAudioProcessor()valrenderersFactory=object:DefaultRenderersFactory(this){overridefunbuildAudioProcessors():Array{valprocessors=super.buildAudioProcessors()returnprocessors+fftAudioProcessor}}player=ExoPlayerFactory.newSimpleInstance(this,renderersFactoryinSelector,())DefaultT在queueInput(inputBuffer:ByteBuffer)方法中,我们将接收捆绑在一起作为帧的字节数据。这些字节可能来自多个通道,为此我取所有通道的平均值,只转发它进行处理。为了使用傅立叶变换,我使用了噪声库。转换采用具有给定样本大小的浮点数列表。样本大小应该是2的一个因子,我选择了4096。增加这个数字会产生更精细的数据,但需要更长的时间来计算并且计算频率更低(因为可以对声音数据的每X字节进行更新,其中X是样本大小).如果数据是PCM-16,则2个字节组成一个幅度。浮点值无关紧要,因为它们可以缩放。如果您提交一个介于0和1之间的数字,则结果将全部介于0和1之间(因为不需要将正弦波幅度乘以更大的数字)。生成的结果也将是一个浮点数列表。我们可以用这些频率一次绘制4096个柱,但这不切实际。让我们看看如何改进这些结果数字。频段首先,我们可以将这些频率组合成更小的组。所以假设我们将0-20kHz频谱分成20个条,每个跨越1kHz。20比4096好画,我们不需要那么多。如果您现在绘制这些值,您可以看到只有最左边的部分发生了显着变化。这是因为音乐中适用的频率范围大约是20-5000Hz,听10kHz的声音会很烦人。如果您从音乐中去除较高的频率,您会注意到它听起来越来越沉闷,但与较低的频率相比,这些频率的振幅非常小。如果您查看工作室均衡器,您会发现频段分布也不均匀,频率的下半部分通常占据频段的80-90%:因此,建议通过以下方式使这些频段更加活跃将更多的频段分配给较低的频率。可变宽度。下面是这样的效果,会更好看:好像不错,但是还有2个问题:第一,右边的频率好像偏了一点。这是因为我们的采样并不完美,这引入了一种称为频谱泄漏的伪像,即原始频率被涂抹到相邻频率中。为了减少这种拖尾,我们可以应用窗口函数,在其中突出显示我们感兴趣的频率并调低其他频率。这些窗口有不同类型,但我将使用Hamming窗口。我们感兴趣的频率是中间频段,两端都有抑制:最后,还有一个小问题,上面的gif中没有反映出来,但在听音乐时会立即注意到:小节跳得太早,它们会在您意想不到的时候出现。意外缓冲区这种不同步行为是因为在ExoPlayerAudioProcessor中,我们在将数据传递给AudioTrack之前接收数据,AudioTrack有自己的缓冲区,这会导致视觉效果领先于音频效果,从而导致延迟输出。解决方案是复制ExoPlayer缓冲区大小计算部分的代码,这样我的AudioProcessor中的缓冲区大小就和AudioTrack中的完全一样了。我将传入的字节放在缓冲区的末尾,只处理缓冲区开头的字节(FIFO队列),并按我的意愿延迟了FFT。最终结果我创建了一个代码存储库,通过播放在线广播和使用我创建的可视化工具绘图来展示我的FFT处理器。它当然不能直接用于在线产品,但如果您正在为您的音乐应用程序寻找可视化工具,它提供了一个很好的基础。