天天看點

錄音及音量 android手機的Mic對聲音的感覺 錄音getMaxAmplitude()

主要涉及類:MediaPlayer 

(1) 當一個MediaPlayer對象被建立或者調用reset()方法之後,它處于空閑狀态,調用release()方法後處于結束狀态 

1,一個MediaPlayer對象調用了reset()方法後,再調用其它方法可能會觸發OnErrorListener.onError()事件,未調用reset()方法則不會觸發 

2,當Mediaplayer對象不再被使用時,最好調用release()方法對其進行釋放,使其處于結束狀态,此時它不能被使用 

3,Mediaplayer對象被建立時(調用構造方法)處于空閑狀态,若使用create()方法建立後則處于準備狀态。 

(2) 一般情況下,一些常用的播放控制操作可能因為音頻、視訊的格式不被支援或者品質較差以及流逾時,也有可能由于開發者的疏忽使得Mediaplayer對象處于無效狀态等而導緻錯誤。此時可通過注冊setOnErrorListener方法實作監控。如果發生了錯誤,Mediaplayer對象将處于多霧狀态,可以使用reset()方法來回複錯誤。 

(3) 任何Mediaplayer對象都必須先處于準備狀态,然後才開始播放 

(4) 要開始播放Mediaplayer對象都必須成功調用start()方法,可通過isPlaying()方法來檢測是否正在播放 

(5) 當Mediaplayer對象在播放時,可以進行暫停和停止操作,pause()方法暫停播放,stop()方法停止播放。處于暫停暫停時可通過start()方法恢複播放,但是處于停止狀态時則必須先調用prepare()方法使其處于準備狀态,再調用start()方法。 

主要方法: 

Mediaplayer:構造方法 

create:        建立一個要播放的多媒體 

getCurrentPosition:得到目前播放位置 

getDuration:    得到檔案的時間 

prepare:    準備(同步) 

prepareAsync:準備(異步) 

seekTo:        指定播放的位置(以毫秒為機關) 

setAudioStreamType:    設定流媒體的類型 

setDataSource:    設定資料來源 

setDisplay:        設定用SurfaceHolder來顯示多媒體 

setOnBufferingUpdateListener:    網絡流媒體的緩沖監聽 

setOnErrorListener:            設定錯誤資訊監聽 

setOnVideoSizeChangedListener:視訊尺寸監聽 

setScreenOnWhilePlaying:        設定是否使用SurfaceHolder來顯示 

setVolume:                    設定音量 

//擷取sd卡上的音頻檔案 

setDataSource(“/sdcard/test.mp3”); 

//裝載資源中的音樂 

MediaPlayer.create(Activity01.this,R.raw.test); 

//目前存在問題,不能循環解析出音頻檔案 

原因:.android_secure檔案夾受保護,無法擷取裡面的檔案資訊 

播放視訊 

相關類:VideoView 

方法說明: 

getBufferPercentage:得到緩沖的百分比 

getCurrentPosition:得到目前播放位置 

getDuration:得到視訊檔案的時間 

resolveAdjustedSize:調整視訊顯示大小 

setMediaController:設定播放控制器模式(播放進度條) 

setOnCompletionListener:當視訊檔案播放完時觸發事件 

setVideoPath:設定視訊源路徑 

setVideoURI:設定視訊源位址 

錄音 

相關類:MediaRecorder 

方法說明: 

MediaRecorder:構造方法 

getMaxAmplitude:得到最大幅度 

setAudioEncoder:設定音頻編碼 

setAudioSource:設定音頻源 

setCamera:設定錄影機 

setMaxDuration:設定最長錄音時間 

setMaxFileSize:設定檔案的最大尺寸 

setOutputFile:設定輸出檔案 

setOutputFormat:設定輸出檔案格式 

setPreviewDisplay:設定預覽 

setVideoEncoder:設定視訊編碼 

setVideoFrameRate:設定視訊幀的頻率 

setVideoSize:設定視訊的寬度和高度(分辨率) 

setVideoSource:設定視訊源 

File類下的方法: 

public static File createTempFile(String prefix, String suffix, File directory) 

Creates an empty temporary file in the given directory using the given prefix and suffix as part of the file name. 

系統會自動在prefix和suffix之間加上一些數字來建構完整的檔案名 

實作錄音的一般步驟: 

1, 執行個體化MediaRecorder mr,調用構造方法 

2, 初始化mr:mr.setAudioSource(MIC)/setVideoSource(CAMERA) 

3, 配置DataSource:設定輸出檔案格式/路徑,編碼器等 

4, 準備錄制:mr.prepare() 

5, 開始錄制:mr.start() 

