前一段实习,本来打算做c++,到了公司发现没啥项目,于是乎转行做了android,写的第一个程序竟然要我处理信号,咱可是一心搞计算机的,没接触过信号的东西,什么都没接触过,于是乎, 找各种朋友,各种熟人,现在想想,专注语言是不对的,语言就是一工具,关键还是业务,算法。好了,废话不多说,上程序,注释都很详细,应该能看懂。

分析声音,其实很简单,就是运用傅里叶变换,将声音信号由时域转化到频域(程序用的是快速傅里叶变换,比较简单),为啥要这样,好处多多,不细讲,公司里的用处是为了检测手机发出声音的信号所在的频率集中范围。

第一个类,复数的计算,用到加减乘,很简单。

package com.mobao360.sunshine;//复数的加减乘运算public class Complex {public double real;public double image;//三个构造函数public Complex() {// TODO Auto-generated constructor stubthis.real = 0;this.image = 0;}public Complex(double real, double image){this.real = real;this.image = image;}public Complex(int real, int image) {Integer integer = real;this.real = integer.floatValue();integer = image;this.image = integer.floatValue();}public Complex(double real) {this.real = real;this.image = 0;}//乘法public Complex cc(Complex complex) {Complex tmpComplex = new Complex();tmpComplex.real = this.real * complex.real - this.image * complex.image;tmpComplex.image = this.real * complex.image + this.image * complex.real;return tmpComplex;}//加法public Complex sum(Complex complex) {Complex tmpComplex = new Complex();tmpComplex.real = this.real + complex.real;tmpComplex.image = this.image + complex.image;return tmpComplex;}//减法public Complex cut(Complex complex) {Complex tmpComplex = new Complex();tmpComplex.real = this.real - complex.real;tmpComplex.image = this.image - complex.image;return tmpComplex;}//获得一个复数的值public int getIntValue(){int ret = 0;ret = (int) Math.round(Math.sqrt(this.real*this.real - this.image*this.image));return ret;}}


这个类是有三个功能,第一,采集数据;第二,进行快速傅里叶计算;第三,绘图。

采集数据用AudioRecord类,网上讲解这个类的蛮多的,搞清楚构造类的各个参数就可以。

绘图用的是SurfaceViewPaintCanvas三个类,本人也是参考网络达人的代码

