Android高仿iOS Messages声音播放波形效果

Android高仿iOS Messages声音播放波形效果

文接《Android低仿iOS Messages录音波形效果》。

上一次开发中,因为无法完美实现波形的收敛效果,因此只能算是一个低仿的版本。

声音波形效果对比之下,比较容易实现。

一、目标

实现声音播放的波形效果,为神马笔记增加录音及播放功能做准备。

二、功能分析

截图 描述
停止时的波形显示为全白色。
播放过程中,波形有暗变亮,以呈现进度。

三、实现效果

基本上实现了播放的波形效果。

四、实现过程

1. 创建波形

绘制波形的前提条件是有波形数据。

波形数据的来源有2种。

  1. 从音频文件读取数据,然后转换为波形数据
  2. 录音时进行采样保存

通过MediaRecorder实现录音时,通过getMaxAmplitude()获取最近最大的振幅,通过一段时间的连续调用,最终可以组成一个波形数据。

有了波形数据后,还需要根据控件大小转换为目标数据。

2. Waveform

从波形数据创建Waveform,用来显示完整波形。

sample()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
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
*
*/
private static class Waveform {

ArrayList<Wave> list;
ArrayList<Wave> recycler;

TapePlayView parent;

Waveform(TapePlayView parent) {
this.parent = parent;

this.list = new ArrayList<>(100);
this.recycler = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
recycler.add(new Wave());
}
}

void clear() {
this.recycler.addAll(list);
list.clear();
}

void sample() {
this.recycler.addAll(list);
this.list.clear();

int count = parent.getWidth() / parent.getWaveWidth();
if (count == 0) {
return;
}

TapeView.Sampler sampler = parent.sampler;
if (sampler == null || sampler.size() == 0) {
return;
}

int size = sampler.size();

if (count == size) { // equals

for (int i = 0; i < size; i++) {
int value = sampler.get(i);
this.addSquare(value);
}

} else if (count > size) { // more than

{
int length;
if (count >= 2 * size) { // more than twice
length = (count - 2 * size) / 2;
} else {
length = (count - size) / 2;
}

while (length > 0) {
this.add(0);
--length;
}
}

if (count >= 2 * size) {
this.addSquare(sampler.get(0) / 2);
this.addSquare(sampler.get(0));

for (int i = 1; i < size; i++) {
double a = sampler.get(i - 1);
a *= a;

double b = sampler.get(i);
b *= b;

a = (a + b) / 2;

this.add(a);
this.add(b);
}

} else {
for (int i = 0; i < size; i++) {
int value = sampler.get(i);
this.addSquare(value);
}
}

{
while (this.size() < count) {
this.add(0);
}
}

} else { // less than

long[] values = new long[count];
Arrays.fill(values, 0);

int[] numbers = new int[count];
Arrays.fill(numbers, 0);

for (int i = 0; i < size; i++) {

double value = sampler.get(i);
value *= value;

int index = i * count / size;
values[index] += value;
numbers[index] += 1;
}

for (int i = 0; i < count; i++) {
if (numbers[i] == 0) {
this.add(0);
} else {
this.add(values[i] / numbers[i]);
}
}
}
}

int size() {
return list.size();
}

void draw(Canvas canvas) {
for (Wave wave : list) {
wave.draw(canvas);
}
}

void addSquare(int value) {
this.add(value * value);
}

void add(double amplitudeSquare) {
int position = list.size();
Wave wave = fetch(position, amplitudeSquare);
list.add(wave);
}

Wave fetch(int position, double amplitudeSquare) {

Wave wave;

int index = recycler.size() - 1;
if (index < 0) {
wave = new Wave();
} else {
wave = recycler.remove(index);
}

wave.init(parent, position, amplitudeSquare);
return wave;
}
}

3. Wave

Wave用来绘制单个的波形柱状图。

根据播放时间的进度显示不同的效果。

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
61
62
63
64
private static class Wave {

int wave;
int position;

TapePlayView parent;

Wave() {

}

void init(TapePlayView parent, int position, double amplitudeSquare) {
this.parent = parent;

this.position = position;
this.wave = parent.transform(amplitudeSquare);
}

void draw(Canvas canvas) {
int width = parent.getWaveWidth();
int height = parent.getWaveHeight(wave);

int x = position * width;
int y = (parent.getHeight() - height) / 2;

Drawable d;

if (parent.isRunning()) {
int progress = parent.getProgress();

if (x >= progress) { // dark

d = parent.darkDrawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);

} else if ((progress - x) >= parent.sampleSolid) { // bright

d = parent.drawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);

} else {

int diff = progress - x;

d = parent.darkDrawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);

d = parent.drawable;
d.setBounds(x, y, x + diff, y + height);
d.draw(canvas);
}

} else {

d = parent.drawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);

}
}
}

4. 播放声音

使用MediaPlayer来播放声音,通过getDuration()getCurrentPosition()获取总时长和当前进度,从而绘制出播放时的波形。

五、开发过程回顾

从转换波形数据开始,创建波形效果,然后绘制了静态的波形图。

之后结合播放进度,绘制不同状态的波形效果。

难点在于获取波形数据。

六、接下来

组合录音和播放,完整录音编辑器功能。

七、Finally

我应灭度一切众生。 灭度一切众生已。 而无有一众生实灭度者。