Android提高之模拟信号示波器的实现

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

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

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

main.xml源码如下:

<?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_height="wrap_content" android:layout_width="fill_parent"
 android:orientation="horizontal">
 <Button android:layout_height="wrap_content" android:id="@+id/btnStart"
  android:text="开始" android:layout_width="80dip"></Button>
 <Button android:layout_height="wrap_content" android:text="停止"
  android:id="@+id/btnExit" android:layout_width="80dip"></Button>
 <ZoomControls android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:id="@+id/zctlX"></ZoomControls>
 <ZoomControls android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:id="@+id/zctlY"></ZoomControls>
 </LinearLayout>
 <SurfaceView android:id="@+id/SurfaceView01"
 android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView>
</LinearLayout>

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

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,代码如下:

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;
 }
 }
}

希望本文实例对于读者进行Android项目开发能起到一定的借鉴与帮助作用。

(0)

相关推荐

  • Android特效之水波纹的实现

    前言 水波纹特效,想必大家或多或少见过,在我的印象中,大致有如下几种: 支付宝 "咻咻咻" 式 流量球 "荡漾" 式 真实的水波纹效果,基于Bitmap处理式 话不多说,先来看看效果: 填充式水波纹,间距相等 非填充式水波纹,间距相等 非填充式水波纹,间距不断变大 填充式水波纹,间距不断变小 想必大家已经知道基本的原理了,就是用Canvas来画嘛,但可不是简单的画哦,请往下看. 分析 这种类型的水波纹,其实无非就是画圆而已,在给定的矩形中,一个个圆由最小半径扩大到最

  • Android实现点击Button产生水波纹效果

    先上图,看看接下来我要向大家介绍的是个什么东西,如下图: 接下来要介绍的就是如何实现上述图中的波纹效果,这种效果如果大家没有体验过的话,可以看看百度手机卫士或者360手机卫士,里面的按钮点击效果都是这样的,另外Android 5.0以上的版本也出现了这种效果.不多说,下面聊聊具体的怎么实现. 首先大家看到的是三个button,水波纹的出现给我们的错觉是直接将波纹绘制在button上面的,但是这样能做到吗?首先button自己有background和src,如果把半透明的水波纹当作backgrou

  • Android波纹扩散效果之仿支付宝咻一咻功能实现波纹扩散特效

    今年春节晚会没看尽兴,被支付宝集福给添了一段插曲,朋友们都在那数定时间段不停的咻一咻,哇,我咻到一个敬业福,不可能的,哈哈.那么咻一咻功能基于程序代码是怎么实现的呢?下面我们小编给大家分享本教程帮助大家学习Android波纹扩散效果之仿支付宝咻一咻功能实现波纹扩散特效,具体内容如下所示: 先来看看这个效果 这是我的在Only上添加的效果,说实话,Only现在都还只是半成品,台面都上不了,怪自己技术不行,也太懒了 PS:这个view也是我模仿了人家的效果,参考了人家的思路写的,不是纯手撸,罪过罪过

  • Android自定义view实现水波纹进度球效果

    今天我们要实现的这个view没有太多交互性的view,所以就继承view. 自定义view的套路,套路很深 1.获取我们自定义属性attrs(可省略) 2.重写onMeasure方法,计算控件的宽和高 3.重写onDraw方法,绘制我们的控件 这么看来,自定义view的套路很清晰嘛. 我们看下今天的效果图,其中一个是放慢的效果(时间调的长) 我们按照套路来. 一.自定义属性 <declare-styleable name="WaveProgressView"> <at

  • Android仿水波纹流量球进度条控制器

    仿水波纹流球进度条控制器,Android实现高端大气的主流特效,供大家参考,具体内容如下 效果图: CircleView 这里主要是实现中心圆以及水波特效 package com.lgl.circleview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.gra

  • Android实现波浪线效果(xml bitmap)

    我们要实现的效果如下: 在这之前先带大家了解一下xml bitmap,何为XML Bitmap? XML Bitmap 是一个用XML定义的文件放在资源目录,定义的对象是图片,为bitmap定义别名,这个文件可以给bitmap定义一些额外的属性.例如:抖动. 1.文件存放位置 res/drawable/filename.xml 2.语法 <?xml version="1.0" encoding="utf-8"?> <bitmap xmlns:and

  • Android实现自定义华丽的水波纹效果

    先来看看效果 实现效果 模拟水波纹的效果:点击屏幕就有圆环出现,半径从小到大,透明度从大到小(0为透明) 实现思路 1.自定义类继承View. 2.定义每个圆环的实体类 Wave,并初始化绘制圆环的画笔的数据. 3.重写onTouchEvent方法,down时,获得坐标点,做为圆环圆心. 4.发送handler信息,对数据进行修改,刷新页面. 5.重写onDraw方法,绘制一个圆环. 1. 自定义类继承View 新建WaterWaveView2类继承View public class Water

  • Android实现兼容的水波纹效果

    先看看效果图 其实,要实现这一效果很简单,只要分drawable和drawablev21两个文件夹就好了. 普通情况下的selector: <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@c

  • Android项目实战手把手教你画圆形水波纹loadingview

    本文实例讲解的是如何画一个满满圆形水波纹loadingview,这类效果应用场景很多,比如内存占用百分比之类的,分享给大家供大家参考,具体内容如下 效果图如下: 预备的知识: 1.贝塞尔曲线    如果你不了解,可以来这里进行基础知识储备:神奇的贝塞尔曲线 2.Paint.setXfermode()  以及PorterDuffXfermode 千万不要被这个b的名字吓到,不熟悉看到可能会认为很难记,其实 只要站在巨人的丁丁上 还是很简单的. 好了 废话不多说 ,跟我一步步来做一个炫酷的view吧

  • Android提高之模拟信号示波器的实现

    前面简单地介绍了Android程序开发中AudioRecord和AudioTrack的使用,这次再结合SurfaceView实现一个Android版的手机模拟信号示波器.最近物联网炒得很火,作为手机软件开发者,如何在不修改手机硬件电路的前提下实现与第三方传感器结合呢?麦克风就是一个很好的ADC接口,通过麦克风与第三方传感器结合,再在软件里对模拟信号做相应的处理,就可以提供更丰富的传感化应用. 先来看看本文程序运行的效果图(屏幕录像截图的速度较慢,真机实际运行起来会很流畅): 本文程序使用8000

  • Android 模拟信号示波器示例代码

    上次简单地介绍了AudioRecord和AudioTrack的使用,这次就结合SurfaceView实现一个Android版的手机模拟信号示波器.最近物联网炒得很火,作为手机软件开发者,如何在不修改手机硬件电路的前提下实现与第三方传感器结合呢?麦克风就是一个很好的ADC接口,通过麦克风与第三方传感器结合,再在软件里对模拟信号做相应的处理,就可以提供更丰富的传感化应用. 先来看看本文程序运行的效果图(屏幕录像速度较慢,真机实际运行起来会更加流畅): 本文程序使用8000hz的采样率,对X轴方向绘图

  • Android提高Service优先级的方法分析

    本文实例讲述了Android提高Service优先级的方法.分享给大家供大家参考,具体如下: Android 系统对于内存管理有自己的一套方法,为了保障系统有序稳定的运信,系统内部会自动分配,控制程序的内存使用.当系统觉得当前的资源非常有限的时候,为了保 证一些优先级高的程序能运行,就会杀掉一些他认为不重要的程序或者服务来释放内存.这样就能保证真正对用户有用的程序仍然再运行.如果你的 Service 碰上了这种情况,多半会先被杀掉.但如果你增加 Service 的优先级就能让他多留一会,我们可以

  • Android提高之SurfaceView与多线程的混搭实例

    前文简单介绍了Android中SurfaceView的基本使用,本文就来介绍一下SurfaceView与多线程的混搭.SurfaceView与多线程混搭,是为了防止动画闪烁而实现的一种多线程应用.android的多线程用法与JAVA的多线程用法完全一样,本文不做多线程方面的介绍了.直接讲解SurfaceView与多线程的混合使用,即开一条线程专门读取图片,另外一条线程专门绘图. 本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图: 对比一下可以看出

  • Android提高之SurfaceView的基本用法实例分析

    前文介绍了Android中MediaPlayer用法的时候稍微介绍了SurfaceView,SurfaceView由于可以直接从内存或者DMA等硬件接口取得图像数据,因此是个非常重要的绘图容器,这次我就来较为详细的介绍SurfaceView的用法.网上介绍SurfaceView的用法有很多,写法也层出不同,例如继承SurfaceView类,或者继承SurfaceHolder.Callback类等,这个可以根据功能实际需要自己选择,本文所述方法就直接在普通的用户界面调用SurfaceHolder的

  • Android提高之蓝牙传感应用实例

    前面文章介绍了Android利用麦克风采集并显示模拟信号的实现方法,这种采集手段适用于无IO控制.单纯读取信号的情况.如果传感器本身需要包含控制电路(例如采集血氧信号需要红外和红外线交替发射),那么传感器本身就需要带一片主控IC,片内采集并输出数字信号了.Android手机如何在不改硬件电路的前提下与这类数字传感器交互呢?可选的通信方式就有USB和蓝牙,两种方式各有好处:USB方式可以给传感器供电,蓝牙方式要自备电源:USB接口标准不一,蓝牙普遍支持SPP协议.本文就选择蓝牙方式做介绍,介绍An

  • Android提高之使用NDK把彩图转换灰度图的方法

    一般而言在Android上使用JAVA实现彩图转换为灰度图,与J2ME上的实现方法类似,不过遇到频繁地转换或者是大图转换时,就必须使用NDK来提高速度了.本文主要通过JAVA和NDK这两种方式来分别实现彩图转换为灰度图,并给出速度的对比,供大家参考. 先来简单地介绍一下Android的NDK使用步骤: 以NDK r4为例,或许以后新版的NDK的使用方法略有不同. 1.下载支持C++的android-ndk-r4-crystax,支持C++的话可玩性更强. 2.下载cygwin,选择ftp://m

  • Android提高之Activity+Intent用法示例

    一般来说.熟悉Android程序设计的人都知道Android有三个基础组件Activity,Service和BroadcastReceiver,他们都是依赖Intent来启动.本文所要介绍的是Activity的生命周期以及针对Activity的Intent使用. 之前的例子一直都是使用Activity,在一个Layout XML与一个Activity捆绑的情况下可以视为一个Form,多个Layout XML与一个Activity捆绑的话那就是个Application本身了.Intent可以分为显

  • Android提高之MediaPlayer播放网络视频的实现方法

    前面讲解了MediaPlayer播放网络音频,主要介绍了MediaPlayer关于网络音频的缓冲和进度条控制的方法,本文再来讲解一下MediaPlayer播放网络视频的方法.播放网络视频比播放网络音频需要多一个SurfaceView而已,在已经熟悉了MediaPlayer播放网络音频之后,相信大家对本文所述的播放网络视频也能很快地掌握. 先来看看本文程序运行截图,如下所示: 本文程序的视频来自http://daily3gp.com,大家可以替换程序中的视频链接,试试其他影片. main.xml的

  • Android提高之MediaPlayer音视频播放

    前面文章已经详细介绍了Android界面的入门技术,相信大家在看完和跟着练习之后,会对于常用的Layout和View都会有一定的了解了,接下来就不再强调介绍界面了,而是针对具体的常见功能而展开. 本文将介绍MediaPlayer的使用.MediaPlayer可以播放音频和视频,另外也可以通过VideoView来播放视频,虽然VideoView比MediaPlayer简单易用,但定制性不如用MediaPlayer,这需要视情况选择.MediaPlayer播放音频比较简单,但是要播放视频就需要Sur

随机推荐