录音及音量


主要涉及类: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不可调换顺序 
添加许可: 
 
 
相机设置 
相关类: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(图像数据)。 
需要许可 
    
若要将图片存储至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.   
  3. import hfut.geron.memorybook.R;  
  4. import android.content.Context;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Color;  
  7. import android.graphics.Paint;  
  8. import android.graphics.drawable.Drawable;  
  9. import android.util.AttributeSet;  
  10. import android.view.View;  
  11.   
  12. public class VUMeter extends View {  
  13.     static final float PIVOT_RADIUS = 3.5f;  
  14.     static final float PIVOT_Y_OFFSET = 10f;  
  15.     static final float SHADOW_OFFSET = 2.0f;  
  16.     static final float DROPOFF_STEP = 0.18f;  
  17.     static final float SURGE_STEP = 0.35f;  
  18.     static final long  ANIMATION_INTERVAL = 70;  
  19.       
  20.     Paint mPaint, mShadow;  
  21.     float mCurrentAngle;  
  22.       
  23.     RecordHelper mRecorder;  
  24.   
  25.     public VUMeter(Context context) {  
  26.         super(context);  
  27.         init(context);  
  28.     }  
  29.   
  30.     public VUMeter(Context context, AttributeSet attrs) {  
  31.         super(context, attrs);  
  32.         init(context);  
  33.     }  
  34.   
  35.     void init(Context context) {  
  36.         Drawable background = context.getResources().getDrawable(R.drawable.record_bg2);  
  37.         setBackgroundDrawable(background);  
  38.           
  39.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  40.         mPaint.setColor(Color.WHITE);  
  41.         mShadow = new Paint(Paint.ANTI_ALIAS_FLAG);  
  42.         mShadow.setColor(Color.argb(60000));  
  43.           
  44.         mRecorder = null;  
  45.           
  46.         mCurrentAngle = 0;  
  47.     }  
  48.   
  49.     public void setRecorder(RecordHelper recorder) {  
  50.         mRecorder = recorder;  
  51.         invalidate();  
  52.     }  
  53.       
  54.     @Override  
  55.     protected void onDraw(Canvas canvas) {  
  56.         super.onDraw(canvas);  
  57.   
  58.         final float minAngle = (float)Math.PI/8;  
  59.         final float maxAngle = (float)Math.PI*7/8;  
  60.                   
  61.         float angle = minAngle;  
  62.         if (mRecorder != null)   
  63.             angle += (float)(maxAngle - minAngle)*mRecorder.getMaxAmplitude()/32768;  
  64.   
  65.         if (angle > mCurrentAngle)  
  66.             mCurrentAngle = angle;  
  67.         else  
  68.             mCurrentAngle = Math.max(angle, mCurrentAngle - DROPOFF_STEP);  
  69.   
  70.         mCurrentAngle = Math.min(maxAngle, mCurrentAngle);  
  71.   
  72.         float w = getWidth();  
  73.         float h = getHeight();  
  74.         float pivotX = w/2;  
  75.         float pivotY = h - PIVOT_RADIUS - PIVOT_Y_OFFSET;  
  76.         float l = h*4/5;  
  77.         float sin = (float) Math.sin(mCurrentAngle);  
  78.         float cos = (float) Math.cos(mCurrentAngle);  
  79.         float x0 = pivotX - l*cos;  
  80.         float y0 = pivotY - l*sin;  
  81.         canvas.drawLine(x0 + SHADOW_OFFSET, y0 + SHADOW_OFFSET, pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, mShadow);  
  82.         canvas.drawCircle(pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, PIVOT_RADIUS, mShadow);  
  83.         canvas.drawLine(x0, y0, pivotX, pivotY, mPaint);  
  84.         canvas.drawCircle(pivotX, pivotY, PIVOT_RADIUS, mPaint);  
  85.           
  86.         if (mRecorder != null)  
  87.             postInvalidateDelayed(ANIMATION_INTERVAL);  
  88.     }  
  89. }  

