本文中实现录音时候、指针摆动的功能主要是参考SoundRecorder的。主要是其中的VUMeter类,VUMeter是通过Recorder.getMaxAmplitude()的值计算,画出指针的偏移摆动。
下面直接上代码:
VUMeter类:
package hfut.geron.record; import hfut.geron.memorybook.R; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; public class VUMeter extends View { static final float PIVOT_RADIUS = 3.5f; static final float PIVOT_Y_OFFSET = 10f; static final float SHADOW_OFFSET = 2.0f; static final float DROPOFF_STEP = 0.18f; static final float SURGE_STEP = 0.35f; static final long ANIMATION_INTERVAL = 70; Paint mPaint, mShadow; float mCurrentAngle; RecordHelper mRecorder; public VUMeter(Context context) { super(context); init(context); } public VUMeter(Context context, AttributeSet attrs) { super(context, attrs); init(context); } void init(Context context) { Drawable background = context.getResources().getDrawable(R.drawable.record_bg2); setBackgroundDrawable(background); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.WHITE); mShadow = new Paint(Paint.ANTI_ALIAS_FLAG); mShadow.setColor(Color.argb(60, 0, 0, 0)); mRecorder = null; mCurrentAngle = 0; } public void setRecorder(RecordHelper recorder) { mRecorder = recorder; invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final float minAngle = (float)Math.PI/8; final float maxAngle = (float)Math.PI*7/8; float angle = minAngle; if (mRecorder != null) angle += (float)(maxAngle - minAngle)*mRecorder.getMaxAmplitude()/32768; if (angle > mCurrentAngle) mCurrentAngle = angle; else mCurrentAngle = Math.max(angle, mCurrentAngle - DROPOFF_STEP); mCurrentAngle = Math.min(maxAngle, mCurrentAngle); float w = getWidth(); float h = getHeight(); float pivotX = w/2; float pivotY = h - PIVOT_RADIUS - PIVOT_Y_OFFSET; float l = h*4/5; float sin = (float) Math.sin(mCurrentAngle); float cos = (float) Math.cos(mCurrentAngle); float x0 = pivotX - l*cos; float y0 = pivotY - l*sin; canvas.drawLine(x0 + SHADOW_OFFSET, y0 + SHADOW_OFFSET, pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, mShadow); canvas.drawCircle(pivotX + SHADOW_OFFSET, pivotY + SHADOW_OFFSET, PIVOT_RADIUS, mShadow); canvas.drawLine(x0, y0, pivotX, pivotY, mPaint); canvas.drawCircle(pivotX, pivotY, PIVOT_RADIUS, mPaint); if (mRecorder != null) postInvalidateDelayed(ANIMATION_INTERVAL); } }
package hfut.geron.record; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import android.content.Context; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaRecorder; import android.util.Log; public class RecordHelper implements OnCompletionListener, OnErrorListener{ private MediaRecorder mRecorder ; private String record_path; public static final int IDLE_STATE = 0; //空闲状态 public static final int RECORDING_STATE = 1; //录音状态 int mState = IDLE_STATE; //状态标示,默认是空闲状态 public static final int NO_ERROR = 0; public static final int SDCARD_ACCESS_ERROR = 1; public static final int INTERNAL_ERROR = 2; public interface OnStateChangedListener { public void onStateChanged(int state); public void onError(int error); } OnStateChangedListener mOnStateChangedListener = null; public void setOnStateChangedListener(OnStateChangedListener listener) { mOnStateChangedListener = listener; } public void startRecording(int outputfileformat, String suffix, Context context) { Log.d("Infor", "start..."); stopRecording(); if(CommonUtils.isSdCardAvailable()){ File audioRecPath = new File(CommonUtils.RECORD_PATH); //判断存储音频的文件夹是否存在,如果不存在则进行创建操作 if(!audioRecPath.exists()){ audioRecPath.mkdir(); } if(audioRecPath != null ){ //创建临时文件,以Record_开头 SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMdd_hhmmss"); String record_name = "Record_"+sDateFormat.format(new java.util.Date()); File audioRecFile; try { audioRecFile = File.createTempFile(record_name, suffix, audioRecPath); } catch (IOException e) { // TODO Auto-generated catch block setError(SDCARD_ACCESS_ERROR); return; } mRecorder= new MediaRecorder(); //设置采样的音频源为麦克风 mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置输出文件格式 mRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); //设置音频的编码方式 mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //设置输出文件 Log.d("Infor", audioRecFile.getAbsolutePath()); record_path= audioRecFile.getAbsolutePath(); mRecorder.setOutputFile(audioRecFile.getAbsolutePath()); try{ mRecorder.prepare(); }catch(IOException e){ setError(INTERNAL_ERROR); mRecorder.reset(); mRecorder.release(); mRecorder = null; return; } //开始录音 if(this.mState==RecordHelper.IDLE_STATE){ mRecorder.start(); setState(RECORDING_STATE); } } } else{ Log.d("Infor", "error,内存卡打不开。。。"); setError(SDCARD_ACCESS_ERROR); } } public void stopRecording() { if (mRecorder == null) return; mRecorder.stop(); mRecorder.release(); mRecorder = null; setState(IDLE_STATE); } void setState(int state){//设置状态 if (state == mState) return; mState = state; signalStateChanged(mState); } public int getMaxAmplitude() {//得到录音的振幅 if (mState != RECORDING_STATE) return 0; return mRecorder.getMaxAmplitude(); } //实现对自定义接口OnStateChangedListener的两个方法调用的条件 private void signalStateChanged(int state) { if (mOnStateChangedListener != null) mOnStateChangedListener.onStateChanged(state); } private void setError(int error) { if (mOnStateChangedListener != null) mOnStateChangedListener.onError(error); } public String getPath(){ return record_path; } /*继承接口方法*/ public boolean onError(MediaPlayer mp, int what, int extra) { // TODO Auto-generated method stub return true; } public void onCompletion(MediaPlayer mp) { // TODO Auto-generated method stub } }
package hfut.geron.memorybook.onebook; import java.io.File; import hfut.geron.memorybook.R; import hfut.geron.record.CommonUtils; import hfut.geron.record.RecordHelper; import hfut.geron.record.RecordHelper.OnStateChangedListener; import hfut.geron.record.VUMeter; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; public class RecordActivity extends Activity implements OnClickListener, OnStateChangedListener { private TextView record_text_time; private ImageButton record_btn_begin,record_btn_end; private VUMeter mVUMeter;//VUMeter类是通过mRecorder.getMaxAmplitude()的值计算,画出指针的偏移摆动 WakeLock mWakeLock;//用WakeLock请求休眠锁,让其不会休眠 private String time; private RecordHelper mRecordHelper;//录音类,实现录音功能 private int s1=0; private int s2=0; private int m1=0; private int m2=0; private final static int RECORD_ISOVER = 1; private final static int SDCARD_ACCESS_ERROR=2; private final static int NOTES_RECORD=1; private Handler time_handler= new Handler(); private Runnable runnable = new Runnable(){ public void run() { // TODO Auto-generated method stub if(s1==10){ s2++; s1=0; } if(s2==6){ s2=0; m1++; } if(m1==10){ m1=0; m2++; } time=""+m2+m1+":"+s2+s1; record_text_time = (TextView) findViewById(R.id.record_text_time); record_text_time.setText(time); s1++; time_handler.postDelayed(runnable, 1000); } }; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); this.setContentView(R.layout.recordialog); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "SoundRecorder");//请求休眠锁 mRecordHelper = new RecordHelper(); mRecordHelper.setOnStateChangedListener(this); mVUMeter = (VUMeter)this.findViewById(R.id.record_VUMeter); mVUMeter.setRecorder(mRecordHelper); record_text_time = (TextView)this.findViewById(R.id.record_text_time); record_btn_begin = (ImageButton)this.findViewById(R.id.record_btn_begin); record_btn_end= (ImageButton)this.findViewById(R.id.record_btn_end); record_btn_begin.setOnClickListener(this); record_btn_end.setOnClickListener(this); record_btn_begin.setId(1); record_btn_end.setId(2); } public void onClick(View v) { // TODO Auto-generated method stub int id=v.getId(); switch(id){ case 1: //录音开始 time_handler.post(runnable); Log.d("Infor", "录音开始..."); mRecordHelper.startRecording(MediaRecorder.OutputFormat.DEFAULT, ".amr", this); record_btn_end.setClickable(true); record_btn_begin.setClickable(false); break; case 2: //录音结束 record_btn_begin.setClickable(true); time_handler.removeCallbacks(runnable); s1=s2=m1=m2=0; Log.d("Infor", "录音关闭..."); mRecordHelper.stopRecording(); showDialog(RECORD_ISOVER); break; } } public void onStateChanged(int state) { // TODO Auto-generated method stub Log.d("Infor", "录音状态发生改变"); if (state == RecordHelper.RECORDING_STATE) { mWakeLock.acquire(); // 录音的时候让其不休眠 } else { if (mWakeLock.isHeld()) mWakeLock.release(); } } public void onError(int error) { // TODO Auto-generated method stub Log.d("Infor", "error:"+error); this.showDialog(error); } @Override protected Dialog onCreateDialog(int id) { // TODO Auto-generated method stub switch(id){ case RECORD_ISOVER: Log.d("Infor", "here"); return CommonUtils.getDialog(this, "提示", "确定要保存该录音音频文件吗?", 0, "是", "否", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub Log.d("Infor", "保存该录音音频"); Toast.makeText(RecordActivity.this, "录音文件保存成功...", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(RecordActivity.this,NoteActivity.class); intent.putExtra("recordpath", mRecordHelper.getPath()); RecordActivity.this.setResult(NOTES_RECORD, intent); RecordActivity.this.finish(); } }, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub //删除之前的录音文件 Log.d("Infor", "取消保存该录音音频"); record_text_time.setText("00:00"); Log.d("Infor", "PATH:"+mRecordHelper.getPath()); File file = new File(mRecordHelper.getPath()); if(file.exists()){ Log.d("Infor", "存在"); try{ file.delete(); }catch(Exception e){ Log.d("Infor", "删除不了"); } } } }); case SDCARD_ACCESS_ERROR: return CommonUtils.getDialog(this, "提示", "无法读取内存卡..", 0, "是", null, new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub RecordActivity.this.finish(); } }, null); default: return null; } } }
要点:
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);