首页 > 代码库 > AndroidFM模块学习之录音功能
AndroidFM模块学习之录音功能
前些天分析了一下FM的流程以及主要类,接下来我们分析一下FM的录音功能;
首先看下流程图:
Fm录音时,当点击了录音按钮,会发一个广播出去,源码在FMRadioService.java中
<span style="font-family:KaiTi_GB2312;font-size:18px;"> public void startRecording() { Log.d(LOGTAG, "In startRecording of Recorder"); if ((true == mSingleRecordingInstanceSupported) && (true == mOverA2DP )) { Toast.makeText( this, "playback on BT in progress,can't record now", Toast.LENGTH_SHORT).show(); return; } sendRecordIntent(RECORD_START); }</span>
State状态控制FMRecordingService.java类service启动与关闭
if (state == 1) {
Log.d(TAG,"FM ONintent received");
startService =true;
context.startService(in);
}elseif(state == 0){
Log.d(TAG,"FM OFFintent received");
startService =false;
context.stopService(in);
}
Fm广播接收的action publicstaticfinal StringACTION_FM = "codeaurora.intent.action.FM";
当FMRadioservice类的private void sendRecordServiceIntent(int action)方法发送一个广播并附带一个开关录音的状态int值
<span style="font-family:KaiTi_GB2312;font-size:18px;">private void sendRecordServiceIntent(int action) { Intent intent = new Intent(ACTION_FM); intent.putExtra("state", action); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); Log.d(LOGTAG, "Sending Recording intent for = " +action); getApplicationContext().sendBroadcast(intent); }</span>
State状态控制FMRecordingService.java类service启动与关闭
<span style="font-family:KaiTi_GB2312;font-size:18px;">public class FMRecordingReceiver extends BroadcastReceiver { private static final String TAG = "FMRecordingReceiver"; public static final String ACTION_FM = "codeaurora.intent.action.FM"; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG, "Received intent: " + action); if((action != null) && action.equals(ACTION_FM)) { Log.d(TAG, "FM intent received"); Intent in = new Intent(); in.putExtras(intent); in.setClass(context, FMRecordingService.class); int state = intent.getIntExtra("state", 0); boolean startService = true; if (state == 1) {Log.d(TAG, "FM ON intent received"); startService = true; context.startService(in); } else if(state == 0){ Log.d(TAG, "FM OFF intent received"); startService = false; context.stopService(in); } } } } </span>
Fm接收广播的action
<span style="font-family:KaiTi_GB2312;font-size:18px;"> public static final String ACTION_FM_RECORDING = "codeaurora.intent.action.FM_Recording"; public static final String ACTION_FM_RECORDING_STATUS = "codeaurora.intent.action.FM.Recording.Status";</span>
onCreat()方法里注册广播接受机制,一个广播是录音状态,一个是关闭fm状态
<span style="font-family:KaiTi_GB2312;font-size:18px;">public void onCreate() { super.onCreate(); Log.d(TAG, "FMRecording Service onCreate"); registerRecordingListner(); registerShutdownListner(); registerStorageMediaListener(); }</span>
onDestroy()方法里写了卸载注册停止录音
public void onDestroy() { Log.d(TAG, "FMRecording Service onDestroy"); if (mFmRecordingOn == true) { Log.d(TAG, "Still recording on progress, Stoping it"); stopRecord(); } unregisterBroadCastReceiver(mFmRecordingReceiver); unregisterBroadCastReceiver(mFmShutdownReceiver); unregisterBroadCastReceiver(mSdcardUnmountReceiver); super.onDestroy(); }
stopRecord();停止录音
private void stopRecord() { Log.d(TAG, "Enter stopRecord"); mFmRecordingOn = false; if (mRecorder == null) return; try { mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; } catch(Exception e) { e.printStackTrace(); } sendRecordingStatusIntent(STOP); saveFile(); stopForeground(true); stopClientStatusCheck(); }
registerShutdownListner();注册接受方法中停止fm录音
private void registerShutdownListner() { if (mFmShutdownReceiver == null) { mFmShutdownReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent " +intent); String action = intent.getAction(); Log.d(TAG, " action = " +action); if (action.equals("android.intent.action.ACTION_SHUTDOWN")) { Log.d(TAG, "android.intent.action.ACTION_SHUTDOWN Intent received"); stopRecord(); } } }; IntentFilter iFilter = new IntentFilter(); iFilter.addAction("android.intent.action.ACTION_SHUTDOWN"); registerReceiver(mFmShutdownReceiver, iFilter); } }
获取sd卡有效空间
private static long getAvailableSpace() { String state = Environment.getExternalStorageState(); Log.d(TAG, "External storage state=" + state); if (Environment.MEDIA_CHECKING.equals(state)) { return PREPARING; } if (!Environment.MEDIA_MOUNTED.equals(state)) { return UNAVAILABLE; } try { File sampleDir = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(sampleDir.getAbsolutePath()); return stat.getAvailableBlocks() * (long) stat.getBlockSize(); } catch (Exception e) { Log.i(TAG, "Fail to access external storage", e); } return UNKNOWN_SIZE; }
主要通过以下方法实现:
FilesampleDir = Environment.getExternalStorageDirectory();
StatFs stat = newStatFs(sampleDir.getAbsolutePath());
return stat.getAvailableBlocks() *(long) stat.getBlockSize();
有四种值:
Environment.MEDIA_CHECKING检查sd卡准备读取
Environment.MEDIA_MOUNTED没有sd卡
UNKNOWN_SIZE sd卡有效空间等于UNKNOWN_SIZE值
LOW_STORAGE_THRESHOLD低于存储空间极限值
更新显示存储判断提示方法
private boolean updateAndShowStorageHint() { mStorageSpace = getAvailableSpace(); return showStorageHint(); }
发送一个录音状态广播
private void sendRecordingStatusIntent(int status) { Intent intent = new Intent(ACTION_FM_RECORDING_STATUS); intent.putExtra("state", status); Log.d(TAG, "posting intent for FM Recording status as = " +status); getApplicationContext().sendBroadcastAsUser(intent, UserHandle.ALL); }
getApplicationContext().sendBroadcastAsUser(intent,UserHandle.ALL);
UserHandle.ALL一个类的对象,USER_ALL= -1返回的一个字符串UserHandle{-1}
回调方法,去fmradioservice.java回调
mCallbacks.onRecordingStarted();方法
mCallbacks.onRecordingStopped();方法
public void registerFMRecordingStatus()
启动录音
private boolean startRecord() { Log.d(TAG, "Enter startRecord"); if (mRecorder != null) { /* Stop existing recording if any */ Log.d(TAG, "Stopping existing record"); try { mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; } catch(Exception e) { e.printStackTrace(); } } if (!updateAndShowStorageHint()) return false; long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD; mRecorder = new MediaRecorder(); try { mRecorder.setMaxFileSize(maxFileSize); if(mRecordDuration >= 0) mRecorder.setMaxDuration(mRecordDuration); } catch (RuntimeException exception) { } mSampleFile = null; File sampleDir; <span style="font-family:KaiTi_GB2312;"> </span>if((Environment.getExternalSDStorageState(this).equals(Environment.MEDIA_MOUNTED))){ sampleDir = new File(Environment.getExternalSDStorageDirectory(), "/FMRecording"); }else{ sampleDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/FMRecording"); } if(!(sampleDir.mkdirs() || sampleDir.isDirectory())) return false; try { mSampleFile = File.createTempFile("FMRecording", ".3gpp", sampleDir); } catch (IOException e) { Log.e(TAG, "Not able to access SD Card"); Toast.makeText(this, "Not able to access SD Card", Toast.LENGTH_SHORT).show(); } try { Log.d(TAG, "AudioSource.FM_RX" +MediaRecorder.AudioSource.FM_RX); mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mAudioType = "audio/3gpp"; } catch (RuntimeException exception) { Log.d(TAG, "RuntimeException while settings"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } Log.d(TAG, "setOutputFile"); mRecorder.setOutputFile(mSampleFile.getAbsolutePath()); try {mRecorder.prepare(); Log.d(TAG, "start"); mRecorder.start(); } catch (IOException e) { Log.d(TAG, "IOException while start"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } catch (RuntimeException e) { Log.d(TAG, "RuntimeException while start"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } mFmRecordingOn = true; Log.d(TAG, "mSampleFile.getAbsolutePath() " +mSampleFile.getAbsolutePath()); mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { public void onInfo(MediaRecorder mr, int what, int extra) { if ((what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) || (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)) { if (mFmRecordingOn) { Log.d(TAG, "Maximum file size/duration reached, stopping the recording"); stopRecord(); } if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { // Show the toast. Toast.makeText(FMRecordingService.this, R.string.FMRecording_reach_size_limit, Toast.LENGTH_LONG).show(); } } } // from MediaRecorder.OnErrorListenerpublic void one rror(MediaRecorder mr, int what, int extra) { Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { // We may have run out of space on the sdcard. if (mFmRecordingOn) { stopRecord(); } updateAndShowStorageHint(); } } }); mSampleStart = System.currentTimeMillis(); sendRecordingStatusIntent(START); startNotification(); return true; }
录音不为空先设置为停止录音从新设置释放资源
mRecorder!= null
mRecorder.stop();
mRecorder.reset();
mRecorder.release();
mRecorder = null;
录音判断存储空间够用不
if (!updateAndShowStorageHint())
return false;
录音最大值为当前值前去低于存储极限值。
longmaxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;
设置录音最大值
mRecorder.setMaxFileSize(maxFileSize);
设置录音持续
mRecorder.setMaxDuration(mRecordDuration);
设置录音来源,放置的位置,录音audio格式,audio写入音源编码格式
mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
录音监听mediaRecorder监听
mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener()
what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
mFmRecordingOn 为true 停止录音
stopRecord();
录音过程报错停止录音
stopRecord();弹出提示
updateAndShowStorageHint();
获取当前时间,发送录音状态广播,启动通知
mSampleStart= System.currentTimeMillis();
sendRecordingStatusIntent(START);
startNotification();
发送通知,设置远程控制,startForeground(102,status);设置sevice至于前台
private void startNotification() { RemoteViews views = new RemoteViews(getPackageName(), R.layout.record_status_bar); Notification status = new Notification(); status.contentView = views; status.flags |= Notification.FLAG_ONGOING_EVENT; status.icon = R.drawable.ic_menu_record; startForeground(102, status); }
停止录音,保存录音文件,停止录音之前台,停止状体切换
private void stopRecord()
sendRecordingStatusIntent(STOP);
saveFile();
stopForeground(true);
stopClientStatusCheck();
保存录音方法(录音一开录,就在往默认内置T中写数据以字节方式写入数据)
private void saveFile() { int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 ); Log.d(TAG, "Enter saveFile"); if (sampleLength == 0) return; String state = Environment.getExternalStorageState(); Log.d(TAG, "storage state is " + state); if (Environment.MEDIA_MOUNTED.equals(state)) { try { this.addToMediaDB(mSampleFile); Toast.makeText(this,getString(R.string.save_record_file, mSampleFile.getAbsolutePath( )), Toast.LENGTH_LONG).show(); } catch(Exception e) { e.printStackTrace(); } } else { Log.e(TAG, "SD card must have removed during recording. "); Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show(); } return; }
获取当时减去录音起始时间如果为零就跳出保存方法不保存数据
intsampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
Environment.MEDIA_MOUNTED.equals(state)sd卡可用就将录音路径添加到多媒体数据库中
this.addToMediaDB(mSampleFile);
将信息添加到多媒体数据库,音频的audio格式存入数据库中
private Uri addToMediaDB(File file) { Log.d(TAG, "In addToMediaDB"); Resources res = getResources(); ContentValues cv = new ContentValues(); long current = System.currentTimeMillis(); long modDate = file.lastModified(); Date date = new Date(current); SimpleDateFormat formatter = new SimpleDateFormat( res.getString(R.string.audio_db_title_format)); String title = formatter.format(date); // Lets label the recorded audio file as NON-MUSIC so that the file // won't be displayed automatically, except for in the playlist. cv.put(MediaStore.Audio.Media.IS_MUSIC, "1");cv.put(MediaStore.Audio.Media.TITLE, title); cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath()); cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000)); cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000)); cv.put(MediaStore.Audio.Media.MIME_TYPE, mAudioType); cv.put(MediaStore.Audio.Media.ARTIST, res.getString(R.string.audio_db_artist_name)); cv.put(MediaStore.Audio.Media.ALBUM, res.getString(R.string.audio_db_album_name)); Log.d(TAG, "Inserting audio record: " + cv.toString());ContentResolver resolver = getContentResolver(); Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; Log.d(TAG, "ContentURI: " + base); Uri result = resolver.insert(base, cv); if (result == null) { Toast.makeText(this, R.string.unable_to_store, Toast.LENGTH_SHORT).show(); return null; } if (getPlaylistId(res) == -1) { createPlaylist(res, resolver); } int audioId = Integer.valueOf(result.getLastPathSegment()); addToPlaylist(resolver, audioId, getPlaylistId(res)); // Notify those applications such as Music listening to the // scanner events that a recorded audio file just created. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); return result; }
获取audio的uri
Uri base =MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
添加到播放列表
addToPlaylist(resolver,audioId, getPlaylistId(res));
可以存入music数据
cv.put(MediaStore.Audio.Media.IS_MUSIC,"1")
发送广播扫描
sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
privatevoid registerRecordingListner()广播接收者
private void registerRecordingListner() { if (mFmRecordingReceiver == null) { mFmRecordingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent " +intent); String action = intent.getAction(); Log.d(TAG, " action = " +action); if (action.equals(ACTION_FM_RECORDING)) { int state = intent.getIntExtra("state", STOP); Log.d(TAG, "ACTION_FM_RECORDING Intent received" + state); if (state == START) { Log.d(TAG, "Recording start"); mRecordDuration = intent.getIntExtra("record_duration", mRecordDuration); if(startRecord()) { clientProcessName = intent.getStringExtra("process_name"); clientPid = intent.getIntExtra("process_id", -1); startClientStatusCheck(); }} else if (state == STOP) { Log.d(TAG, "Stop recording"); stopRecord(); } } } }; IntentFilter iFilter = new IntentFilter(); iFilter.addAction(ACTION_FM_RECORDING); registerReceiver(mFmRecordingReceiver, iFilter); } }
广播状态为1的时候就开始录音并检查线程
private void startClientStatusCheck()
获取客户端所有进程信息进行匹配如果启动的app没有被杀死就继续录音否则停止
privateboolean getClientStatus(int pid, String processName)
ActivityManageractvityManager =
(ActivityManager)this.getSystemService(
this.ACTIVITY_SERVICE);
List<RunningAppProcessInfo>procInfos =
actvityManager.getRunningAppProcesses();
for(RunningAppProcessInfo procInfo :procInfos) {
if ((pid == procInfo.pid)
&&
(procInfo.processName.equals(processName))) {
status = true;
break;
}
}
procInfos.clear();
privateRunnable clientStatusCheckThread = new Runnable()
停止录音后睡眠500毫秒
privatevoid stopClientStatusCheck()中断线程mStatusCheckThread
private void stopClientStatusCheck() { if(mStatusCheckThread != null) { mStatusCheckThread.interrupt(); } }
AndroidFM模块学习之录音功能