录音类:RecordHelper.java

 
[java]  view plain copy print ?
  1. package hfut.geron.record;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.text.SimpleDateFormat;  
  6. import android.content.Context;  
  7. import android.media.MediaPlayer;  
  8. import android.media.MediaPlayer.OnCompletionListener;  
  9. import android.media.MediaPlayer.OnErrorListener;  
  10. import android.media.MediaRecorder;  
  11. import android.util.Log;  
  12.   
  13. public class RecordHelper implements OnCompletionListener, OnErrorListener{  
  14.      private MediaRecorder mRecorder ;  
  15.      private String record_path;  
  16.      public static final int IDLE_STATE = 0;   //空闲状态  
  17.      public static final int RECORDING_STATE = 1//录音状态  
  18.      
  19.        
  20.      int mState = IDLE_STATE;   //状态标示,默认是空闲状态  
  21.        
  22.      public static final int NO_ERROR = 0;  
  23.      public static final int SDCARD_ACCESS_ERROR = 1;  
  24.      public static final int INTERNAL_ERROR = 2;  
  25.        
  26.      public interface OnStateChangedListener {  
  27.          public void onStateChanged(int state);  
  28.          public void onError(int error);  
  29.      }  
  30.      OnStateChangedListener mOnStateChangedListener = null;  
  31.      public void setOnStateChangedListener(OnStateChangedListener listener) {  
  32.          mOnStateChangedListener = listener;  
  33.      }  
  34.        
  35.      public void startRecording(int outputfileformat, String suffix, Context context) {  
  36.          Log.d("Infor""start...");  
  37.          stopRecording();  
  38.            
  39.          if(CommonUtils.isSdCardAvailable()){  
  40.              File audioRecPath = new File(CommonUtils.RECORD_PATH);  
  41.             //判断存储音频的文件夹是否存在,如果不存在则进行创建操作  
  42.              if(!audioRecPath.exists()){  
  43.                  audioRecPath.mkdir();  
  44.               
  45.              }  
  46.               
  47.              if(audioRecPath != null ){  
  48.                   //创建临时文件,以Record_开头  
  49.                  SimpleDateFormat   sDateFormat   =   new   SimpleDateFormat("yyyyMMdd_hhmmss");   
  50.                  String record_name   =   "Record_"+sDateFormat.format(new java.util.Date());   
  51.                  File audioRecFile;  
  52.                  try {  
  53.                     audioRecFile = File.createTempFile(record_name, suffix, audioRecPath);  
  54.                       
  55.                  } catch (IOException e) {  
  56.                         // TODO Auto-generated catch block  
  57.                      setError(SDCARD_ACCESS_ERROR);  
  58.                      return;  
  59.                     }  
  60.                    
  61.                    
  62.                     mRecorder= new MediaRecorder();  
  63.                     //设置采样的音频源为麦克风  
  64.                     mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);  
  65.                     //设置输出文件格式  
  66.                     mRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);  
  67.                     //设置音频的编码方式  
  68.                     mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);  
  69.                     //设置输出文件  
  70.                     Log.d("Infor", audioRecFile.getAbsolutePath());  
  71.                     record_path= audioRecFile.getAbsolutePath();  
  72.                     mRecorder.setOutputFile(audioRecFile.getAbsolutePath());  
  73.                    
  74.                       
  75.                    try{  
  76.                     mRecorder.prepare();                      
  77.                    }catch(IOException e){  
  78.                        setError(INTERNAL_ERROR);  
  79.                        mRecorder.reset();  
  80.                        mRecorder.release();  
  81.                        mRecorder = null;  
  82.                        return;  
  83.                    }  
  84.                     //开始录音  
  85.                     if(this.mState==RecordHelper.IDLE_STATE){  
  86.                     mRecorder.start();  
  87.                     setState(RECORDING_STATE);  
  88.                     }  
  89.                    
  90.              }  
  91.          }  
  92.            
  93.          else{  
  94.              Log.d("Infor""error,内存卡打不开。。。");  
  95.              setError(SDCARD_ACCESS_ERROR);  
  96.          }  
  97.            
  98.      }  
  99.       
  100.      public void stopRecording() {  
  101.             if (mRecorder == null)  
  102.                 return;  
  103.   
  104.             mRecorder.stop();  
  105.             mRecorder.release();  
  106.             mRecorder = null;  
  107.   
  108.             
  109.             setState(IDLE_STATE);  
  110.         }  
  111.        
  112.      void setState(int state){//设置状态  
  113.          if (state == mState)  
  114.                 return;           
  115.             mState = state;      
  116.             signalStateChanged(mState);  
  117.      }  
  118.        
  119.        
  120.      public int getMaxAmplitude() {//得到录音的振幅  
  121.             if (mState != RECORDING_STATE)  
  122.                 return 0;  
  123.             return mRecorder.getMaxAmplitude();  
  124.         }  
  125.        
  126.      //实现对自定义接口OnStateChangedListener的两个方法调用的条件  
  127.         private void signalStateChanged(int state) {  
  128.             if (mOnStateChangedListener != null)  
  129.                 mOnStateChangedListener.onStateChanged(state);  
  130.         }  
  131.           
  132.         private void setError(int error) {  
  133.             if (mOnStateChangedListener != null)  
  134.                 mOnStateChangedListener.onError(error);  
  135.         }  
  136.        
  137.           
  138.         public String getPath(){  
  139.             return record_path;  
  140.         }  
  141.        
  142.      /*继承接口方法*/  
  143.        
  144.     public boolean onError(MediaPlayer mp, int what, int extra) {  
  145.         // TODO Auto-generated method stub  
  146.           
  147.           
  148.           
  149.         return true;  
  150.     }  
  151.     public void onCompletion(MediaPlayer mp) {  
  152.         // TODO Auto-generated method stub  
  153.           
  154.     }  
  155.   
  156. }  

