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

本文来自http://blog.csdn.net/hellogv/,引用必须注明出处!

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

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

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

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

main.xml源码如下:

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

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

  1. packagecom.testOscilloscope;
  2. importjava.util.ArrayList;
  3. importandroid.graphics.Canvas;
  4. importandroid.graphics.Color;
  5. importandroid.graphics.Paint;
  6. importandroid.graphics.Rect;
  7. importandroid.media.AudioRecord;
  8. importandroid.view.SurfaceView;
  9. publicclassClsOscilloscope{
  10. privateArrayList<short[]>inBuf=newArrayList<short[]>();
  11. privatebooleanisRecording=false;//线程控制标记
  12. /**
  13. *X轴缩小的比例
  14. */
  15. publicintrateX=4;
  16. /**
  17. *Y轴缩小的比例
  18. */
  19. publicintrateY=4;
  20. /**
  21. *Y轴基线
  22. */
  23. publicintbaseLine=0;
  24. /**
  25. *初始化
  26. */
  27. publicvoidinitOscilloscope(intrateX,intrateY,intbaseLine){
  28. this.rateX=rateX;
  29. this.rateY=rateY;
  30. this.baseLine=baseLine;
  31. }
  32. /**
  33. *开始
  34. *
  35. *@paramrecBufSize
  36. *AudioRecord的MinBufferSize
  37. */
  38. publicvoidStart(AudioRecordaudioRecord,intrecBufSize,SurfaceViewsfv,
  39. PaintmPaint){
  40. isRecording=true;
  41. newRecordThread(audioRecord,recBufSize).start();//开始录制线程
  42. newDrawThread(sfv,mPaint).start();//开始绘制线程
  43. }
  44. /**
  45. *停止
  46. */
  47. publicvoidStop(){
  48. isRecording=false;
  49. inBuf.clear();//清除
  50. }
  51. /**
  52. *负责从MIC保存数据到inBuf
  53. *
  54. *@authorGV
  55. *
  56. */
  57. classRecordThreadextendsThread{
  58. privateintrecBufSize;
  59. privateAudioRecordaudioRecord;
  60. publicRecordThread(AudioRecordaudioRecord,intrecBufSize){
  61. this.audioRecord=audioRecord;
  62. this.recBufSize=recBufSize;
  63. }
  64. publicvoidrun(){
  65. try{
  66. short[]buffer=newshort[recBufSize];
  67. audioRecord.startRecording();//开始录制
  68. while(isRecording){
  69. //从MIC保存数据到缓冲区
  70. intbufferReadResult=audioRecord.read(buffer,0,
  71. recBufSize);
  72. short[]tmpBuf=newshort[bufferReadResult/rateX];
  73. for(inti=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(Throwablet){
  83. }
  84. }
  85. };
  86. /**
  87. *负责绘制inBuf中的数据
  88. *
  89. *@authorGV
  90. *
  91. */
  92. classDrawThreadextendsThread{
  93. privateintoldX=0;//上次绘制的X坐标
  94. privateintoldY=0;//上次绘制的Y坐标
  95. privateSurfaceViewsfv;//画板
  96. privateintX_index=0;//当前画图所在屏幕X轴的坐标
  97. privatePaintmPaint;//画笔
  98. publicDrawThread(SurfaceViewsfv,PaintmPaint){
  99. this.sfv=sfv;
  100. this.mPaint=mPaint;
  101. }
  102. publicvoidrun(){
  103. while(isRecording){
  104. ArrayList<short[]>buf=newArrayList<short[]>();
  105. synchronized(inBuf){
  106. if(inBuf.size()==0)
  107. continue;
  108. buf=(ArrayList<short[]>)inBuf.clone();//保存
  109. inBuf.clear();//清除
  110. }
  111. for(inti=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. *@paramstart
  125. *X轴开始的位置(全屏)
  126. *@parambuffer
  127. *缓冲区
  128. *@paramrate
  129. *Y轴数据缩小的比例
  130. *@parambaseLine
  131. *Y轴基线
  132. */
  133. voidSimpleDraw(intstart,short[]buffer,intrate,intbaseLine){
  134. if(start==0)
  135. oldX=0;
  136. Canvascanvas=sfv.getHolder().lockCanvas(
  137. newRect(start,0,start+buffer.length,sfv.getHeight()));//关键:获取画布
  138. canvas.drawColor(Color.BLACK);//清除背景
  139. inty;
  140. for(inti=0;i<buffer.length;i++){//有多少画多少
  141. intx=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,代码如下:

  1. packagecom.testOscilloscope;
  2. importandroid.app.Activity;
  3. importandroid.graphics.Color;
  4. importandroid.graphics.Paint;
  5. importandroid.media.AudioFormat;
  6. importandroid.media.AudioRecord;
  7. importandroid.media.MediaRecorder;
  8. importandroid.os.Bundle;
  9. importandroid.view.MotionEvent;
  10. importandroid.view.SurfaceView;
  11. importandroid.view.View;
  12. importandroid.view.View.OnTouchListener;
  13. importandroid.widget.Button;
  14. importandroid.widget.ZoomControls;
  15. publicclasstestOscilloscopeextendsActivity{
  16. /**Calledwhentheactivityisfirstcreated.*/
  17. ButtonbtnStart,btnExit;
  18. SurfaceViewsfv;
  19. ZoomControlszctlX,zctlY;
  20. ClsOscilloscopeclsOscilloscope=newClsOscilloscope();
  21. staticfinalintfrequency=8000;//分辨率
  22. staticfinalintchannelConfiguration=AudioFormat.CHANNEL_CONFIGURATION_MONO;
  23. staticfinalintaudioEncoding=AudioFormat.ENCODING_PCM_16BIT;
  24. staticfinalintxMax=16;//X轴缩小比例最大值,X轴数据量巨大,容易产生刷新延时
  25. staticfinalintxMin=8;//X轴缩小比例最小值
  26. staticfinalintyMax=10;//Y轴缩小比例最大值
  27. staticfinalintyMin=1;//Y轴缩小比例最小值
  28. intrecBufSize;//录音最小buffer大小
  29. AudioRecordaudioRecord;
  30. PaintmPaint;
  31. @Override
  32. publicvoidonCreate(BundlesavedInstanceState){
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.main);
  35. //录音组件
  36. recBufSize=AudioRecord.getMinBufferSize(frequency,
  37. channelConfiguration,audioEncoding);
  38. audioRecord=newAudioRecord(MediaRecorder.AudioSource.MIC,frequency,
  39. channelConfiguration,audioEncoding,recBufSize);
  40. //按键
  41. btnStart=(Button)this.findViewById(R.id.btnStart);
  42. btnStart.setOnClickListener(newClickEvent());
  43. btnExit=(Button)this.findViewById(R.id.btnExit);
  44. btnExit.setOnClickListener(newClickEvent());
  45. //画板和画笔
  46. sfv=(SurfaceView)this.findViewById(R.id.SurfaceView01);
  47. sfv.setOnTouchListener(newTouchEvent());
  48. mPaint=newPaint();
  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(newView.OnClickListener(){
  56. @Override
  57. publicvoidonClick(Viewv){
  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(newView.OnClickListener(){
  65. @Override
  66. publicvoidonClick(Viewv){
  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(newView.OnClickListener(){
  75. @Override
  76. publicvoidonClick(Viewv){
  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(newView.OnClickListener(){
  84. @Override
  85. publicvoidonClick(Viewv){
  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. protectedvoidonDestroy(){
  95. super.onDestroy();
  96. android.os.Process.killProcess(android.os.Process.myPid());
  97. }
  98. /**
  99. *按键事件处理
  100. *@authorGV
  101. *
  102. */
  103. classClickEventimplementsView.OnClickListener{
  104. @Override
  105. publicvoidonClick(Viewv){
  106. if(v==btnStart){
  107. clsOscilloscope.baseLine=sfv.getHeight()/2;
  108. clsOscilloscope.Start(audioRecord,recBufSize,sfv,mPaint);
  109. }elseif(v==btnExit){
  110. clsOscilloscope.Stop();
  111. }
  112. }
  113. }
  114. /**
  115. *触摸屏动态设置波形图基线
  116. *@authorGV
  117. *
  118. */
  119. classTouchEventimplementsOnTouchListener{
  120. @Override
  121. publicbooleanonTouch(Viewv,MotionEventevent){
  122. clsOscilloscope.baseLine=(int)event.getY();
  123. returntrue;
  124. }
  125. }
  126. }

你可能感兴趣的:(android)