系统 Visualizer
提供了方便的 api 来获取播放音频的波形或 FFT 数据,一般使用方式是:
- 用 audio session ID 创建
Visualizer
对象,传 0 可获取混音后的可视化数据,传特定播放器或AudioTrack
所使用的 audio session 的 ID,可获取它们所播放音频的可视化数据 - 调
setCaptureSize
方法设置每次获取的数据大小,调setDataCaptureListener
方法设置数据回调并指定获取数据频率(即回调频率)和数据类型(波形或 FFT) - 调
setEnabled
方法开始获取数据,不再需要时调release
方法释放资源
更详细的 api 信息可查看官方文档。
系统 Visualizer
输出的数据大小正比于音量,当音量为 0 时,输出也为 0,可视化效果会随音量变化。
使用系统 Visualizer
存在兼容性问题,在有些机型上会导致系统音效失效,如要在所有机型上都能无副作用地展示动效,需要实现自定义 Visualizer
。
自定义 Visualizer
作为跟系统 Visualizer
功能一致的数据源,自定义 Visualizer 需具备两个功能:
- 获取 pcm 数据,计算 FFT
- 以指定频率和大小发送 FFT 数据
实现第一个功能首先要获取播放音频的 pcm 数据,这要求使用的播放器能够提供 pcm 数据,我们的播放器是自己实现的,能够满足这个要求。我们对播放器进行了扩展,增加了收集解码后的 pcm 数据计算 FFT 的功能。
由于不同音频采样率不同,而计算 FFT 时采用固定的窗口大小,导致 FFT 计算结果回调频率随播放音频改变,同时指定的数据大小可能跟计算结果的大小不同,因此要实现第二个功能,需要对计算结果做固定频率和采样等处理。
另外,我们的播放器在播放进程中运行,而实际使用 FFT 数据的动效页面运行于主进程中,所以还需要跨进程传输数据。
综上,自定义 Visualizer 的整体流程是:在播放进程 native 层中计算 FFT,通过 JNI 调用,把计算结果回调给Java 层,然后通过 AIDL 把 FFT 数据传递给主进程进行后续的数据处理和发送操作。如下图所示:
固定频率需要将可变的 FFT 计算结果回调频率转换为外部设置的 Visualizer 回调频率,如下图所示:
根据所需数据发送时间间隔和 FFT 回调时间间隔差值的不同,我们采用两种不同的方式。
当时间间隔差值大于回调时间间隔时,每 t1/tt1/t 次回调发送一次数据,其中 t1 为所需数据发送时间间隔,t 为 FFT 回调时间间隔,如下图所示:
采样就是当外部设置的数据大小小于 FFT 计算结果的数据大小时,对原始 FFT 数据以合适的间隔抽取数据,以满足设置的要求。
为了让自定义 Visualizer 返回数据的取值范围跟系统 Visualizer 一致,从而实现数据源无缝切换,我们需要对 FFT 数据进行缩放。这里就需要用到前面提到的模与振幅的计算了,解码所得 pcm 数据的取值范围为 [-1, 1],所以原始信号振幅取值范围为 [0, 1],即 2M/N2M/N2M/N 的取值范围为 [0, 1](绘制时不会用到直流分量,这里不考虑);而系统 Visualizer 返回的 FFT 数据是一个 byte 数组,实部和虚部的取值范围为 [-128, 128],模的取值范围为 [0,128×2][0, 128 \times \sqrt2][0,128×2],那么 2M/N×128×22M/N \times 128 \times \sqrt22M/N×128×2 的取值范围跟系统 Visualizer 输出 FFT 的模的取值范围一致。由于绘制不会用到相位信息,我们可以将用上述方式缩放后的值作为输出 FFT 数据的实部,并把虚部设为 0。
由于数据发送的频率较高,为了避免频繁创建对象导致内存抖动,我们采用对象池来保存数据数组对象,每次从对象池中获取所需大小的数组对象,填充采样数据后加入到队列中等待发送,数据消费完后将数组对象返回到对象池中。
数据处理
不同动效的具体数据处理方式不同,忽略细节上的差异,云音乐现有的动效中,除了宇宙尘埃和孤独星球,其他的处理流程基本一致,如下图所示:
首先根据动效选择的频率范围计算所需的频率数据在 FFT 数组中的索引位置:
然后根据动效所需数据点数,对频率范围内的 FFT 数据进行采样或用一个 FFT 数据表示多个数据点。
然后计算分贝:
其中 M 为 FFT 数据的模。
然后将分贝转化为高度:
其中 MAX_DB 是预设的分贝最大值,maxHeight 是当前动效要求的最大高度。
最后对计算出的高度做数据上的平滑处理。
平滑
对最终用来绘制的数据做平滑处理,可以得到更柔和的曲线,达到更好的视觉效果,如下图所示:
数据平滑算法有很多,我们综合考虑效果和计算复杂度选择了 Savitzky–Golay 滤波法,其计算方式如下,对应的窗口大小分别为5、7 和 9,可以按需选择不同的窗口大小。
经过平滑处理后数据的变化如下图所示:
BufferQueue
有些动效的数据处理计算比较复杂,为提升并行性,减少主线程耗时,我们借鉴系统图形框架中 BufferQueue 的思想,实现了一个简单的承载动效绘制数据,连接数据处理和绘制的 BufferQueue,其工作过程如下图所示:
在使用 BufferQueue
的动效绘制类初始化时,根据需要创建一个合适大小的 BufferQueue
,并启动用于执行数据处理的 Looper
线程。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
结语
网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了上述安卓开发高级工程师面试题以及答案。希望帮助大家顺利进阶为高级工程师。
目前我就职于某大厂安卓高级工程师职位,在当下大环境下也想为安卓工程师出一份力,通过我的技术经验整理了面试经常问的题,答案部分是一篇文章或者几篇文章,都是我认真看过并且觉得不错才整理出来。
大家知道高级工程师不会像刚入门那样被问的问题一句话两句话就能表述清楚,所以我通过过滤好文章来帮助大家理解。
现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
籍】、【Android知识点PDF】。**
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!