6, 停止錄制:mr.stop() 

7, 釋放資源:mr.release() 

注:2,3不可調換順序 

添加許可: 

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

<uses-permission android:name="android.permission.RECORD_AUDIO"> 

相機設定 

相關類:Camera,它是專門用來連接配接和斷開相機服務的類 

Camera的幾個事件: 

Camera.AutoFocusCallback:    自動調焦功能 

Camera.ErrorCallback:        錯誤資訊捕捉 

Camera.Parameters:        相機的屬性參數 

Camera.PictureCallback:    拍照、産生圖檔時觸發 

Camera.PreviewCallback:    相機預覽設定 

Camera.ShutterCallback:    快門設定 

Camera.Size:                圖檔的尺寸 

Camera類沒有構造方法,可通過open()方法來打開相機裝置 

Camera類的方法介紹: 

autoFocus:        設定自動對焦 

getParameters:    得到相機參數 

open:            啟動相機服務 

release:            釋放相機服務 

setParameters:    設定參數 

setPreviewDisplay:設定預覽 

startPreview:    開始預覽 

stopPreview:        停止預覽 

takePicture:        拍照 

注:takePicture方法要實作3個回調函數作為它的三個參數:Camera.ShutterCallback(快門),和兩個Camera.Picture.Callback(圖像資料)。 

需要許可 

<uses-permission android:name="android.permission.CAMERA" />    

若要将圖檔存儲至sd卡中,則需要sd卡讀寫許可 

目前存在問題:隻能拍照一次,不能重新回到預覽界面 

鬧鐘設定 

相關類:AlarmManager,它是專門用來設定在某個指定的時間去完成指定的事件。AlarmManager提供了通路系統警報的服務,隻要在程式中設定了警報服務,AlarmManager就會通過onReceive()方法去執行這些事件,就算系統處于待機狀态,同樣不會影響運作。可通過Context.getSystemService(ALARM_SERVICE)方法來獲得該服務。 

方法說明: 

cancel:    取消AlarmManager服務 

set:    設定AlarmManager服務 

setInexactRepeating:設定不精确周期 

setRepeating:設定精确周期 

setTimeZone:設定時區 

注:需建立一個BroadcastReceiver的子類,并覆寫onReceive()方法 

鈴聲設定 

系統自帶的鈴聲都放在/system/medio/audio/檔案夾中 

鈴音類型: TYPE_RINGTONE(來電鈴音),TYPE_ALARM,TYPE_NOTIFICATION 

相關類:RingtoneManager 

方法介紹: 

getActualDefaultRingtoneUri:取得指定類型的鈴聲 

getCursor:傳回所有可用鈴聲的遊标 

getDefaultType:得到指定URI預設的鈴聲類型 

getRingtone 

getRingtonePosition:得到鈴聲位置 

getRingtoneUri 

getValidRingtoneUri:得到一個可用鈴聲的URI 

isDefault:得到指定的Uri是否為預設的鈴聲 

setActualDefaultRingtoneUri:設定預設的鈴聲 

擷取的Cursor共有4列,列名依次為:_id,title,”content://media/internal/audio/media”,title_key 

以設定手機鈴音為例: 

if (isFolder(strRingtongFolder)) {//如果不存在該檔案夾則建立一個 

                // 打開系統鈴聲設定 

                Intent intent = new Intent( 

                        RingtoneManager.ACTION_RINGTONE_PICKER); 

                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, true); 

                // 類型為來電ringtong 

                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, 

                        RingtoneManager.TYPE_RINGTONE); 

                // 設定顯示的題目 

                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "設定來電的鈴聲"); 

                // 當設定完成之後傳回到目前的activity 

                startActivityForResult(intent, RingtongButton); 

            } 

然後複寫onActivityResult(int requestCode, int resultCode, Intent data)方法,resultCode就是點選設定dialog的按鈕編号,需要判斷是否點選了确認按鈕 

if (resultCode != RESULT_OK) { 

            return; 

        } 

            try { 

                // 得到我們選擇的鈴聲 

                Uri pickedUri = data                        .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); 

                // 将我們選擇的鈴聲選擇成預設 

                if (pickedUri != null) { 

                    RingtoneManager.setActualDefaultRingtoneUri( 

                            Media_RingTongActivity.this, 

                            RingtoneManager.TYPE_RINGTONE, pickedUri); 

                } 

            } catch (Exception e) { 

                e.printStackTrace(); 

            } 

本文中實作錄音時候、指針擺動的功能主要是參考SoundRecorder的。主要是其中的VUMeter類,VUMeter是通過Recorder.getMaxAmplitude()的值計算,畫出指針的偏移擺動。

