模拟信号示波器
来源:互联网 发布:全局近似算法 编辑:程序博客网 时间:2024/05/06 19:21
转自http://www.android-study.com/duomeitijishu/498.html
一个Android版的手机模拟信号示波器。最近物联网炒得很火,作为手机软件开发者,如何在不修改手机硬件电路的前提下实现与第三方传感器结合呢?麦克风就是一个很好的ADC接口,通过麦克风与第三方传感器结合,再在软件里对模拟信号做相应的处理,就可以提供更丰富的传感化应用。
先来看看本文程序运行的效果图(屏幕录像速度较慢,真机实际运行起来会更加流畅):
本文程序使用8000hz的采样率,对X轴方向绘图的实时性要求较高,如果不降低X轴的分辨率,程序的实时性较差,因此程序对X轴数据缩小区间为8倍~16倍。由于采用16位采样,因此Y轴数据的高度相对于手机屏幕来说也偏大,程序也对Y轴数据做缩小,区间为1倍~10倍。在SurfaceView的OnTouchListener方法里加入了波形基线的位置调节,直接在SurfaceView控件上触摸即可控制整体波形偏上或偏下显示。
main.xml源码如下:
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
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
>
<
LinearLayout
android:id
=
"@+id/LinearLayout01"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"horizontal"
>
<
Button
android:id
=
"@+id/btnStart"
android:layout_width
=
"80dip"
android:layout_height
=
"wrap_content"
android:text
=
"开始"
/>
<
Button
android:id
=
"@+id/btnExit"
android:layout_width
=
"80dip"
android:layout_height
=
"wrap_content"
android:text
=
"停止"
/>
<
ZoomControls
android:id
=
"@+id/zctlX"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
/>
<
ZoomControls
android:id
=
"@+id/zctlY"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
/>
</
LinearLayout
>
<
SurfaceView
android:id
=
"@+id/SurfaceView01"
android:layout_height
=
"fill_parent"
android:layout_width
=
"fill_parent"
>
</
SurfaceView
>
</
LinearLayout
>
ClsOscilloscope.java是实现示波器的类库,包含AudioRecord操作线程和SurfaceView绘图线程的实现,两个线程同步操作,代码如下:
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
158
159
160
161
162
package
com.testOscilloscope;
import
java.util.ArrayList;
import
android.graphics.Canvas;
import
android.graphics.Color;
import
android.graphics.Paint;
import
android.graphics.Rect;
import
android.media.AudioRecord;
import
android.view.SurfaceView;
public
class
ClsOscilloscope {
private
ArrayList<
short
[]> inBuf =
new
ArrayList<
short
[]>();
private
boolean
isRecording =
false
;
// 线程控制标记
/**
* X轴缩小的比例
*/
public
int
rateX =
4
;
/**
* Y轴缩小的比例
*/
public
int
rateY =
4
;
/**
* Y轴基线
*/
public
int
baseLine =
0
;
/**
* 初始化
*/
public
void
initOscilloscope(
int
rateX,
int
rateY,
int
baseLine) {
this
.rateX = rateX;
this
.rateY = rateY;
this
.baseLine = baseLine;
}
/**
* 开始
*
* @param recBufSize
* AudioRecord的MinBufferSize
*/
public
void
Start(AudioRecord audioRecord,
int
recBufSize, SurfaceView sfv,
Paint mPaint) {
isRecording =
true
;
new
RecordThread(audioRecord, recBufSize).start();
// 开始录制线程
new
DrawThread(sfv, mPaint).start();
// 开始绘制线程
}
/**
* 停止
*/
public
void
Stop() {
isRecording =
false
;
inBuf.clear();
// 清除
}
/**
* 负责从MIC保存数据到inBuf
*
* @author GV
*
*/
class
RecordThread
extends
Thread {
private
int
recBufSize;
private
AudioRecord audioRecord;
public
RecordThread(AudioRecord audioRecord,
int
recBufSize) {
this
.audioRecord = audioRecord;
this
.recBufSize = recBufSize;
}
public
void
run() {
try
{
short
[] buffer =
new
short
[recBufSize];
audioRecord.startRecording();
// 开始录制
while
(isRecording) {
// 从MIC保存数据到缓冲区
int
bufferReadResult = audioRecord.read(buffer,
0
,
recBufSize);
short
[] tmpBuf =
new
short
[bufferReadResult / rateX];
for
(
int
i =
0
, ii =
0
; i < tmpBuf.length; i++, ii = i
* rateX) {
tmpBuf[i] = buffer[ii];
}
synchronized
(inBuf) {
//
inBuf.add(tmpBuf);
// 添加数据
}
}
audioRecord.stop();
}
catch
(Throwable t) {
}
}
};
/**
* 负责绘制inBuf中的数据
*
* @author GV
*
*/
class
DrawThread
extends
Thread {
private
int
oldX =
0
;
// 上次绘制的X坐标
private
int
oldY =
0
;
// 上次绘制的Y坐标
private
SurfaceView sfv;
// 画板
private
int
X_index =
0
;
// 当前画图所在屏幕X轴的坐标
private
Paint mPaint;
// 画笔
public
DrawThread(SurfaceView sfv, Paint mPaint) {
this
.sfv = sfv;
this
.mPaint = mPaint;
}
public
void
run() {
while
(isRecording) {
ArrayList<
short
[]> buf =
new
ArrayList<
short
[]>();
synchronized
(inBuf) {
if
(inBuf.size() ==
0
)
continue
;
buf = (ArrayList<
short
[]>) inBuf.clone();
// 保存
inBuf.clear();
// 清除
}
for
(
int
i =
0
; i < buf.size(); i++) {
short
[] tmpBuf = buf.get(i);
SimpleDraw(X_index, tmpBuf, rateY, baseLine);
// 把缓冲区数据画出来
X_index = X_index + tmpBuf.length;
if
(X_index > sfv.getWidth()) {
X_index =
0
;
}
}
}
}
/**
* 绘制指定区域
*
* @param start
* X轴开始的位置(全屏)
* @param buffer
* 缓冲区
* @param rate
* Y轴数据缩小的比例
* @param baseLine
* Y轴基线
*/
void
SimpleDraw(
int
start,
short
[] buffer,
int
rate,
int
baseLine) {
if
(start ==
0
)
oldX =
0
;
Canvas canvas = sfv.getHolder().lockCanvas(
new
Rect(start,
0
, start + buffer.length, sfv.getHeight()));
// 关键:获取画布
canvas.drawColor(Color.BLACK);
// 清除背景
int
y;
for
(
int
i =
0
; i < buffer.length; i++) {
// 有多少画多少
int
x = i + start;
y = buffer[i] / rate + baseLine;
// 调节缩小比例,调节基准线
canvas.drawLine(oldX, oldY, x, y, mPaint);
oldX = x;
oldY = y;
}
sfv.getHolder().unlockCanvasAndPost(canvas);
// 解锁画布,提交画好的图像
}
}
}
testOscilloscope.java是主程序,控制UI和ClsOscilloscope,代码如下:
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
package
com.testOscilloscope;
import
android.app.Activity;
import
android.graphics.Color;
import
android.graphics.Paint;
import
android.media.AudioFormat;
import
android.media.AudioRecord;
import
android.media.MediaRecorder;
import
android.os.Bundle;
import
android.view.MotionEvent;
import
android.view.SurfaceView;
import
android.view.View;
import
android.view.View.OnTouchListener;
import
android.widget.Button;
import
android.widget.ZoomControls;
public
class
testOscilloscope
extends
Activity {
/** Called when the activity is first created. */
Button btnStart, btnExit;
SurfaceView sfv;
ZoomControls zctlX, zctlY;
ClsOscilloscope clsOscilloscope =
new
ClsOscilloscope();
static
final
int
frequency =
8000
;
// 分辨率
static
final
int
channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
static
final
int
audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
static
final
int
xMax =
16
;
// X轴缩小比例最大值,X轴数据量巨大,容易产生刷新延时
static
final
int
xMin =
8
;
// X轴缩小比例最小值
static
final
int
yMax =
10
;
// Y轴缩小比例最大值
static
final
int
yMin =
1
;
// Y轴缩小比例最小值
int
recBufSize;
// 录音最小buffer大小
AudioRecord audioRecord;
Paint mPaint;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 录音组件
recBufSize = AudioRecord.getMinBufferSize(frequency,
channelConfiguration, audioEncoding);
audioRecord =
new
AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
channelConfiguration, audioEncoding, recBufSize);
// 按键
btnStart = (Button)
this
.findViewById(R.id.btnStart);
btnStart.setOnClickListener(
new
ClickEvent());
btnExit = (Button)
this
.findViewById(R.id.btnExit);
btnExit.setOnClickListener(
new
ClickEvent());
// 画板和画笔
sfv = (SurfaceView)
this
.findViewById(R.id.SurfaceView01);
sfv.setOnTouchListener(
new
TouchEvent());
mPaint =
new
Paint();
mPaint.setColor(Color.GREEN);
// 画笔为绿色
mPaint.setStrokeWidth(
1
);
// 设置画笔粗细
// 示波 器类库
clsOscilloscope.initOscilloscope(xMax /
2
, yMax /
2
,
sfv.getHeight() /
2
);
// 缩放控件,X轴的数据缩小的比率高些
zctlX = (ZoomControls)
this
.findViewById(R.id.zctlX);
zctlX.setOnZoomInClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(clsOscilloscope.rateX > xMin)
clsOscilloscope.rateX--;
setTitle(
"X轴缩小"
+ String.valueOf(clsOscilloscope.rateX) +
"倍"
+
","
+
"Y轴缩小"
+ String.valueOf(clsOscilloscope.rateY)
+
"倍"
);
}
});
zctlX.setOnZoomOutClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(clsOscilloscope.rateX < xMax)
clsOscilloscope.rateX++;
setTitle(
"X轴缩小"
+ String.valueOf(clsOscilloscope.rateX) +
"倍"
+
","
+
"Y轴缩小"
+ String.valueOf(clsOscilloscope.rateY)
+
"倍"
);
}
});
zctlY = (ZoomControls)
this
.findViewById(R.id.zctlY);
zctlY.setOnZoomInClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(clsOscilloscope.rateY > yMin)
clsOscilloscope.rateY--;
setTitle(
"X轴缩小"
+ String.valueOf(clsOscilloscope.rateX) +
"倍"
+
","
+
"Y轴缩小"
+ String.valueOf(clsOscilloscope.rateY)
+
"倍"
);
}
});
zctlY.setOnZoomOutClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
if
(clsOscilloscope.rateY < yMax)
clsOscilloscope.rateY++;
setTitle(
"X轴缩小"
+ String.valueOf(clsOscilloscope.rateX) +
"倍"
+
","
+
"Y轴缩小"
+ String.valueOf(clsOscilloscope.rateY)
+
"倍"
);
}
});
}
@Override
protected
void
onDestroy() {
super
.onDestroy();
android.os.Process.killProcess(android.os.Process.myPid());
}
/**
* 按键事件处理
*
* @author GV
*
*/
class
ClickEvent
implements
View.OnClickListener {
@Override
public
void
onClick(View v) {
if
(v == btnStart) {
clsOscilloscope.baseLine = sfv.getHeight() /
2
;
clsOscilloscope.Start(audioRecord, recBufSize, sfv, mPaint);
}
else
if
(v == btnExit) {
clsOscilloscope.Stop();
}
}
}
/**
* 触摸屏动态设置波形图基线
*
* @author GV
*
*/
class
TouchEvent
implements
OnTouchListener {
@Override
public
boolean
onTouch(View v, MotionEvent event) {
clsOscilloscope.baseLine = (
int
) event.getY();
return
true
;
}
}
}
- 11.模拟信号示波器
- 模拟信号示波器
- Android之模拟信号示波器
- Android版的手机模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高应用篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- Android提高第十一篇之模拟信号示波器
- 示波器基本原理1:模拟示波器
- C语言模拟示波器
- 模拟示波器使用总结
- iMSO-204L混合信号示波器
- 模拟信号
- simulink信号分支和多信号输入到一个示波器
- c/c++在windows下获取时间和计算时间差的几种方法总结
- 微信公众帐号开发系列教程第1篇-引言 (Java版)
- 公交司机解决PSQLException
- Central Europe 1996 / UVa 311 / POJ 1017 Packets (贪心)
- Java中使用JDBC实现增删改查
- 模拟信号示波器
- tcp头详解
- LINUX与UNIX Shell编程指南
- 最大流算法
- windows7下提示:“'telnet' 不是内部或外部命令 解决办法
- Alfresco 安装
- Oracle表的管理
- IP头结构详解
- 【PAT Advanced Level】1012. The Best Rank (25)