RecordActivity:

        
[java]  view plain copy print ?
  1. package hfut.geron.memorybook.onebook;  
  2.   
  3.   
  4.   
  5.   
  6.   
  7. import java.io.File;  
  8.   
  9. import hfut.geron.memorybook.R;  
  10. import hfut.geron.record.CommonUtils;  
  11. import hfut.geron.record.RecordHelper;  
  12. import hfut.geron.record.RecordHelper.OnStateChangedListener;  
  13. import hfut.geron.record.VUMeter;  
  14. import android.app.Activity;  
  15. import android.app.Dialog;  
  16. import android.content.Context;  
  17. import android.content.DialogInterface;  
  18. import android.content.Intent;  
  19. import android.media.MediaRecorder;  
  20. import android.os.Bundle;  
  21. import android.os.Handler;  
  22. import android.os.Message;  
  23. import android.os.PowerManager;  
  24. import android.os.PowerManager.WakeLock;  
  25. import android.util.Log;  
  26. import android.view.View;  
  27. import android.view.View.OnClickListener;  
  28. import android.widget.ImageButton;  
  29. import android.widget.TextView;  
  30. import android.widget.Toast;  
  31.   
  32. public class RecordActivity extends Activity implements OnClickListener, OnStateChangedListener {  
  33.     private TextView record_text_time;  
  34.     private ImageButton record_btn_begin,record_btn_end;  
  35.     private VUMeter mVUMeter;//VUMeter类是通过mRecorder.getMaxAmplitude()的值计算,画出指针的偏移摆动  
  36.     WakeLock mWakeLock;//用WakeLock请求休眠锁,让其不会休眠  
  37.     private String time;  
  38.     private RecordHelper mRecordHelper;//录音类,实现录音功能  
  39.       
  40.     private int s1=0;  
  41.     private int s2=0;  
  42.     private int m1=0;  
  43.     private int m2=0;  
  44.     private final static int RECORD_ISOVER = 1;  
  45.     private final static int SDCARD_ACCESS_ERROR=2;  
  46.       
  47.       
  48.     private final static int NOTES_RECORD=1;  
  49.     private Handler time_handler= new Handler();  
  50.     private Runnable runnable = new Runnable(){  
  51.   
  52.         public void run() {  
  53.             // TODO Auto-generated method stub  
  54.               
  55.             if(s1==10){  
  56.                 s2++;  
  57.                 s1=0;  
  58.             }  
  59.             if(s2==6){  
  60.                 s2=0;  
  61.                 m1++;  
  62.             }  
  63.             if(m1==10){  
  64.                 m1=0;  
  65.                 m2++;  
  66.             }  
  67.           
  68.             time=""+m2+m1+":"+s2+s1;  
  69.             record_text_time = (TextView) findViewById(R.id.record_text_time);  
  70.             record_text_time.setText(time);  
  71.             s1++;  
  72.             time_handler.postDelayed(runnable, 1000);  
  73.               
  74.         }  
  75.           
  76.     };  
  77.      
  78.       
  79.     @Override  
  80.     protected void onCreate(Bundle savedInstanceState) {  
  81.         // TODO Auto-generated method stub  
  82.         super.onCreate(savedInstanceState);  
  83.         this.setContentView(R.layout.recordialog);  
  84.   
  85.         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);  
  86.         mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,  
  87.                 "SoundRecorder");//请求休眠锁  
  88.           
  89.         mRecordHelper = new RecordHelper();  
  90.         mRecordHelper.setOnStateChangedListener(this);  
  91.           
  92.         mVUMeter = (VUMeter)this.findViewById(R.id.record_VUMeter);  
  93.         mVUMeter.setRecorder(mRecordHelper);  
  94.           
  95.         record_text_time = (TextView)this.findViewById(R.id.record_text_time);  
  96.         record_btn_begin = (ImageButton)this.findViewById(R.id.record_btn_begin);  
  97.           
  98.         record_btn_end= (ImageButton)this.findViewById(R.id.record_btn_end);  
  99.         record_btn_begin.setOnClickListener(this);  
  100.         record_btn_end.setOnClickListener(this);  
  101.         record_btn_begin.setId(1);  
  102.         record_btn_end.setId(2);  
  103.       
  104.           
  105.     }  
  106.     public void onClick(View v) {  
  107.         // TODO Auto-generated method stub  
  108.         int id=v.getId();  
  109.         switch(id){  
  110.         case 1:    //录音开始  
  111.             time_handler.post(runnable);  
  112.             Log.d("Infor""录音开始...");  
  113.               
  114.             mRecordHelper.startRecording(MediaRecorder.OutputFormat.DEFAULT,  
  115.                     ".amr"this);  
  116.             record_btn_end.setClickable(true);  
  117.             record_btn_begin.setClickable(false);  
  118.         break;  
  119.         case 2:    //录音结束  
  120.             record_btn_begin.setClickable(true);  
  121.             time_handler.removeCallbacks(runnable);  
  122.             s1=s2=m1=m2=0;  
  123.             Log.d("Infor""录音关闭...");  
  124.             mRecordHelper.stopRecording();  
  125.             showDialog(RECORD_ISOVER);  
  126.         break;  
  127.         }  
  128.     }  
  129.       
  130.       
  131.       
  132.     public void onStateChanged(int state) {  
  133.         // TODO Auto-generated method stub  
  134.         Log.d("Infor""录音状态发生改变");  
  135.         if (state == RecordHelper.RECORDING_STATE) {  
  136.             mWakeLock.acquire(); // 录音的时候让其不休眠  
  137.                                       
  138.         } else {  
  139.             if (mWakeLock.isHeld())  
  140.                 mWakeLock.release();  
  141.         }  
  142.           
  143.     }  
  144.     public void onError(int error) {  
  145.         // TODO Auto-generated method stub  
  146.         Log.d("Infor""error:"+error);  
  147.         this.showDialog(error);  
  148.     }  
  149.     @Override  
  150.     protected Dialog onCreateDialog(int id) {  
  151.         // TODO Auto-generated method stub  
  152.           
  153.         switch(id){  
  154.         case RECORD_ISOVER:  
  155.             Log.d("Infor""here");  
  156.             return CommonUtils.getDialog(this"提示""确定要保存该录音音频文件吗?"0,  
  157.                     "是""否"new DialogInterface.OnClickListener() {  
  158.                           
  159.                         public void onClick(DialogInterface dialog, int which) {  
  160.                             // TODO Auto-generated method stub  
  161.                             Log.d("Infor""保存该录音音频");  
  162.                             Toast.makeText(RecordActivity.this"录音文件保存成功...", Toast.LENGTH_SHORT).show();  
  163.                               
  164.                             Intent intent = new Intent(RecordActivity.this,NoteActivity.class);  
  165.                             intent.putExtra("recordpath", mRecordHelper.getPath());  
  166.                             RecordActivity.this.setResult(NOTES_RECORD, intent);  
  167.                             RecordActivity.this.finish();  
  168.                         }  
  169.                     }, new DialogInterface.OnClickListener() {  
  170.                           
  171.                         public void onClick(DialogInterface dialog, int which) {  
  172.                             // TODO Auto-generated method stub  
  173.                               
  174.                             //删除之前的录音文件  
  175.                             Log.d("Infor""取消保存该录音音频");  
  176.                             record_text_time.setText("00:00");  
  177.                             Log.d("Infor""PATH:"+mRecordHelper.getPath());  
  178.                             File file = new File(mRecordHelper.getPath());  
  179.                             if(file.exists()){  
  180.                                 Log.d("Infor""存在");  
  181.                                 try{  
  182.                                 file.delete();  
  183.                                 }catch(Exception e){  
  184.                                       
  185.                                     Log.d("Infor""删除不了");  
  186.                                 }  
  187.                             }                                                         
  188.                         }  
  189.                     });  
  190.               
  191.         case SDCARD_ACCESS_ERROR:  
  192.               
  193.             return CommonUtils.getDialog(this"提示""无法读取内存卡.."0"是"null,  new DialogInterface.OnClickListener(){  
  194.   
  195.                 public void onClick(DialogInterface dialog, int which) {  
  196.                     // TODO Auto-generated method stub  
  197.                 RecordActivity.this.finish();     
  198.                 }  
  199.                   
  200.             }, null);  
  201.               
  202.               
  203.         default:  
  204.             return null;  
  205.         }  
  206.     }  
  207. }  

结果截图:


要点:

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 * buffer;
                            }
                             
                            //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):

                

        然后,分开介绍两者的用法。

        《!--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),解释如下:

复制代码
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).
复制代码

然后看了一下小米录音机的源码,它其中就有一段代码是用来反映录音音量大小的,关键代码如下:

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

其中MAX_VU_SIZE是小米将音量分的等级数,然后可以将获得的振幅处以32768(关于这个数字,前面一段资料里面有解释),这样子就能获得音量所处的等级。








你可能感兴趣的:(android片段)