下面直接上代碼:

VUMeter類:

  [java]  view plain copy print ?

  1. package hfut.geron.record;  
  2. import hfut.geron.memorybook.R;  
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.graphics.drawable.Drawable;  
  8. import android.util.AttributeSet;  
  9. import android.view.View;  
  10. public class VUMeter extends View {  
  11.     static final float PIVOT_RADIUS = 3.5f;  
  12.     static final float PIVOT_Y_OFFSET = 10f;  
  13.     static final float SHADOW_OFFSET = 2.0f;  
  14.     static final float DROPOFF_STEP = 0.18f;  
  15.     static final float SURGE_STEP = 0.35f;  
  16.     static final long  ANIMATION_INTERVAL = 70;  
  17.     Paint mPaint, mShadow;  
  18.     float mCurrentAngle;  
  19.     RecordHelper mRecorder;  
  20.     public VUMeter(Context context) {  
  21.         super(context);  
  22.         init(context);  
  23.     }  
  24.     public VUMeter(Context context, AttributeSet attrs) {  
  25.         super(context, attrs);  
  26.         init(context);  
  27.     }  
  28.     void init(Context context) {  
  29.         Drawable background = context.getResources().getDrawable(R.drawable.record_bg2);  
  30.         setBackgroundDrawable(background);  
  31.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  32.         mPaint.setColor(Color.WHITE);  
  33.         mShadow = new Paint(Paint.ANTI_ALIAS_FLAG);  
  34.         mShadow.setColor(Color.argb(60, 0, 0, 0));  
  35.         mRecorder = null;  
  36.         mCurrentAngle = 0;  
  37.     }  
  38.     public void setRecorder(RecordHelper recorder) {  
  39.         mRecorder = recorder;  
  40.         invalidate();  
  41.     }  
  42.     @Override  
  43.     protected void onDraw(Canvas canvas) {  
  44.         super.onDraw(canvas);  
  45.         final float minAngle = (float)Math.PI/8;  
  46.         final float maxAngle = (float)Math.PI*7/8;  
  47.         float angle = minAngle;  
  48.         if (mRecorder != null)   
  49.             angle += (float)(maxAngle - minAngle)*mRecorder.getMaxAmplitude()/32768;  
  50.         if (angle > mCurrentAngle)  
  51.             mCurrentAngle = angle;  
  52.         else  
  53.             mCurrentAngle = Math.max(angle, mCurrentAngle - DROPOFF_STEP);  
  54.         mCurrentAngle = Math.min(maxAngle, mCurrentAngle);  
  55.         float w = getWidth();  
  56.         float h = getHeight();  
  57.         float pivotX = w/2;  
  58.         float pivotY = h - PIVOT_RADIUS - PIVOT_Y_OFFSET;  
  59.         float l = h*4/5;  
  60.         float sin = (float) Math.sin(mCurrentAngle);  
  61.         float cos = (float) Math.cos(mCurrentAngle);  
  62.         float x0 = pivotX - l*cos;  
  63.         float y0 = pivotY - l*sin;  
  64.         canvas.drawLine(x0 + SHADOW_OFFSET, y0 + SHADOW_OFFSET, pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, mShadow);  
  65.         canvas.drawCircle(pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, PIVOT_RADIUS, mShadow);  
  66.         canvas.drawLine(x0, y0, pivotX, pivotY, mPaint);  
  67.         canvas.drawCircle(pivotX, pivotY, PIVOT_RADIUS, mPaint);  
  68.         if (mRecorder != null)  
  69.             postInvalidateDelayed(ANIMATION_INTERVAL);  
  70.     }  
  71. }  

