Android使用音频信息绘制动态波纹

时间:2021-07-26 20:27:35

在一些音乐类应用中, 经常会展示随着节奏上下起伏的波纹信息, 这些波纹形象地传达了声音信息, 可以提升用户体验, 那么是如何实现的呢? 可以使用visualizer类获取当前播放的声音信息, 并绘制在画布上, 使用波纹展示即可. 我来讲解一下使用方法.

Android使用音频信息绘制动态波纹

主要

(1) visualizer类提取波纹信息的方式.
(2) 应用动态权限管理的方法.
(3) 分离自定义视图的展示和逻辑.

1. 基础准备

android 6.0引入动态权限管理, 在这个项目中, 会使用系统的音频信息, 因此把权限管理引入这个项目, 参考. gradle配置引入了lambda表达式, 参考.

页面布局, 使用自定义的波纹视图控件.

?
1
2
3
4
5
<!--波纹视图-->
<me.chunyu.spike.wcl_visualizer_demo.visualizers.waveformview
android:id="@+id/main_wv_waveform"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

效果

Android使用音频信息绘制动态波纹

 

2. 首页逻辑

添加动态权限管理, 在启动页面时, 获取应用所需的音频权限.
rendererfactory工厂类创建波纹的绘制类simplewaveformrender.
startvisualiser方法获取当前播放音乐的音频信息.
注意页面关闭, 在onpause时, 释放visualiser类.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class mainactivity extends appcompatactivity {
private static final int capture_size = 256; // 获取这些数据, 用于显示
private static final int request_code = 0;
// 权限
private static final string[] permissions = new string[]{
manifest.permission.record_audio,
manifest.permission.modify_audio_settings
};
@bind(r.id.main_wv_waveform) waveformview mwvwaveform; // 波纹视图
private visualizer mvisualizer; // 音频可视化类
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
butterknife.bind(this);
rendererfactory rendererfactory = new rendererfactory();
mwvwaveform.setrenderer(rendererfactory.createsimplewaveformrender(contextcompat.getcolor(this, r.color.colorprimary), color.white));
}
@override protected void onresume() {
super.onresume();
permissionschecker checker = new permissionschecker(this);
 
if (checker.lakespermissions(permissions)) {
permissionsactivity.startactivityforresult(this, request_code, permissions);
} else {
startvisualiser();
}
}
@override protected void onactivityresult(int requestcode, int resultcode, intent data) {
super.onactivityresult(requestcode, resultcode, data);
if (requestcode == request_code && resultcode == permissionsactivity.permissions_denied) {
finish();
}
}
// 设置音频线
private void startvisualiser() {
mvisualizer = new visualizer(0); // 初始化
mvisualizer.setdatacapturelistener(new visualizer.ondatacapturelistener() {
@override
public void onwaveformdatacapture(visualizer visualizer, byte[] waveform, int samplingrate) {
if (mwvwaveform != null) {
mwvwaveform.setwaveform(waveform);
}
}
@override
public void onfftdatacapture(visualizer visualizer, byte[] fft, int samplingrate) {
}
}, visualizer.getmaxcapturerate(), true, false);
mvisualizer.setcapturesize(capture_size);
mvisualizer.setenabled(true);
}
// 释放
@override protected void onpause() {
if (mvisualizer != null) {
mvisualizer.setenabled(false);
mvisualizer.release();
}
super.onpause();
}
}

visualizer类

new visualizer(0), 初始化; setcapturesize, 获取波纹数量; setenabled, 启动监听;
setdatacapturelistener, 第一个参数是回调, 使用waveformdata或fftdata; 第二个是更新率; 第三个是判断使用waveformdata; 第四个是判断使用fftdata, 第三\四个均与回调的返回值有关.

3. 波纹视图

页面框架, 分离显示和逻辑, 使用接口渲染, 输入画布canvas和波纹waveform.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 音频波纹视图
* <p>
* created by wangchenlong on 16/2/11.
*/
public class waveformview extends view {
private waveformrenderer mrenderer; // 绘制类
private byte[] mwaveform; // 波纹形状
public waveformview(context context) {
super(context);
}
public waveformview(context context, attributeset attrs) {
super(context, attrs);
}
public waveformview(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
}
@targetapi(21)
public waveformview(context context, attributeset attrs, int defstyleattr, int defstyleres) {
super(context, attrs, defstyleattr, defstyleres);
}
public void setrenderer(waveformrenderer renderer) {
mrenderer = renderer;
}
public void setwaveform(byte[] waveform) {
mwaveform = arrays.copyof(waveform, waveform.length); // 数组复制
invalidate(); // 设置波纹之后, 需要重绘
}
@override protected void ondraw(canvas canvas) {
super.ondraw(canvas);
if (mrenderer != null) {
mrenderer.render(canvas, mwaveform);
}
}
}

数组复制arrays.copyof(), 在设置波纹后重绘页面invalidate().

4. 波纹逻辑

核心部分renderwaveform, 渲染波纹.
把页面分为网格样式, 根据波纹值, 绘制曲线; 没有波纹, 绘制居中水平直线.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 波纹渲染逻辑
* <p>
* created by wangchenlong on 16/2/12.
*/
public class simplewaveformrenderer implements waveformrenderer {
private static final int y_factor = 0xff; // 2的8次方 = 256
private static final float half_factor = 0.5f;
@colorint private final int mbackgroundcolor;
private final paint mforegroundpaint;
private final path mwaveformpath;
private simplewaveformrenderer(@colorint int backgroundcolor, paint foregroundpaint, path waveformpath) {
mbackgroundcolor = backgroundcolor;
mforegroundpaint = foregroundpaint;
mwaveformpath = waveformpath;
}
public static simplewaveformrenderer newinstance(@colorint int backgroundcolor, @colorint int foregroundcolour) {
paint paint = new paint();
paint.setcolor(foregroundcolour);
paint.setantialias(true); // 抗锯齿
paint.setstrokewidth(8.0f); // 设置宽度
paint.setstyle(paint.style.stroke); // 填充
path waveformpath = new path();
return new simplewaveformrenderer(backgroundcolor, paint, waveformpath);
}
@override public void render(canvas canvas, byte[] waveform) {
canvas.drawcolor(mbackgroundcolor);
float width = canvas.getwidth();
float height = canvas.getheight();
mwaveformpath.reset();
// 没有数据
if (waveform != null) {
// 绘制波形
renderwaveform(waveform, width, height);
} else {
// 绘制直线
renderblank(width, height);
}
canvas.drawpath(mwaveformpath, mforegroundpaint);
}
private void renderwaveform(byte[] waveform, float width, float height) {
float xincrement = width / (float) (waveform.length); // 水平块数
float yincrement = height / y_factor; // 竖直块数
int halfheight = (int) (height * half_factor); // 居中位置
mwaveformpath.moveto(0, halfheight);
for (int i = 1; i < waveform.length; ++i) {
float yposition = waveform[i] > 0 ?
height - (yincrement * waveform[i]) : -(yincrement * waveform[i]);
mwaveformpath.lineto(xincrement * i, yposition);
}
mwaveformpath.lineto(width, halfheight); // 最后的点, 水平居中
}
// 居中画一条直线
private void renderblank(float width, float height) {
int y = (int) (height * half_factor);
mwaveformpath.moveto(0, y);
mwaveformpath.lineto(width, y);
}
}

绘制移动moveto, 绘制直线lineto.

动画效果

Android使用音频信息绘制动态波纹

通过绘制波纹, 可以类似地绘制一些连续数据, 更加直观地展示, 提升用户体验.