基於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

詳細的看代碼吧,有什麼寫的詳細的可以留言

第一次寫技術文章,寫的不好,大家不要怪罪,將就着看把

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