錄音類:RecordHelper.java

  [java]  view plain copy print ?

  1. package hfut.geron.record;  
  2. import java.io.File;  
  3. import java.io.IOException;  
  4. import java.text.SimpleDateFormat;  
  5. import android.content.Context;  
  6. import android.media.MediaPlayer;  
  7. import android.media.MediaPlayer.OnCompletionListener;  
  8. import android.media.MediaPlayer.OnErrorListener;  
  9. import android.media.MediaRecorder;  
  10. import android.util.Log;  
  11. public class RecordHelper implements OnCompletionListener, OnErrorListener{  
  12.      private MediaRecorder mRecorder ;  
  13.      private String record_path;  
  14.      public static final int IDLE_STATE = 0;   //空閑狀态  
  15.      public static final int RECORDING_STATE = 1; //錄音狀态  
  16.      int mState = IDLE_STATE;   //狀态标示,預設是空閑狀态  
  17.      public static final int NO_ERROR = 0;  
  18.      public static final int SDCARD_ACCESS_ERROR = 1;  
  19.      public static final int INTERNAL_ERROR = 2;  
  20.      public interface OnStateChangedListener {  
  21.          public void onStateChanged(int state);  
  22.          public void onError(int error);  
  23.      }  
  24.      OnStateChangedListener mOnStateChangedListener = null;  
  25.      public void setOnStateChangedListener(OnStateChangedListener listener) {  
  26.          mOnStateChangedListener = listener;  
  27.      }  
  28.      public void startRecording(int outputfileformat, String suffix, Context context) {  
  29.          Log.d("Infor", "start...");  
  30.          stopRecording();  
  31.          if(CommonUtils.isSdCardAvailable()){  
  32.              File audioRecPath = new File(CommonUtils.RECORD_PATH);  
  33.             //判斷存儲音頻的檔案夾是否存在,如果不存在則進行建立操作  
  34.              if(!audioRecPath.exists()){  
  35.                  audioRecPath.mkdir();  
  36.              }  
  37.              if(audioRecPath != null ){  
  38.                   //建立臨時檔案,以Record_開頭  
  39.                  SimpleDateFormat   sDateFormat   =   new   SimpleDateFormat("yyyyMMdd_hhmmss");   
  40.                  String record_name   =   "Record_"+sDateFormat.format(new java.util.Date());   
  41.                  File audioRecFile;  
  42.                  try {  
  43.                     audioRecFile = File.createTempFile(record_name, suffix, audioRecPath);  
  44.                  } catch (IOException e) {  
  45.                         // TODO Auto-generated catch block  
  46.                      setError(SDCARD_ACCESS_ERROR);  
  47.                      return;  
  48.                     }  
  49.                     mRecorder= new MediaRecorder();  
  50.                     //設定采樣的音頻源為麥克風  
  51.                     mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);  
  52.                     //設定輸出檔案格式  
  53.                     mRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);  
  54.                     //設定音頻的編碼方式  
  55.                     mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);  
  56.                     //設定輸出檔案  
  57.                     Log.d("Infor", audioRecFile.getAbsolutePath());  
  58.                     record_path= audioRecFile.getAbsolutePath();  
  59.                     mRecorder.setOutputFile(audioRecFile.getAbsolutePath());  
  60.                    try{  
  61.                     mRecorder.prepare();                      
  62.                    }catch(IOException e){  
  63.                        setError(INTERNAL_ERROR);  
  64.                        mRecorder.reset();  
  65.                        mRecorder.release();  
  66.                        mRecorder = null;  
  67.                        return;  
  68.                    }  
  69.                     //開始錄音  
  70.                     if(this.mState==RecordHelper.IDLE_STATE){  
  71.                     mRecorder.start();  
  72.                     setState(RECORDING_STATE);  
  73.                     }  
  74.              }  
  75.          }  
  76.          else{  
  77.              Log.d("Infor", "error,記憶體卡打不開。。。");  
  78.              setError(SDCARD_ACCESS_ERROR);  
  79.          }  
  80.      }  
  81.      public void stopRecording() {  
  82.             if (mRecorder == null)  
  83.                 return;  
  84.             mRecorder.stop();  
  85.             mRecorder.release();  
  86.             mRecorder = null;  
  87.             setState(IDLE_STATE);  
  88.         }  
  89.      void setState(int state){//設定狀态  
  90.          if (state == mState)  
  91.                 return;           
  92.             mState = state;      
  93.             signalStateChanged(mState);  
  94.      }  
  95.      public int getMaxAmplitude() {//得到錄音的振幅  
  96.             if (mState != RECORDING_STATE)  
  97.                 return 0;  
  98.             return mRecorder.getMaxAmplitude();  
  99.         }  
  100.      //實作對自定義接口OnStateChangedListener的兩個方法調用的條件  
  101.         private void signalStateChanged(int state) {  
  102.             if (mOnStateChangedListener != null)  
  103.                 mOnStateChangedListener.onStateChanged(state);  
  104.         }  
  105.         private void setError(int error) {  
  106.             if (mOnStateChangedListener != null)  
  107.                 mOnStateChangedListener.onError(error);  
  108.         }  
  109.         public String getPath(){  
  110.             return record_path;  
  111.         }  
  112.     public boolean onError(MediaPlayer mp, int what, int extra) {  
  113.         // TODO Auto-generated method stub  
  114.         return true;  
  115.     }  
  116.     public void onCompletion(MediaPlayer mp) {  
  117.         // TODO Auto-generated method stub  
  118.     }  
  119. }  

