原文链接(http://blog.csdn.net/hellogv/article/details/6032046)

上次简单地介绍了AudioRecord和AudioTrack的使用,这次就结合SurfaceView实现一个Android版的手机模拟信号示波器(PS:以前也讲过J2ME版的手机示波器)。最近物联网炒得很火,作为手机软件开发者,如何在不修改手机硬件电路的前提下实现与第三方传感器结合呢?麦克风就是一个很好的ADC接口,通过麦克风与第三方传感器结合,再在软件里对模拟信号做相应的处理,就可以提供更丰富的传感化应用。

先来看看本文程序运行的效果图(屏幕录像速度较慢,真机实际运行起来会更加流畅):

本文程序使用8000hz的采样率,对X轴方向绘图的实时性要求较高,如果不降低X轴的分辨率,程序的实时性较差,因此程序对X轴数据缩小区间为8倍~16倍。由于采用16位采样,因此Y轴数据的高度相对于手机屏幕来说也偏大,程序也对Y轴数据做缩小,区间为1倍~10倍。在SurfaceView的OnTouchListener方法里加入了波形基线的位置调节,直接在SurfaceView控件上触摸即可控制整体波形偏上或偏下显示。

main.xml源码如下:

[xhtml] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical" android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <LinearLayout android:id="@+id/LinearLayout01"
  6. android:layout_height="wrap_content" android:layout_width="fill_parent"
  7. android:orientation="horizontal">
  8. <Button android:layout_height="wrap_content" android:id="@+id/btnStart"
  9. android:text="开始" android:layout_width="80dip"></Button>
  10. <Button android:layout_height="wrap_content" android:text="停止"
  11. android:id="@+id/btnExit" android:layout_width="80dip"></Button>
  12. <ZoomControls android:layout_width="wrap_content"
  13. android:layout_height="wrap_content" android:id="@+id/zctlX"></ZoomControls>
  14. <ZoomControls android:layout_width="wrap_content"
  15. android:layout_height="wrap_content" android:id="@+id/zctlY"></ZoomControls>
  16. </LinearLayout>
  17. <SurfaceView android:id="@+id/SurfaceView01"
  18. android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView>
  19. </LinearLayout>

ClsOscilloscope.java是实现示波器的类库,包含AudioRecord操作线程和SurfaceView绘图线程的实现,两个线程同步操作,代码如下:

[java] view plaincopyprint?
  1. package com.testOscilloscope;
  2. import java.util.ArrayList;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.graphics.Rect;
  7. import android.media.AudioRecord;
  8. import android.view.SurfaceView;
  9. public class ClsOscilloscope {
  10. private ArrayList<short[]> inBuf = new ArrayList<short[]>();
  11. private boolean isRecording = false;// 线程控制标记
  12. /**
  13. * X轴缩小的比例
  14. */
  15. public int rateX = 4;
  16. /**
  17. * Y轴缩小的比例
  18. */
  19. public int rateY = 4;
  20. /**
  21. * Y轴基线
  22. */
  23. public int baseLine = 0;
  24. /**
  25. * 初始化
  26. */
  27. public void initOscilloscope(int rateX, int rateY, int baseLine) {
  28. this.rateX = rateX;
  29. this.rateY = rateY;
  30. this.baseLine = baseLine;
  31. }
  32. /**
  33. * 开始
  34. *
  35. * @param recBufSize
  36. *            AudioRecord的MinBufferSize
  37. */
  38. public void Start(AudioRecord audioRecord, int recBufSize, SurfaceView sfv,
  39. Paint mPaint) {
  40. isRecording = true;
  41. new RecordThread(audioRecord, recBufSize).start();// 开始录制线程
  42. new DrawThread(sfv, mPaint).start();// 开始绘制线程
  43. }
  44. /**
  45. * 停止
  46. */
  47. public void Stop() {
  48. isRecording = false;
  49. inBuf.clear();// 清除
  50. }
  51. /**
  52. * 负责从MIC保存数据到inBuf
  53. *
  54. * @author GV
  55. *
  56. */
  57. class RecordThread extends Thread {
  58. private int recBufSize;
  59. private AudioRecord audioRecord;
  60. public RecordThread(AudioRecord audioRecord, int recBufSize) {
  61. this.audioRecord = audioRecord;
  62. this.recBufSize = recBufSize;
  63. }
  64. public void run() {
  65. try {
  66. short[] buffer = new short[recBufSize];
  67. audioRecord.startRecording();// 开始录制
  68. while (isRecording) {
  69. // 从MIC保存数据到缓冲区
  70. int bufferReadResult = audioRecord.read(buffer, 0,
  71. recBufSize);
  72. short[] tmpBuf = new short[bufferReadResult / rateX];
  73. for (int i = 0, ii = 0; i < tmpBuf.length; i++, ii = i
  74. * rateX) {
  75. tmpBuf[i] = buffer[ii];
  76. }
  77. synchronized (inBuf) {//
  78. inBuf.add(tmpBuf);// 添加数据
  79. }
  80. }
  81. audioRecord.stop();
  82. } catch (Throwable t) {
  83. }
  84. }
  85. };
  86. /**
  87. * 负责绘制inBuf中的数据
  88. *
  89. * @author GV
  90. *
  91. */
  92. class DrawThread extends Thread {
  93. private int oldX = 0;// 上次绘制的X坐标
  94. private int oldY = 0;// 上次绘制的Y坐标
  95. private SurfaceView sfv;// 画板
  96. private int X_index = 0;// 当前画图所在屏幕X轴的坐标
  97. private Paint mPaint;// 画笔
  98. public DrawThread(SurfaceView sfv, Paint mPaint) {
  99. this.sfv = sfv;
  100. this.mPaint = mPaint;
  101. }
  102. public void run() {
  103. while (isRecording) {
  104. ArrayList<short[]> buf = new ArrayList<short[]>();
  105. synchronized (inBuf) {
  106. if (inBuf.size() == 0)
  107. continue;
  108. buf = (ArrayList<short[]>) inBuf.clone();// 保存
  109. inBuf.clear();// 清除
  110. }
  111. for (int i = 0; i < buf.size(); i++) {
  112. short[] tmpBuf = buf.get(i);
  113. SimpleDraw(X_index, tmpBuf, rateY, baseLine);// 把缓冲区数据画出来
  114. X_index = X_index + tmpBuf.length;
  115. if (X_index > sfv.getWidth()) {
  116. X_index = 0;
  117. }
  118. }
  119. }
  120. }
  121. /**
  122. * 绘制指定区域
  123. *
  124. * @param start
  125. *            X轴开始的位置(全屏)
  126. * @param buffer
  127. *            缓冲区
  128. * @param rate
  129. *            Y轴数据缩小的比例
  130. * @param baseLine
  131. *            Y轴基线
  132. */
  133. void SimpleDraw(int start, short[] buffer, int rate, int baseLine) {
  134. if (start == 0)
  135. oldX = 0;
  136. Canvas canvas = sfv.getHolder().lockCanvas(
  137. new Rect(start, 0, start + buffer.length, sfv.getHeight()));// 关键:获取画布
  138. canvas.drawColor(Color.BLACK);// 清除背景
  139. int y;
  140. for (int i = 0; i < buffer.length; i++) {// 有多少画多少
  141. int x = i + start;
  142. y = buffer[i] / rate + baseLine;// 调节缩小比例,调节基准线
  143. canvas.drawLine(oldX, oldY, x, y, mPaint);
  144. oldX = x;
  145. oldY = y;
  146. }
  147. sfv.getHolder().unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像
  148. }
  149. }
  150. }

testOscilloscope.java是主程序,控制UI和ClsOscilloscope,代码如下:

[java] view plaincopyprint?
  1. package com.testOscilloscope;
  2. import android.app.Activity;
  3. import android.graphics.Color;
  4. import android.graphics.Paint;
  5. import android.media.AudioFormat;
  6. import android.media.AudioRecord;
  7. import android.media.MediaRecorder;
  8. import android.os.Bundle;
  9. import android.view.MotionEvent;
  10. import android.view.SurfaceView;
  11. import android.view.View;
  12. import android.view.View.OnTouchListener;
  13. import android.widget.Button;
  14. import android.widget.ZoomControls;
  15. public class testOscilloscope extends Activity {
  16. /** Called when the activity is first created. */
  17. Button btnStart,btnExit;
  18. SurfaceView sfv;
  19. ZoomControls zctlX,zctlY;
  20. ClsOscilloscope clsOscilloscope=new ClsOscilloscope();
  21. static final int frequency = 8000;//分辨率
  22. static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
  23. static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
  24. static final int xMax = 16;//X轴缩小比例最大值,X轴数据量巨大,容易产生刷新延时
  25. static final int xMin = 8;//X轴缩小比例最小值
  26. static final int yMax = 10;//Y轴缩小比例最大值
  27. static final int yMin = 1;//Y轴缩小比例最小值
  28. int recBufSize;//录音最小buffer大小
  29. AudioRecord audioRecord;
  30. Paint mPaint;
  31. @Override
  32. public void onCreate(Bundle savedInstanceState) {
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.main);
  35. //录音组件
  36. recBufSize = AudioRecord.getMinBufferSize(frequency,
  37. channelConfiguration, audioEncoding);
  38. audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
  39. channelConfiguration, audioEncoding, recBufSize);
  40. //按键
  41. btnStart = (Button) this.findViewById(R.id.btnStart);
  42. btnStart.setOnClickListener(new ClickEvent());
  43. btnExit = (Button) this.findViewById(R.id.btnExit);
  44. btnExit.setOnClickListener(new ClickEvent());
  45. //画板和画笔
  46. sfv = (SurfaceView) this.findViewById(R.id.SurfaceView01);
  47. sfv.setOnTouchListener(new TouchEvent());
  48. mPaint = new Paint();
  49. mPaint.setColor(Color.GREEN);// 画笔为绿色
  50. mPaint.setStrokeWidth(1);// 设置画笔粗细
  51. //示波器类库
  52. clsOscilloscope.initOscilloscope(xMax/2, yMax/2, sfv.getHeight()/2);
  53. //缩放控件,X轴的数据缩小的比率高些
  54. zctlX = (ZoomControls)this.findViewById(R.id.zctlX);
  55. zctlX.setOnZoomInClickListener(new View.OnClickListener() {
  56. @Override
  57. public void onClick(View v) {
  58. if(clsOscilloscope.rateX>xMin)
  59. clsOscilloscope.rateX--;
  60. setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍"
  61. +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍");
  62. }
  63. });
  64. zctlX.setOnZoomOutClickListener(new View.OnClickListener() {
  65. @Override
  66. public void onClick(View v) {
  67. if(clsOscilloscope.rateX<xMax)
  68. clsOscilloscope.rateX++;
  69. setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍"
  70. +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍");
  71. }
  72. });
  73. zctlY = (ZoomControls)this.findViewById(R.id.zctlY);
  74. zctlY.setOnZoomInClickListener(new View.OnClickListener() {
  75. @Override
  76. public void onClick(View v) {
  77. if(clsOscilloscope.rateY>yMin)
  78. clsOscilloscope.rateY--;
  79. setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍"
  80. +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍");
  81. }
  82. });
  83. zctlY.setOnZoomOutClickListener(new View.OnClickListener() {
  84. @Override
  85. public void onClick(View v) {
  86. if(clsOscilloscope.rateY<yMax)
  87. clsOscilloscope.rateY++;
  88. setTitle("X轴缩小"+String.valueOf(clsOscilloscope.rateX)+"倍"
  89. +","+"Y轴缩小"+String.valueOf(clsOscilloscope.rateY)+"倍");
  90. }
  91. });
  92. }
  93. @Override
  94. protected void onDestroy() {
  95. super.onDestroy();
  96. android.os.Process.killProcess(android.os.Process.myPid());
  97. }
  98. /**
  99. * 按键事件处理
  100. * @author GV
  101. *
  102. */
  103. class ClickEvent implements View.OnClickListener {
  104. @Override
  105. public void onClick(View v) {
  106. if (v == btnStart) {
  107. clsOscilloscope.baseLine=sfv.getHeight()/2;
  108. clsOscilloscope.Start(audioRecord,recBufSize,sfv,mPaint);
  109. } else if (v == btnExit) {
  110. clsOscilloscope.Stop();
  111. }
  112. }
  113. }
  114. /**
  115. * 触摸屏动态设置波形图基线
  116. * @author GV
  117. *
  118. */
  119. class TouchEvent implements OnTouchListener{
  120. @Override
  121. public boolean onTouch(View v, MotionEvent event) {
  122. clsOscilloscope.baseLine=(int)event.getY();
  123. return true;
  124. }
  125. }
  126. }

