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

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

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

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

       main.xml源码如下:

XML/HTML代码

<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 android:layout_height="wrap_content" android:text="停止"
            android:id="@+id/btnExit" android:layout_width="80dip">
        <zoomcontrols android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/zctlX">
        <zoomcontrols android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/zctlY"> 

    <surfaceview android:id="@+id/SurfaceView01"
        android:layout_height="fill_parent" android:layout_width="fill_parent"> 

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 inBuf = new ArrayList();
    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 buf = new ArrayList();
                synchronized (inBuf) {
                    if (inBuf.size() == 0)
                        continue;
                    buf = (ArrayList) 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 重力传感器在游戏开发中的应用

    手势操作可以说是智能手机的一种魅力所在,前两节给大家讲解了两种有趣的手势操作,将它们置于游戏当中,大大提升了游戏的可玩性和趣味性.本节将继续介绍智能手机的另一种神奇之处:传感器.    一.何为传感器 所谓传感器就是能够探测如光.热.温度.重力.方向等等的装置.    二.Android提供了哪些传感器 1.加速度传感器(重力传感器) 2.陀螺仪传感器 3.光传感器 4.恒定磁场传感器 5.方向传感器 6.恒定的压力传感器 7.接近传感器 8.温度传感器 今天我们给大家介绍的是游戏开发中最最常见

  • Android中贝塞尔曲线的绘制方法示例代码

    贝塞尔曲线,很多人可能不太了解,什么叫做贝塞尔曲线呢?这里先做一下简单介绍:贝塞尔曲线也可以叫做贝济埃曲线或者贝兹曲线,它由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋.一般的矢量图形软件常利用贝塞尔曲线来精确画出曲线. 上面的介绍中,"线段像可伸缩的皮筋"这句话非常关键,但也特别好理解.至于贝塞尔曲线的详细内容大家可以查阅相关资料.        Android提供的贝塞尔曲线绘制接口 在Android开发中,要实现贝塞尔曲线其实还是很简单的,因为Android已经给我们提

  • Android View进行手势识别详解

    我们在进行Android游戏开发时会用到很多种控制,包括前面讲到的按键和轨迹球控制方式,除此之外还有手势操作.重力感应等多种控制方式需要了解掌握.本节主要为大家讲解在View中如何进行手势识别. 很多网友发现Android中手势识别提供了两个类,由于Android 1.6以下的版本比如cupcake中无法使用android.view.GestureDetector,而android.gesture.Gesture是Android 1.6开始支持的,考虑到仍然有使用Android 1.5固件的网友

  • Android 屏幕双击事件的捕获简单示例

    在Android游戏开发中,我们可能经常要像PC操作一样在屏幕上双击.对于屏幕双击操作,Android 1.6版本以前并没有提供完善的手势识别类,Android 1.5的SDK中提供了android.view.GestureDetector.OnDoubleTapListener,但经测试无法正常工作,不知是何原因.最终我们的解决方案如下面的代码: Java代码 public class TouchLayout extends RelativeLayout { public Handler do

  • Android开发实例之多点触控程序

    智能终端设备的多点触控操作为我们带来了种种炫酷体验,这也使得很多Android开发者都对多点触控程序的开发感兴趣.实际上多点触控程序的实现并不是那么遥不可及,而是比较容易.本文就主要通过一个实例具体讲解多点触控程序的实现.        首先来了解一下Android中多点触控的原理. Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouch技术,对于网页缩放.手

  • Android中图片的三级缓存机制

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存缓存中,缓存到强引用中 也就是LruCache中.如果强引用中空间不足,就会将较早存储的图片对象驱逐到软引用(softReference)中存储,然后将图片缓存到文件(内部存储外部存储)中:读取图片的时候,先读取内存缓存,判断强引用中是否存在图片,如果强引用中存在,则直接读取,如果强引用中不存在,则

  • Android 物理游戏之重力系统开发示例代码

    本节为大家提供有关物理游戏的知识,讲解了一个简单的圆形自由落体Demo的编写.本文要介绍的重力系统实际上是类似的. 在重力传感器中,虽然我也实现了一个圆形会根据手机反转的角度而拥有不同的速度,但是其内置加速度算法都是Android os封装好的,而今天我们要讲的重力系统就是去模拟这个加速度,从而让一个自由落体的圆形,感觉跟现实中的皮球一样有质有量!下落的时候速度加快,反弹起来以后速度慢慢减下来. 先贴上两张效果截图,让大家有一个直观的了解,之后再详加讲解: 圆形自由落体Demo简介 当你点击模拟

  • Android重力传感器实现滚动的弹球

    熟知: 什么是传感器: 所谓传感器能够探测如光.热.温度.重力.方向 等等的功能! Android中提供传感器有哪些: 1.  加速度传感器(重力传感器)      2.  陀螺仪传感器      3.  光传感器      5.  恒定磁场传感器      6.  方向传感器      7.  恒定的压力传感器      8.  接近传感器      9.  温度传感器 一. 问题描述 Android中有多达11种传感器,不同的手机设备支持的传感器类型也不尽相同 1. 重力传感器 GV-sen

  • Android开发之ScrollView的滑动监听

    我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部.可惜的是SDK并没有相应的方法,不过倒是提供了一个 protected void onScrollChanged(int l, int t, int oldl, int oldt) 显然这个方法是不能被外界调用的,因此就需要把它暴露出去,解决方式就是写一个接口 /** * Created by 刘楠 on 2016/8/21 0021.17:24 */ public interface ScrollViewL

  • Android SurfaceView运行机制剖析--处理切换到后台再重新进入程序时的异常

    有不少朋友都遇到过这种问题,程序执行时切换到后台,然后再重新进入会报异常,本文就这种问题全面讲解下SurfaceView的运行机制,了解了这些原理你就能自己解决这些问题了. 我们通常会通过单击HOME按键或返回按键等操作切换到后台,之后可能会再次进入程序,这个时候就有可能报异常.这里SurfaceView可能报的异常主要有两点,如下: 一.提交画布异常.如下图(模拟器错误提示,以及Logcat Detail) Java代码 public void draw() { try { canvas =

  • Android Service判断设备联网状态详解

    首先,要想获得当前android设备是否处于联网状态,那么android本身给我们提供了一个服务. private ConnectivityManager connectivityManager;//用于判断是否有网络 connectivityManager = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);//获取当前网络的连接服务 NetworkInfo info = co

随机推荐