RecordActivity:

         [java]  view plain copy print ?

  1. package hfut.geron.memorybook.onebook;  
  2. import java.io.File;  
  3. import hfut.geron.memorybook.R;  
  4. import hfut.geron.record.CommonUtils;  
  5. import hfut.geron.record.RecordHelper;  
  6. import hfut.geron.record.RecordHelper.OnStateChangedListener;  
  7. import hfut.geron.record.VUMeter;  
  8. import android.app.Activity;  
  9. import android.app.Dialog;  
  10. import android.content.Context;  
  11. import android.content.DialogInterface;  
  12. import android.content.Intent;  
  13. import android.media.MediaRecorder;  
  14. import android.os.Bundle;  
  15. import android.os.Handler;  
  16. import android.os.Message;  
  17. import android.os.PowerManager;  
  18. import android.os.PowerManager.WakeLock;  
  19. import android.util.Log;  
  20. import android.view.View;  
  21. import android.view.View.OnClickListener;  
  22. import android.widget.ImageButton;  
  23. import android.widget.TextView;  
  24. import android.widget.Toast;  
  25. public class RecordActivity extends Activity implements OnClickListener, OnStateChangedListener {  
  26.     private TextView record_text_time;  
  27.     private ImageButton record_btn_begin,record_btn_end;  
  28.     private VUMeter mVUMeter;//VUMeter類是通過mRecorder.getMaxAmplitude()的值計算,畫出指針的偏移擺動  
  29.     WakeLock mWakeLock;//用WakeLock請求休眠鎖,讓其不會休眠  
  30.     private String time;  
  31.     private RecordHelper mRecordHelper;//錄音類,實作錄音功能  
  32.     private int s1=0;  
  33.     private int s2=0;  
  34.     private int m1=0;  
  35.     private int m2=0;  
  36.     private final static int RECORD_ISOVER = 1;  
  37.     private final static int SDCARD_ACCESS_ERROR=2;  
  38.     private final static int NOTES_RECORD=1;  
  39.     private Handler time_handler= new Handler();  
  40.     private Runnable runnable = new Runnable(){  
  41.         public void run() {  
  42.             // TODO Auto-generated method stub  
  43.             if(s1==10){  
  44.                 s2++;  
  45.                 s1=0;  
  46.             }  
  47.             if(s2==6){  
  48.                 s2=0;  
  49.                 m1++;  
  50.             }  
  51.             if(m1==10){  
  52.                 m1=0;  
  53.                 m2++;  
  54.             }  
  55.             time=""+m2+m1+":"+s2+s1;  
  56.             record_text_time = (TextView) findViewById(R.id.record_text_time);  
  57.             record_text_time.setText(time);  
  58.             s1++;  
  59.             time_handler.postDelayed(runnable, 1000);  
  60.         }  
  61.     };  
  62.     @Override  
  63.     protected void onCreate(Bundle savedInstanceState) {  
  64.         // TODO Auto-generated method stub  
  65.         super.onCreate(savedInstanceState);  
  66.         this.setContentView(R.layout.recordialog);  
  67.         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);  
  68.         mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,  
  69.                 "SoundRecorder");//請求休眠鎖  
  70.         mRecordHelper = new RecordHelper();  
  71.         mRecordHelper.setOnStateChangedListener(this);  
  72.         mVUMeter = (VUMeter)this.findViewById(R.id.record_VUMeter);  
  73.         mVUMeter.setRecorder(mRecordHelper);  
  74.         record_text_time = (TextView)this.findViewById(R.id.record_text_time);  
  75.         record_btn_begin = (ImageButton)this.findViewById(R.id.record_btn_begin);  
  76.         record_btn_end= (ImageButton)this.findViewById(R.id.record_btn_end);  
  77.         record_btn_begin.setOnClickListener(this);  
  78.         record_btn_end.setOnClickListener(this);  
  79.         record_btn_begin.setId(1);  
  80.         record_btn_end.setId(2);  
  81.     }  
  82.     public void onClick(View v) {  
  83.         // TODO Auto-generated method stub  
  84.         int id=v.getId();  
  85.         switch(id){  
  86.         case 1:    //錄音開始  
  87.             time_handler.post(runnable);  
  88.             Log.d("Infor", "錄音開始...");  
  89.             mRecordHelper.startRecording(MediaRecorder.OutputFormat.DEFAULT,  
  90.                     ".amr", this);  
  91.             record_btn_end.setClickable(true);  
  92.             record_btn_begin.setClickable(false);  
  93.         break;  
  94.         case 2:    //錄音結束  
  95.             record_btn_begin.setClickable(true);  
  96.             time_handler.removeCallbacks(runnable);  
  97.             s1=s2=m1=m2=0;  
  98.             Log.d("Infor", "錄音關閉...");  
  99.             mRecordHelper.stopRecording();  
  100.             showDialog(RECORD_ISOVER);  
  101.         break;  
  102.         }  
  103.     }  
  104.     public void onStateChanged(int state) {  
  105.         // TODO Auto-generated method stub  
  106.         Log.d("Infor", "錄音狀态發生改變");  
  107.         if (state == RecordHelper.RECORDING_STATE) {  
  108.             mWakeLock.acquire(); // 錄音的時候讓其不休眠  
  109.         } else {  
  110.             if (mWakeLock.isHeld())  
  111.                 mWakeLock.release();  
  112.         }  
  113.     }  
  114.     public void onError(int error) {  
  115.         // TODO Auto-generated method stub  
  116.         Log.d("Infor", "error:"+error);  
  117.         this.showDialog(error);  
  118.     }  
  119.     @Override  
  120.     protected Dialog onCreateDialog(int id) {  
  121.         // TODO Auto-generated method stub  
  122.         switch(id){  
  123.         case RECORD_ISOVER:  
  124.             Log.d("Infor", "here");  
  125.             return CommonUtils.getDialog(this, "提示", "确定要儲存該錄音音頻檔案嗎?", 0,  
  126.                     "是", "否", new DialogInterface.OnClickListener() {  
  127.                         public void onClick(DialogInterface dialog, int which) {  
  128.                             // TODO Auto-generated method stub  
  129.                             Log.d("Infor", "儲存該錄音音頻");  
  130.                             Toast.makeText(RecordActivity.this, "錄音檔案儲存成功...", Toast.LENGTH_SHORT).show();  
  131.                             Intent intent = new Intent(RecordActivity.this,NoteActivity.class);  
  132.                             intent.putExtra("recordpath", mRecordHelper.getPath());  
  133.                             RecordActivity.this.setResult(NOTES_RECORD, intent);  
  134.                             RecordActivity.this.finish();  
  135.                         }  
  136.                     }, new DialogInterface.OnClickListener() {  
  137.                         public void onClick(DialogInterface dialog, int which) {  
  138.                             // TODO Auto-generated method stub  
  139.                             //删除之前的錄音檔案  
  140.                             Log.d("Infor", "取消儲存該錄音音頻");  
  141.                             record_text_time.setText("00:00");  
  142.                             Log.d("Infor", "PATH:"+mRecordHelper.getPath());  
  143.                             File file = new File(mRecordHelper.getPath());  
  144.                             if(file.exists()){  
  145.                                 Log.d("Infor", "存在");  
  146.                                 try{  
  147.                                 file.delete();  
  148.                                 }catch(Exception e){  
  149.                                     Log.d("Infor", "删除不了");  
  150.                                 }  
  151.                             }                                                         
  152.                         }  
  153.                     });  
  154.         case SDCARD_ACCESS_ERROR:  
  155.             return CommonUtils.getDialog(this, "提示", "無法讀取記憶體卡..", 0, "是", null,  new DialogInterface.OnClickListener(){  
  156.                 public void onClick(DialogInterface dialog, int which) {  
  157.                     // TODO Auto-generated method stub  
  158.                 RecordActivity.this.finish();     
  159.                 }  
  160.             }, null);  
  161.         default:  
  162.             return null;  
  163.         }  
  164.     }  
  165. }  

