Android錄音有MediaRecorder和AudioRecord兩種方式,前者使用方便,可以直接生成錄音文件,但是錄音格式為aac和amr等等,都經過壓縮處理,不方便進行音頻分析。
而用AudioRecord可以得到PCM編碼的原音頻數據,可以用FFT對數據進行處理,簡單分析聲音的頻率。
1.AndroidRecord錄音
private static final String FILE_NAME = "MainMicRecord"; private static final int SAMPLE_RATE = 44100;//Hz,采樣頻率 private static final double FREQUENCY = 500; //Hz,標准頻率(這里分析的是500Hz) private static final double RESOLUTION = 10; //Hz,誤差 private static final long RECORD_TIME = 2000; private File mSampleFile; private int bufferSize=0; private AudioRecord mAudioRecord; private void startRecord() { try { mSampleFile = new File(getFilesDir()+"/"+FILE_NAME); if(mSampleFile.exists()){ if(!mSampleFile.delete()){ return; } } if(!mSampleFile.createNewFile()){ return; } } catch(IOException e) { return; } //為了方便,這里只錄制單聲道 //如果是雙聲道,得到的數據是一左一右,注意數據的保存和處理 bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); mAudioRecord.startRecording(); new Thread(new AudioRecordThread()).start(); } private class AudioRecordThread implements Runnable{ @Override public void run() { //將錄音數據寫入文件 short[] audiodata = new short[bufferSize/2]; DataOutputStream fos = null; try { fos = new DataOutputStream( new FileOutputStream(mSampleFile)); int readSize; while (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){ readSize = mAudioRecord.read(audiodata,0,audiodata.length); if(AudioRecord.ERROR_INVALID_OPERATION != readSize){ for(int i = 0;i<readSize;i++){ fos.writeShort(audiodata[i]); fos.flush(); } } } } catch (IOException e) { e.printStackTrace(); }finally { if(fos!=null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } //在這里release mAudioRecord.release(); mAudioRecord = null; } } }; //在這里stop的時候先不要release private void stopRecording() { mAudioRecord.stop(); } //對錄音文件進行分析 private void frequencyAnalyse(){ if(mSampleFile == null){return; } try { DataInputStream inputStream = new DataInputStream(new FileInputStream(mSampleFile)); //16bit采樣,因此用short[] //如果是8bit采樣,這里直接用byte[] //從文件中讀出一段數據,這里長度是SAMPLE_RATE,也就是1s采樣的數據 short[] buffer=new short[SAMPLE_RATE]; for(int i = 0;i<buffer.length;i++){ buffer[i] = inputStream.readShort(); } short[] data = new short[FFT.FFT_N]; //為了數據穩定,在這里FFT分析只取最后的FFT_N個數據 System.arraycopy(buffer, buffer.length - FFT.FFT_N, data, 0, FFT.FFT_N); //FFT分析得到頻率 double frequence = FFT.GetFrequency(data); if(Math.abs(frequence - FREQUENCY)<RESOLUTION){ //測試通過 }else{ //測試失敗 } } catch (IOException e) { e.printStackTrace(); } }
2.FFT實現
參考:http://introcs.cs.princeton.edu/java/97data/FFT.java.html
(1)復數類

1 public class Complex { 2 3 public double real, imag; 4 5 public Complex(double real,double im){ 6 this.real = real; 7 this.imag = im; 8 } 9 10 public Complex(){ 11 this(0,0); 12 } 13 14 public Complex(Complex c){ 15 this(c.real,c.imag); 16 } 17 18 @Override 19 public String toString() { 20 return "("+this.real+"+"+this.imag +"i)"; 21 } 22 23 //加法 24 public final Complex add(Complex c){ 25 return new Complex(this.real+c.real,this.imag +c.imag); 26 } 27 28 //減法 29 public final Complex minus(Complex c){ 30 return new Complex(this.real-c.real,this.imag -c.imag); 31 } 32 33 //求模值 34 public final double getMod(){ 35 return Math.sqrt(this.real * this.real+this.imag * this.imag); 36 } 37 38 //乘法 39 public final Complex multiply(Complex c){ 40 return new Complex( 41 this.real*c.real - this.imag *c.imag, 42 this.real*c.imag + this.imag *c.real); 43 } 44 }
(2)FFT求最大頻率
public class FFT { public static final int FFT_N = 4096; public static final int SAMPLE_RATE = 44100; //HZ //快速傅里葉變換 public static Complex[] getFFT(Complex[] data){ int N = data.length; if(N==1){ return new Complex[]{data[0]}; } if(N%2 != 0){ throw new RuntimeException("N is not a power of 2"); } //fft of even/odd terms Complex[] even = new Complex[N/2]; Complex[] odd = new Complex[N/2]; for(int k = 0;k<N/2;k++){ even[k] = data[2*k]; odd[k] = data[2*k+1]; } Complex[] q= getFFT(even); Complex[] r = getFFT(odd); Complex[] y = new Complex[N]; for (int k = 0;k<N/2;k++){ double kth = -2*k*Math.PI/N; Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); y[k] = q[k].add(wk.multiply(r[k])); y[k+N/2] = q[k].minus(wk.multiply(r[k])); } return y; } //================================================================ public static double GetFrequency(short[] data){ Log.i("FFT","GetFrequency"); if(data.length<FFT_N){ throw new RuntimeException("Data length lower than "+FFT_N); } Complex[] f = new Complex[FFT_N]; for(int i=0;i<FFT_N;i++){ f[i] = new Complex(data[i],0); //實部為正弦波FFT_N點采樣,賦值為1 //虛部為0 } f = getFFT(f); //進行快速福利葉變換 // String str = ""; // for(int i = 0;i<FFT_N;i++){ // str+=f[i].toString()+" "; // } // Log.i("FFT","fft: "+str); double[] s = new double[FFT_N/2]; // str = ""; for(int i=0;i<FFT_N/2;i++){ s[i] = f[i].getMod(); // str += ""+s[i]+" "; } // Log.i("FFT","s: "+str); int fmax=0; for(int i=1;i<FFT_N/2;i++){ //利用FFT的對稱性,只取前一半進行處理 if(s[i]>s[fmax]) fmax=i; //計算最大頻率的序號值 } // Log.i("FFT","max index:"+fmax+" fft:"+f[fmax]+" s:"+s[fmax]); double fre = fmax*(double)SAMPLE_RATE / FFT_N; Log.i("FFT","fre:"+fre); return fre; } }