Android之模拟信号示波器相关推荐

  1. Android版的手机模拟信号示波器

    本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处! 上次简单地介绍了AudioRecord和AudioTrack的使用,这次就结合SurfaceView实现一个A ...

  2. Android提高第十一篇之模拟信号示波器

    本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处! 上次简单地介绍了AudioRecord和AudioTrack的使用,这次就结合SurfaceView实现一个A ...

  3. Android实现简易示波器

    用SurfaceView简易的实现一个示波器,这需求估计玩电子的比较需要吧. 这里学到一个接口蛮有意思的,简单的说一下. holder.lockCanvas(null),就是锁住整张画布,绘画完成后也 ...

  4. Android实现音乐示波器、均衡器、重低音和音场功能

    本实例来自于<疯狂Android讲义>,要实现具体的功能,需要了解以下API: MediaPlayer  媒体播放器 Visualizer 频谱 Equalizer 均衡器 BassBoo ...

  5. 我的Android进阶之旅------gt;Android实现音乐示波器、均衡器、重低音和音场功能...

    本实例来自于<疯狂Android讲义>.要实现详细的功能,须要了解下面API: MediaPlayer  媒体播放器 Visualizer 频谱 Equalizer 均衡器 BassBoo ...

  6. Android语音信号波形显示

    简单地介绍了AudioRecord和AudioTrack的使用,这次就结合SurfaceView实现一个Android版的手机模拟信号示波器(PS:以前也讲过J2ME版的手机示波器).最近物联网炒得很 ...

  7. Android提高篇内容整理

    1.Android提高第一篇之MediaPlayer http://www.apkbus.com/android-23947-1-1.html 2.Android提高第二篇之SurfaceView的基 ...

  8. android开发教程21篇(强烈推荐,几乎每一篇都是精华教程)

    qianqianlianmeng android开发教程21篇(强烈推荐,几乎每一篇都是精华教程) 推荐hellogv 的二十一篇android开发教程,说句真心话,几乎是每一篇都是精华,值得很多开发 ...

  9. 高德车载导航Android平台DR回放技术方案

    导读 DR:(英文为Dead Reckoning,航迹推算).用于推算的传感器大致有:陀螺仪.四轮速.车速脉冲.3D加速度计等.在车载导航中,航位推算是使用先前确定的位置,通过测量移动的距离和方位,计 ...