結果截圖:

錄音及音量 android手機的Mic對聲音的感覺 錄音getMaxAmplitude()

要點:

1、該程式中使用了用WakeLock請求休眠鎖,讓其不會休眠。這樣能保證錄音過程中螢幕常亮。

2、該程式中主要使用VUMeter,自定義該類,VUMeter類是通過mRecorder.getMaxAmplitude()的值計算,畫出指針的偏移擺動。

3、RecordHelper類中定義了一個接口OnStateChangedListener ,定義如下

public interface OnStateChangedListener {

         public void onStateChanged(int state);

         public void onError(int error);

     }

并在RecordHelper類中執行個體化該接口并set,如下:

     OnStateChangedListener mOnStateChangedListener = null;

         public void setOnStateChangedListener(OnStateChangedListener listener) {

               mOnStateChangedListener = listener;

     }

定義在何種情況下使用該接口中的兩個方法:

 //實作對自定義接口OnStateChangedListener的兩個方法調用的條件

   private void signalStateChanged(int state) {

       if (mOnStateChangedListener != null)

           mOnStateChangedListener.onStateChanged(state);

   }

   private void setError(int error) {

       if (mOnStateChangedListener != null)

           mOnStateChangedListener.onError(error);

   }

在RecordActivity類中就可以繼承該接口

 class RecordActivity extends Activity implements OnStateChangedListener{}

