原文:An alternative Android Visualizer
作者:Dániel Zolnai
译者:却把清梅嗅

听音乐时,有时你会看到那些视觉上令人愉悦的跃动条,它们音量越大跳得越高。通常,左边的条形对应的频率较低(低音),而右边的条形对应较高的频率(高音):

这些跃动条通常被称为 视觉均衡器可视化器,若想在 Android 应用中展示类似的可视化效果,你可以使用 Android 原生的 Visualizer 类,它是Android框架中的一部分,且能够附加到你的 AudioTrack

它是切实有效的,但有一个重要的缺陷:它需要申请 麦克风权限 ,而从官方文档上来看,这是有确切考虑的:

To protect privacy of certain audio data (e.g voice mail) the use of the visualizer requires the permission.

为了保护某些音频数据(例如语音邮件)的隐私,使用 Visualizer 需要获取权限。

问题是,用户不会允许音乐 APP 申请使用他们的麦克风权限(这毫无疑问)。而当我翻遍了 Android 官方提供的API或者其他三方库,却找不到实现这样 可视化器 效果的替代方案。

因此我考虑自己造轮子,第一个问题是,我需要思考如何将正在播放的音乐,转换成每个跳跃条对应的高度。

可视化器的工作原理

首先,让我们从输入开始。当数字化音频时,我们通常会对信号幅度进行非常频繁的采样,这称为脉冲编码调制 (PCM)。振幅随之被量化,我们将其表示到我们自己的 数字标度 上。

举个例子,如果编码是 PCM-16,这个比例将是 16 bit,我们可在 216 次幂的数字范围内表示一个幅度,即 65536 个不同的幅度值。

如果您在多个 channel 上采样(如立体声,分别录制左右声道),这些幅度会相互跟随,因此首先是 channel 0 的幅度,然后是 channel 1 的幅度,然后是 channel 0,依此类推。 一旦我们获得了这些幅度值作为原始数据,我们就可以继续下一步。 为此,我们需要了解声音实际上是什么:

我们听到的声音是物体振动的结果。例如人的声带、吉他的金属弦和木琴身。一般情况下,若不受特定声音振动的影响,空气分子会随机移动。

选自《 Digital Sound and Music 》

当敲击音叉时,它会以非常特定的 440 次/秒 (Hz) 振动,这种振动将通过空气传播到耳膜,在那里以相同的频率共振,大脑会将其解释为音符A。

PCM 中,这可以表示为正弦波,每秒重复 440 次。这些波的高度不会改变音符,但它们代表振幅;通俗点说,就是当听到它时,你耳朵里的响度。

但是当听音乐时,通常不仅有正在听的音符A(虽然我希望这样),而且还有过多的乐器和声音,从而导致 PCM 图形对人眼没有意义。实际上它是不同频率和振幅的不同正弦波大量振动的组合。

即使是非常简单的 PCM 信号(例如方波)在解构为不同的正弦波时也非常复杂:

方波解构为近似正弦和余弦波,参考自 这里 。

幸运的是,我们有算法来进行这种解构,我们称之为 傅立叶变换 。正如上文可视化器所展示的,它实际上是从正弦波和余弦波的组合中解构而出的。余弦基本上是一个 延迟 的正弦波,但是在这个算法中拥有它们非常有用,否则我们将无法为点 0 创建一个值,因为每个正弦波都是从 0 开始的,相乘仍然会得到 0

执行 傅里叶变换 的算法之一是 快速傅里叶变换 ( FFT )。 在我们的 PCM 声音数据上运行此 FFT 算法时,我们将获得每个正弦波的幅度列表。这些波是声音的频率。在列表的开头,我们可以找到低频(低音),最后是高频(高音)。

这样,我们通过绘制一个这样的条形图,其高度由每个频率的幅度决定——我们得到了我们想要的可视化器。

技术实现

现在回到 Android。 首先,我们需要音频的 PCM 数据。 为此,我们可以将 AudioProcessor 配置给到我们的 ExoPlayer 实例,它会在转发之前接收每个音频字节。您还可以进行修改,例如更改 幅度过滤通道,但不是现在。