package com.mobao360.sunshine;import java.util.ArrayList;import java.lang.Short;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.DashPathEffect;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PathEffect;import android.graphics.Rect;import android.media.AudioRecord;import android.util.Log;import android.view.SurfaceView;public class AudioProcess {public static final float pi= (float) 3.1415926;//应该把处理前后处理后的普线都显示出来private ArrayList<short[]> inBuf = new ArrayList<short[]>();//原始录入数据private ArrayList<int[]> outBuf = new ArrayList<int[]>();//处理后的数据private boolean isRecording = false;Context mContext;private int shift = 30;public int frequence = 0;private int length = 256;//y轴缩小的比例public int rateY = 21;//y轴基线public int baseLine = 0;//初始化画图的一些参数public void initDraw(int rateY, int baseLine,Context mContext, int frequence){this.mContext = mContext;this.rateY = rateY;this.baseLine = baseLine;this.frequence = frequence;}//启动程序public void start(AudioRecord audioRecord, int minBufferSize, SurfaceView sfvSurfaceView) {isRecording = true;new RecordThread(audioRecord, minBufferSize).start();new DrawThread(sfvSurfaceView).start();}//停止程序public void stop(SurfaceView sfvSurfaceView){isRecording = false;inBuf.clear();}//录音线程class RecordThread extends Thread{private AudioRecord audioRecord;private int minBufferSize;public RecordThread(AudioRecord audioRecord,int minBufferSize){this.audioRecord = audioRecord;this.minBufferSize = minBufferSize;}public void run(){try{short[] buffer = new short[minBufferSize];audioRecord.startRecording();while(isRecording){int res = audioRecord.read(buffer, 0, minBufferSize);synchronized (inBuf){inBuf.add(buffer);}//保证长度为2的幂次数length=up2int(res);short[]tmpBuf = new short[length];System.arraycopy(buffer, 0, tmpBuf, 0, length);Complex[]complexs = new Complex[length];int[]outInt = new int[length];for(int i=0;i < length; i++){Short short1 = tmpBuf[i];complexs[i] = new Complex(short1.doubleValue());}fft(complexs,length);for (int i = 0; i < length; i++) {outInt[i] = complexs[i].getIntValue();}synchronized (outBuf) {outBuf.add(outInt);}}audioRecord.stop();}catch (Exception e) {// TODO: handle exceptionLog.i("Rec E",e.toString());}}}//绘图线程class DrawThread extends Thread{//画板private SurfaceView sfvSurfaceView;//当前画图所在屏幕x轴的坐标//画笔private Paint mPaint;private Paint tPaint;private Paint dashPaint;public DrawThread(SurfaceView sfvSurfaceView) {this.sfvSurfaceView = sfvSurfaceView;//设置画笔属性mPaint = new Paint();mPaint.setColor(Color.BLUE);mPaint.setStrokeWidth(2);mPaint.setAntiAlias(true);tPaint = new Paint();tPaint.setColor(Color.YELLOW);tPaint.setStrokeWidth(1);tPaint.setAntiAlias(true);//画虚线dashPaint = new Paint();dashPaint.setStyle(Paint.Style.STROKE);dashPaint.setColor(Color.GRAY);Path path = new Path();        path.moveTo(0, 10);        path.lineTo(480,10);         PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);        dashPaint.setPathEffect(effects);}@SuppressWarnings("unchecked")public void run() {while (isRecording) {ArrayList<int[]>buf = new ArrayList<int[]>();synchronized (outBuf) {if (outBuf.size() == 0) {continue;}buf = (ArrayList<int[]>)outBuf.clone();outBuf.clear();}//根据ArrayList中的short数组开始绘图for(int i = 0; i < buf.size(); i++){int[]tmpBuf = buf.get(i);SimpleDraw(tmpBuf, rateY, baseLine);}}}/**          * 绘制指定区域          *           * @param start          *            X 轴开始的位置(全屏)          * @param buffer          *             缓冲区          * @param rate          *            Y 轴数据缩小的比例          * @param baseLine          *            Y 轴基线          */ private void SimpleDraw(int[] buffer, int rate, int baseLine){Canvas canvas = sfvSurfaceView.getHolder().lockCanvas(new Rect(0, 0, buffer.length,sfvSurfaceView.getHeight()));canvas.drawColor(Color.BLACK);canvas.drawText("幅度值", 0, 3, 2, 15, tPaint);canvas.drawText("原点(0,0)", 0, 7, 5, baseLine + 15, tPaint);canvas.drawText("频率(HZ)", 0, 6, sfvSurfaceView.getWidth() - 50, baseLine + 30, tPaint);canvas.drawLine(shift, 20, shift, baseLine, tPaint);canvas.drawLine(shift, baseLine, sfvSurfaceView.getWidth(), baseLine, tPaint);canvas.save();canvas.rotate(30, shift, 20);canvas.drawLine(shift, 20, shift, 30, tPaint);canvas.rotate(-60, shift, 20);canvas.drawLine(shift, 20, shift, 30, tPaint);canvas.rotate(30, shift, 20);canvas.rotate(30, sfvSurfaceView.getWidth()-1, baseLine);canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);canvas.rotate(-60, sfvSurfaceView.getWidth()-1, baseLine);canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);canvas.restore();//tPaint.setStyle(Style.STROKE);for(int index = 64; index <= 512; index = index + 64){canvas.drawLine(shift + index, baseLine, shift + index, 40, dashPaint);String str = String.valueOf(frequence / 1024 * index);canvas.drawText( str, 0, str.length(), shift + index - 15, baseLine + 15, tPaint);}int y;for(int i = 0; i < buffer.length; i = i + 1){y = baseLine - buffer[i] / rateY ;canvas.drawLine(2*i + shift, baseLine, 2*i +shift, y, mPaint);}sfvSurfaceView.getHolder().unlockCanvasAndPost(canvas);}}/** * 向上取最接近iint的2的幂次数.比如iint=320时,返回256 * @param iint * @return */private int up2int(int iint) {int ret = 1;while (ret<=iint) {ret = ret << 1;}return ret>>1;}//快速傅里叶变换public void fft(Complex[] xin,int N){    int f,m,N2,nm,i,k,j,L;//L:运算级数    float p;    int e2,le,B,ip;    Complex w = new Complex();    Complex t = new Complex();    N2 = N / 2;//每一级中蝶形的个数,同时也代表m位二进制数最高位的十进制权值    f = N;//f是为了求流程的级数而设立的    for(m = 1; (f = f / 2) != 1; m++);                             //得到流程图的共几级    nm = N - 2;    j = N2;    /******倒序运算——雷德算法******/    for(i = 1; i <= nm; i++)    {        if(i < j)//防止重复交换        {            t = xin[j];            xin[j] = xin[i];            xin[i] = t;        }        k = N2;        while(j >= k)        {            j = j - k;            k = k / 2;        }        j = j + k;    }    /******蝶形图计算部分******/    for(L=1; L<=m; L++)                                    //从第1级到第m级    {    e2 = (int) Math.pow(2, L);        //e2=(int)2.pow(L);        le=e2+1;        B=e2/2;        for(j=0;j<B;j++)                                    //j从0到2^(L-1)-1        {            p=2*pi/e2;            w.real = Math.cos(p * j);            //w.real=Math.cos((double)p*j);                                   //系数W            w.image = Math.sin(p*j) * -1;            //w.imag = -sin(p*j);            for(i=j;i<N;i=i+e2)                                //计算具有相同系数的数据            {                ip=i+B;                                           //对应蝶形的数据间隔为2^(L-1)                t=xin[ip].cc(w);                xin[ip] = xin[i].cut(t);                xin[i] = xin[i].sum(t);            }        }    }}}

主程序

package com.mobao360.sunshine;import java.util.ArrayList;import android.app.Activity;import android.app.AlertDialog;import android.content.Context;import android.content.DialogInterface;import android.os.Bundle;import android.util.Log;import android.view.SurfaceView;import android.view.View;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.Spinner;import android.widget.TextView;import android.widget.Toast;import android.widget.ZoomControls;import android.media.AudioFormat;import android.media.AudioRecord;import android.media.MediaRecorder;public class AudioMaker extends Activity {    /** Called when the activity is first created. */    static  int frequency = 8000;//分辨率      static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;      static final int audioEncodeing = AudioFormat.ENCODING_PCM_16BIT;     static final int yMax = 50;//Y轴缩小比例最大值      static final int yMin = 1;//Y轴缩小比例最小值  int minBufferSize;//采集数据需要的缓冲区大小AudioRecord audioRecord;//录音AudioProcess audioProcess = new AudioProcess();//处理    Button btnStart,btnExit;  //开始停止按钮    SurfaceView sfv;  //绘图所用    ZoomControls zctlX,zctlY;//频谱图缩放    Spinner spinner;//下拉菜单    ArrayList<String> list=new ArrayList<String>();    ArrayAdapter<String>adapter;//下拉菜单适配器    TextView tView;        @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                initControl();        }    @Override    protected void onDestroy(){    super.onDestroy();    android.os.Process.killProcess(android.os.Process.myPid());    }      //初始化控件信息    private void initControl() {    //获取采样率        tView = (TextView)this.findViewById(R.id.tvSpinner);        spinner = (Spinner)this.findViewById(R.id.spinnerFre);        String []ls =getResources().getStringArray(R.array.action);        for(int i=0;i<ls.length;i++){        list.add(ls[i]);        }        adapter=new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,list);        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);        spinner.setAdapter(adapter);        spinner.setPrompt("请选择采样率");        spinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){        @SuppressWarnings("unchecked")        public void onItemSelected(AdapterView arg0,View agr1,int arg2,long arg3){        frequency = Integer.parseInt(adapter.getItem(arg2));        tView.setText("您选择的是:"+adapter.getItem(arg2)+"HZ");        Log.i("sunshine",String.valueOf(minBufferSize));        arg0.setVisibility(View.VISIBLE);        }        @SuppressWarnings("unchecked")        public void onNothingSelected(AdapterView arg0){        arg0.setVisibility(View.VISIBLE);        }        });                        Context mContext = getApplicationContext();        //按键        btnStart = (Button)this.findViewById(R.id.btnStart);        btnExit = (Button)this.findViewById(R.id.btnExit);        //按键事件处理        btnStart.setOnClickListener(new ClickEvent());        btnExit.setOnClickListener(new ClickEvent());        //画笔和画板        sfv = (SurfaceView)this.findViewById(R.id.SurfaceView01);        //初始化显示        audioProcess.initDraw(yMax/2, sfv.getHeight(),mContext,frequency);        //画板缩放        zctlY = (ZoomControls)this.findViewById(R.id.zctlY);        zctlY.setOnZoomInClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  if(audioProcess.rateY - 5>yMin){                audioProcess.rateY = audioProcess.rateY - 5;                  setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");                }else{                audioProcess.rateY = 1;                setTitle("原始尺寸");                }            }          });                    zctlY.setOnZoomOutClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  if(audioProcess.rateY<yMax){                audioProcess.rateY = audioProcess.rateY + 5;                      setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");                  }else {                setTitle("Y轴已经不能再缩小");}            }          });}        /**     * 按键事件处理     */    class ClickEvent implements View.OnClickListener{    @Override    public void onClick(View v){    Button button = (Button)v;    if(button == btnStart){    if(button.getText().toString().equals("Start")){                try {            //录音                    minBufferSize = AudioRecord.getMinBufferSize(frequency,                     channelConfiguration,                     audioEncodeing);                    //minBufferSize = 2 * minBufferSize;                     audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,frequency,                    channelConfiguration,                    audioEncodeing,                    minBufferSize);            audioProcess.baseLine = sfv.getHeight()-100;            audioProcess.frequence = frequency;            audioProcess.start(audioRecord, minBufferSize, sfv);            Toast.makeText(AudioMaker.this,             "当前设备支持您所选择的采样率:"+String.valueOf(frequency),             Toast.LENGTH_SHORT).show();            btnStart.setText(R.string.btn_exit);                    spinner.setEnabled(false);    } catch (Exception e) {    // TODO: handle exception            Toast.makeText(AudioMaker.this,             "当前设备不支持你所选择的采样率"+String.valueOf(frequency)+",请重新选择",             Toast.LENGTH_SHORT).show();    }        }else if (button.getText().equals("Stop")) {        spinner.setEnabled(true);    btnStart.setText(R.string.btn_start);    audioProcess.stop(sfv);    }    }    else {    new AlertDialog.Builder(AudioMaker.this)              .setTitle("提示")              .setMessage("确定退出?")              .setPositiveButton("确定", new DialogInterface.OnClickListener() {             public void onClick(DialogInterface dialog, int whichButton) {             setResult(RESULT_OK);//确定按钮事件  AudioMaker.this.finish();             finish();              }              })              .setNegativeButton("取消", new DialogInterface.OnClickListener() {             public void onClick(DialogInterface dialog, int whichButton) {              //取消按钮事件              }              })              .show();}        }    }}



程序源码下载地址: http://download.csdn.net/detail/sunshine_okey/3790484

详细的看代码吧,有什么写的详细的可以留言

第一次写技术文章,写的不好,大家不要怪罪,将就着看把

更多相关文章

  1. 一句话锁定MySQL数据占用元凶
  2. Android通过ksoap向webserice传递复杂类型数据
  3. android黑科技之读取用户短信+插入短信到系统短信数据库
  4. 在 SQL 数据库中保存数据
  5. Android之路——第一个上线 APP项目总结
  6. Android基于IIS的APK下载(三)用JSON传输更新数据
  7. 友盟2013年上半年数据报告:与开发者相关的各种干货数据
  8. 为什么Android应用用Java开发,为什么Android大型游戏要用数据包?这
  9. android显示RGB565数据图像

随机推荐

  1. Android横竖屏切换的解决方法
  2. Andrioid SystemProperties和Settings.Sy
  3. 【Android】Replace "..." with ellipsis
  4. android引入JAR包,打包成JAR包,打包成Libra
  5. android屏幕旋转时,Activity不重新调用onC
  6. android解析xml文档的各种方法
  7. 国内更新Android SDK 使用Android SDK Ma
  8. 我的Android进阶之旅------>Android拍照
  9. Button 按钮的几个属性
  10. 关于ADT(eclipse android)项目迁移到Androi