mRecordHelper = new RecordHelper();

mRecordHelper.setOnStateChangedListener(this);

獲得音量:

class RecordThread extends Thread {

            private AudioRecord ar;

            private int bs;

            private final int SAMPLE_RATE_IN_HZ = 8000;

            private boolean isRun = false;

            public RecordThread() {

                    super();

                    bs = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ,

                                    AudioFormat.CHANNEL_CONFIGURATION_MONO,

                                    AudioFormat.ENCODING_PCM_16BIT);

                    ar = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_IN_HZ,

                                    AudioFormat.CHANNEL_CONFIGURATION_MONO,

                                    AudioFormat.ENCODING_PCM_16BIT, bs);

            }

            public void run() {

                    super.run();

                    ar.startRecording();

                    // 用于讀取的

                   short[] buffer = new short[bs];

                    isRun = true;

                    while (isRun) {

                            int r = ar.read(buffer, 0, bs);

                            int v = 0;

                            // 将 buffer 内容取出,進行平方和運算

                            for (int i = 0; i < buffer.length; i++) {

                                    // 這裡沒有做運算的優化,為了更加清晰的展示代碼

                                    v += buffer<i> * buffer<i>;

                            }

                            //value 的 值 控制 為 0 到 100 之間 0為最小 》= 100為最大!!

                            int value = (int) (Math.abs((int)(v /(float)r)/10000) >> 1);

                            Log.d("111", "v = " + v);

                            // 平方和除以資料總長度,得到音量大小。可以擷取白噪聲值,然後對實際采樣進行标準化。

                            // 如果想利用這個數值進行操作,建議用 sendMessage 将其抛出,在 Handler 裡進行處理。

                            Log.d("222", String.valueOf(v / (float) r));

                            double dB = 10*Math.log10(v/(double)r);

                            Log.d("333", "dB = " + dB);

                            Message msg = new Message();

                            msg.what = 3;

                            msg.arg1 = value;

                            mHandler.sendMessage(msg);

                    }

                    ar.stop();

            }

            public void pause() {

                    // 在調用本線程的 Activity 的 onPause 裡調用,以便 Activity 暫停時釋放麥克風

                    isRun = false;

            }

            public void start() {

                    // 在調用本線程的 Activity 的 onResume 裡調用,以便 Activity 恢複後繼續擷取麥克風輸入音量

                    if (!isRun) {

                            super.start();

                    }

            }

    }

android手機的Mic對聲音的感覺

        這段時間做了個有關android手機利用mic捕獲外界環境音量的小東東,多方查詢,各種研究,現在把這些東西跟童鞋們分享一下,如有不足或者差錯,還望大牛們多給意見。

        android提供可以實作錄音功能的有AudioRecord和MediaRecorder,其中AudioRecord是讀取Mic的音頻流,可以邊錄音邊分析流的資料;而MediaRecorder則能夠直接把Mic的資料存到檔案,并且能夠進行編碼(如AMR,MP3等)。

        首先,要将你的應用加入權限(無論你是使用AudioRecord還是MediaRecorder):

                 <uses-permission android:name="android.permission.RECORD_AUDIO" />

        然後,分開介紹兩者的用法。

        《!--AudioRecord--》

        1、建立錄音采樣類,實作接口:

             public class MicSensor implements AudioRecord.OnRecordPositionUpdateListener

        2、關于AudioRecord的初始化:

             public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

             audioSource:              錄音源(例如:MediaRecorder.AudioSource.MIC    指定Mic為錄音源)

             sampleRateInHz:        預設的采樣頻率,機關為Hz。(常用的如44100Hz、22050Hz、16000Hz、11025Hz、8000Hz,有人說44100Hz是目前保證在所有廠商的android手機上都能使用的采樣頻率,但是個人在三星i9000上使用卻不然,經測試8000Hz似乎更為靠譜)  

            channelConfig:           描述音頻通道設定。(在此我使用了AudioFormat.CHANNEL_CONFIGURATION_MONO)

            audioFormat:              音頻資料支援格式。(這個好像跟聲道有關,16bit的脈碼調制錄音應該是所謂的雙聲道,而8bit脈碼調制錄音是單聲道。AudioFormat.ENCODING_PCM_16BIT、AudioFormat.ENCODING_PCM_8BIT)

           bufferSizeInBytes:        在錄制過程中,音頻資料寫入緩沖區的總數(位元組)。 從緩沖區讀取的新音頻資料總會小于此值。 getMinBufferSize(int, int, int)傳回AudioRecord 執行個體建立成功後的最小緩沖區。 設定的值比getMinBufferSize()還小則會導緻初始化失敗。

       3、初始化成功後則可啟動錄音    audioRecord.startRecording()

       4、編寫線程類将錄音資料讀入緩沖區,進行分析

             short[] buffer = new short[bufferSize];              //short類型對應16bit音頻資料格式,byte類型對應于8bit

             audioRecord.read(buffer, 0, bufferSize);            //傳回值是個int類型的資料長度值

      5、在此需要對buffer中的資料進行一些說明:

           這樣讀取的資料是在時域下的資料,直接用于計算沒有任何實際意義。需要将時域下的資料轉化為頻域下的資料,才能訴諸于計算。

           頻域(frequency domain)是指在對函數或信号進行分析時,分析其和頻率有關部份,而不是和時間有關的部份。

           函數或信号可以透過一對數學的運算子在時域及頻域之間轉換。例如傅裡葉變換可以将一個時域信号轉換成在不同頻率下對應的振幅及相位,其頻譜就是時域信号在頻域下的表現,而反傅裡葉變換可以将頻譜再轉換回時域的信号。

           信号在時域下的圖形可以顯示信号如何随着時間變化,而信号在頻域下的圖形(一般稱為頻譜)可以顯示信号分布在哪些頻率及其比例。頻域的表示法除了有各個頻率下的大小外,也會有各個頻率的相位,利用大小及相位的資訊可以将各頻率的弦波給予不同的大小及相位,相加以後可以還原成原始的信号。

           經傅立葉變化後得到的複數數組是個二維數組,實部和虛部的平方和取對數後乘以10就大緻等于我們通常表示音量的分貝了。

       《!--MediaRecorder--》

         相對于AudioRecord,MediaRecorder提供了更為簡單的api。

