前一段實習,本來打算做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
詳細的看代碼吧,有什么寫的詳細的可以留言
第一次寫技術文章,寫的不好,大家不要怪罪,將就着看把