基于android的实时音频频谱仪

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

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

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

package com.mobao360.sunshine;
//复数的加减乘运算
public class Complex {
	public double real;
	public double image;
	
	//三个构造函数
	public Complex() {
		// TODO Auto-generated constructor stub
		this.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类,网上讲解这个类的蛮多的,搞清楚构造类的各个参数就可以。

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

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 exception
				Log.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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章