[java]  view plain copy

  1.               mediaRecorder = new MediaRecorder();  
  2. mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);  
  3. mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);  
  4. mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);  
  5. mediaRecorder.setOutputFile("/dev/null");    

         設定好mediaRecorder的各個屬性,然後通過線程調用方法  mediaRecorder.getMaxAmplitude();

            得到的是瞬時的最大振幅,直接取對數然後乘以10就可以表征分貝了。

            最後需要說明一下,android手機廠商定制的硬體不盡相同,是以mic擷取的值也隻能“表征”,而不能拿過來當真正的依據。它們雖是智能手機,但也還是手機,機器人不是人!呵呵。。。

            對了,每個手機mic在聲信号和電信号進行轉換時都有做過電容保護,為了其不因外界環境的過于嘈雜而易受到損壞。是以超音波和次聲波,我們人不容易接受的聲音,手機也不會入耳的。

錄音getMaxAmplitude()

這個方法是用來擷取在前一次調用此方法之後錄音中出現的最大振幅,文檔解釋如下:

Returns the maximum absolute amplitude that was sampled since the last call to this method. Call this only after the setAudioSource().

很多人遇到問題,說是傳回值為0,文檔中解釋如下:the maximum absolute amplitude measured since the last call, or 0 when called for the first time。

是以這個方法是需要間隔一段時間調用一次的,也就是說,需要放線上程裡面調用的。第一次調用會傳回0。

最近需要使用這個方法擷取音量的變化,對其傳回值很好奇,查了一些資料(from stackoverflow),解釋如下:

錄音及音量 android手機的Mic對聲音的感覺 錄音getMaxAmplitude()
The MediaRecorder.getMaxAmplitude() function returns unsigned 16-bit integer values (0-32767). Which is probably just the abs() of the CD-quality sample values that range from -32768 to 32767. This means that they probably represent a 16-bit digitalization of the electrical output from 0-100% maximum voltage range of the microphone build into that mobile phone. Since even in one brand of mobile these microphones sometimes vary in their precise range not even to similar phones will necessarily return the same value given the same distance to the same sound source.

This value however correlates to sound pressure in Pascal since it's also a linear quantisation of the solund pressure, in the area where sound can be measured with the given microphone (which will not cover the entire sprectrum due to the limitations of the phone).      
錄音及音量 android手機的Mic對聲音的感覺 錄音getMaxAmplitude()

然後看了一下小米錄音機的源碼,它其中就有一段代碼是用來反映錄音音量大小的,關鍵代碼如下:

1 int vuSize = MAX_VU_SIZE * mRecorder.getMaxAmplitude() / 32768;      

其中MAX_VU_SIZE是小米将音量分的等級數,然後可以将獲得的振幅處以32768(關于這個數字,前面一段資料裡面有解釋),這樣子就能獲得音量所處的等級。