private val fftAudioProcessor = FFTAudioProcessor()val renderersFactory = object : DefaultRenderersFactory(this) {override fun buildAudioProcessors(): Array<AudioProcessor> {val processors = super.buildAudioProcessors()return processors + fftAudioProcessor}
}
player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, DefaultTrackSelector())

queueInput(inputBuffer: ByteBuffer) 方法中,我们将收到捆在一起作为一帧的 byte 数据。

这些 byte 可能来自多个 channel,为此我取了所有 channel 的平均值,并且仅将其转发以进行处理。

为了使用 傅立叶变换,我使用了 Noise 库。变换需要一个具有给定样本大小的float列表。样本大小应该是 2 的因子,我选择了 4096

增加这个数字可获得更精细的数据,但计算时间更长,计算也更不频繁(因为可针对每 X 字节的声音数据进行一次更新,其中 X 是样本大小)。如果数据是 PCM-16,则 2 个字节构成一个幅度。浮点值并不重要,因为它们可以缩放。如果您提交一个介于 01 之间的数字,则结果都将介于 01 之间(因为无需将正弦波幅度与更高的数字相乘)。

所得结果也将是一个float列表。我们可使用这些频率立即绘制 4096 个条形图,但这不切实际。

来看看如何改进这些结果数据。

频段

首先,我们可以将这些频率组合成更小的组。因此,假设我们将 0-20kHz 频谱划分为 20 个小节,每个小节跨越 1kHz

20 条比 4096 条更容易绘制,我们也无需那么多条。如果现在绘制这些值,可以看到,只有最左边的部分在大幅度移动。

这是因为音乐中频率的适用范围大约是 20-5000Hz,而听10kHz的声音会让人很烦躁。若将音乐中的较高频率排除在外,你会注意到,它听起来会越来越沉闷,但与较低频率相比,这些频率的幅度非常小。

如果你看过录音室均衡器,则会发现频段也分布不均,频率的下半部分通常占用 80-90% 的频段:

鉴于此,建议通过为较低频率分配更多频带来使这些频带具有可变宽度。下图是这样做的效果,它看起来会好一些:

似乎不错,但仍然存在 2 个问题:

首先,右边的频率似乎移动得有点太多了。这是因为我们的采样并不完美,它引入了称为 频谱泄漏 的伪像,其中原始频率会涂抹到相邻的频率中。为了减少这种拖尾现象,我们可以应用一个窗口函数,在那里我们突出我们感兴趣的频率并调低其他频率。这些窗口有不同类型,但我将使用 汉明窗口 (Hamming-window)。我们感兴趣的频率是中间的频段,并针对两端进行抑制:

最后,还有一个小问题,这在上面的 gif 中无法体现,但当听音乐时会立即注意到:条跃动太早了,它们在你意料不到的时候跃动起来。

意外缓冲区

这种不同步的行为是因为在 ExoPlayer AudioProcessor 中,我们在数据传递到 AudioTrack 之前接收到数据,而 AudioTrack 有自己的缓冲区,这会导致视觉效果领先于音频效果,导致延迟输出。

对此的解决方案是将 ExoPlayer 缓冲区大小计算部分的代码进行复制,因此我的 AudioProcessor 中的缓冲区大小与 AudioTrack 完全相同。

我将传入的字节放在缓冲区的末尾,只处理缓冲区开头的字节(FIFO 队列),我如愿以偿延迟了 FFT

最终效果

我创建了一个 代码仓库,这上面,我通过播放在线广播并使用我创建的可视化器进行绘图,以展示我的 FFT 处理器。它肯定不能直接用于线上产品,但如果您正在为音乐APP寻找可视化工具,它会提供一个很好的基础。