最新文章

  1. 三代测序的基本原理、组装方法和应用场景
  2. “我明明是一个老师,病毒却把我逼成了主播……”
  3. Running pip as root will break packages and permissions. You should install packages reliably by usi
  4. 【人脸识别】基于matlab GUI肤色人脸识别定位【含Matlab源码 674期】
  5. 一篇文章掌握Egg.js的核心用法
  6. js判断是否是数组的几种方法
  7. 阿里云企业邮箱版本对比(标准版、集团版和尊享版)
  8. phython入门基础
  9. wordpress mysql 密码重置_WordPress登录密码找回办法之修改数据库篇 | Wopus
  10. 2015 Syrian Private Universities Collegiate Programming Contest
  11. 马丁富勒微服务论文连接
  12. 修改MySQL数据库的密码
  13. SSM在线车队货车管理系统
  14. 移动支付(mobile money)系列 —— Apple Pay的原理
  15. 利用windows上的VMware安装CentOS7(上)
  16. 【cov-19】新冠肺炎的SIR模型补充与应用
  17. 记录一次升级openssl的问题 (pthread_atfork undefined)
  18. 【网络流量入侵检测数据集】CIC-IDS-2017数据集预处理
  19. 鸦片战争始末小结与个人感悟·《中国近百年政治史》
  20. 丑女贝蒂第一至四季/全集Ugly Betty迅雷下载

热门文章

  1. IBM X230I装回xp或win7
  2. 查阅中文字符的Unicode编码
  3. Apple_friend讲述如何将word转换成pdf文件的操作
  4. Ubuntu(Debian)截图并编辑软件推荐:Flameshot(实现类似QQ的截图功能,具有插入文字功能)
  5. 特征选择方法之CHI、IG、TF-IDF
  6. 超15亿人听力损伤,耳机是罪魁祸首吗?
  7. kaggle lung cancer detection--Full Preprocessing Tuturial(附翻译)
  8. 记忆里总有着各种各样的故事
  9. 现代机器人(Modern Robotics):力学,规划,控制读书笔记
  10. Java 1.8(圆的面积和周长)编写程序,使用以下公式计算并显示半径为5.5的圆的面积和周长。