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