[译] Android Visualizer 可视化器的自定义实现相关推荐

  1. Android 节操视频播放器jiecaovideoplayer自定义播放音频使用:屏蔽全屏按钮,增加倒计时,当前时间/总时间

    一.屏蔽全屏按钮 找到JCVideoPlayerStandard.java文件中的代码: private void fixAudio() {if (SrcType.equalsIgnoreCase(& ...

  2. 史上最好用的Android音视频播放器-ExoPlayer的使用及自定义UI

    ExoPlayer是运行在YouTube app Android版本上的视频播放器.不仅功能强大,而且使用简单,可定制性强.ExoPlayer也是Google官方推荐的Android媒体播放器,可以在 ...

  3. Android音频可视化

    本文作者:熊鋆洋 (网易云音乐大前端团队) 前言 音频可视化,顾名思义就是将声音以视觉的方式呈现出来.如何将音频信号绘制出来?如何将声音的变化在视觉上清晰的表现出来,让视觉和听觉上的感受一致?这些在 ...

  4. 基于android音乐播放器的设计

    本科毕业论文(设计)诚信声明 本人郑重声明:所呈交的毕业论文(设计),题目<---基于android音乐播放器的设计----------->是本人在指导教师的指导下,进行研究工作所取得的成 ...

  5. 使用Vitamio打造自己的Android万能播放器(2)—— 手势控制亮度、音量、缩放

    前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础. 声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over1 ...

  6. android类中定义颜色,自定义实现简单的Android颜色选择器(附带源码)

    在写Android App过程中需要一个简单的颜色选择器,Android自带的ColorPicker和网上的一些ColorPicker都太高端了,都实现了颜色渐变功能,我要的不需要那么复杂,只想提供几 ...

  7. Android显示九宫图(自定义圆角,仿微信九宫格图)

    详细解析Android显示九宫图(自定义圆角,仿微信九宫格图) 这是一个自定义九宫格图片框架,里面有设置圆角大小,还有当图片一张的时候控件自定义的大小,图片的间隔,四张图片的时候图片自定义为两行两列等 ...

  8. [译]Android 动画的灵魂—— Interpolator

    本文讲的是[译]Android 动画的灵魂-- Interpolator, 用定制的非线性定时曲线改善你的动画 在现实世界中的运动是非线性的.(当你穿过街道时,你只要略微将你盯着手机的眼睛瞄一眼街道就 ...

  9. Android 音乐播放器的开发教程(三) 小卷毛播放器的主界面开发 ---- 小达

    Android 音乐播放器的开发教程(三) 小卷毛播放器的主界面开发 拿好素材之后,打开你们的开发工具,小达这里用的是android studio1.0, 新建一个项目,打开activity_main ...

最新文章

  1. C++标准库中sstream和strstream的区别
  2. 易观的大数据中台之路
  3. python 第3课 数据类型之list
  4. python中文转拼音
  5. 基于MATLAB的自由空间损耗模型的理论与仿真
  6. PageHelper分页时超过最大数量的页数仍然返回数据,PageHelper分页失效
  7. php压缩多个CSS/JS文件
  8. 学术人必备!懒人制作学术会议Oral/Spotlight Video指南
  9. 你知道应聘上一份机器学习的工作需要哪些条件吗?
  10. python基础代码大全-Python网络爬虫实战项目代码大全(长期更新,欢迎补充)
  11. linux--kubectl命令和docker命令
  12. 空间分析方法在计算机上的应用,常见的空间分析方法(很经典的总结)
  13. 实践四 -- 文本词频分析
  14. 【Java合并图片】使用Java实现两张图片合并成一张图片的功能(水平合并、垂直合并、透明背景颜色)
  15. gem install factory_girl
  16. 【LeetCode】460 and 1132(LFU缓存机制)
  17. swing文本框添加背景图片
  18. 生产计划为何难实施?
  19. Java中IO流-18-flush和close方法的区别
  20. python 中文乱码问题

热门文章

  1. 浅谈光波近场传播表述的方式 ——角谱法
  2. 【Go】strings库字符串处理详说
  3. 同济大学计算机硕士毕业单位,同济大学硕士毕业开题报告.doc
  4. 电商客服提升客户咨询转化率的秘诀
  5. 浙江大学远程计算机基础知识题,浙江大学远程教育计算机3Word知识题答案.doc
  6. 前端HTML中盒子的显隐性
  7. 关于 TeamViewer 客户端被黑客攻击的通报,但真相是?
  8. 利用基因沉默和过表达技术研究棉花的基因功能
  9. 河北省电子工程高级职称公示_2018年河北省电子工程系列正高级、高级职称评审答辩工作的通知...
  10. flutter 生成二维码,中心可加图片