AndroidFM模塊學習之錄音功能
發表時間:2020-10-19
發布人:葵宇科技
浏覽次數:40
前些天禀析了一下(xià)FM的流程以及重要類,接下(xià)來我們分析一下(xià)FM的灌音功能;
[img]http://img.blog.csdn.net/20150106172922265?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGZzbG92ZXhpemk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
Fm灌音時,當點擊了灌音按鈕,會發一個(gè)廣播出去,源碼在FMRadioService.java中(zhōng)
<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啟動(dòng)與封閉
if (state == 1) {
發送廣播掃描
Environment.MEDIA_CHECKING檢查sd卡預備攫取
Log.d(TAG,"FM ONintent received");
startService = true;
context.startService(in);
} elseif(state == 0){
Log.d(TAG,"FM OFFintent received");
startService = false;
獲取audio的uri
context.stopService(in);
}
Fm廣播接收的action publicstatic final StringACTION_FM = "codeaurora.intent.action.FM";
當FMRadioservice類的private void sendRecordServiceIntent(int action)辦法發送一個(gè)廣播并附帶一個(gè)開關(guān)灌音的狀況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啟動(dòng)與封閉
<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()辦法裡注冊廣播接收機制,一個(gè)廣播是灌音狀況,一個(gè)是封閉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>
stopRecord();
onDestroy()辦法裡寫了卸載注冊停止灌音
起首看下(xià)賤程圖:
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();停止灌音
status = true;
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();注冊接收辦法中(zhōng)停止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; }
重要經由過程以下(xià)辦法實現:
FilesampleDir = Environment.getExternalStorageDirectory();
StatFs stat = newStatFs(sampleDir.getAbsolutePath());
return stat.getAvailableBlocks() *(long) stat.getBlockSize();
有四種值:
Environment.MEDIA_MOUNTED沒有sd卡
UNKNOWN_SIZE sd卡有效空間等于UNKNOWN_SIZE值
LOW_STORAGE_THRESHOLD低于存儲空間極限值
更新顯示存儲斷定提示辦法
private boolean updateAndShowStorageHint() { mStorageSpace = getAvailableSpace(); return showStorageHint(); }
發送一個(gè)灌音狀況廣播
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一個(gè)類的對象,USER_ALL= -1返回的一個(gè)字符串UserHandle{-1}
回調辦法,去fmradioservice.java回調
mCallbacks.onRecordingStarted();辦法
mCallbacks.onRecordingStopped();辦法
public void registerFMRecordingStatus()
啟動(dòng)灌音
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 onError(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; }
灌音不為空先設置為停止灌音大年夜新設置釋放資(zī)本
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
灌音過程報錯停止灌音
stopRecord();彈出提示
updateAndShowStorageHint();
獲取當前時光,發送灌音狀況廣播,啟動(dòng)通(tōng)知
mSampleStart= System.currentTimeMillis();
sendRecordingStatusIntent(START);
startNotification();
mFmRecordingOn 為true 停止灌音
發送通(tōng)知,設置長途控制,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); }
停止灌音,保存灌音文(wén)件,停止灌音之前台,停止狀體切換
private void stopRecord()
sendRecordingStatusIntent(STOP);
saveFile();
stopForeground(true);
stopClientStatusCheck();
保存灌音辦法(灌音一開錄,就在往默認内置T中(zhōng)寫數據以字節方法寫入數據)
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; }
獲取當時減去灌音肇端時光如(rú)不雅為零就彪炳保存辦法不保存數據
intsampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
Environment.MEDIA_MOUNTED.equals(state)sd卡可(kě)用就将灌音路(lù)徑添加到多媒體數據庫中(zhōng)
this.addToMediaDB(mSampleFile);
将信息添加到多媒體數據庫,音頻的audio格式存入數據庫中(zhōng)
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; }
Uri base =MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
添加到播放列表
addToPlaylist(resolver,audioId, getPlaylistId(res));
可(kě)以存入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()
獲取客戶端所有過程信息進行匹配如(rú)不雅啟動(dòng)的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)
&&
privateRunnable clientStatusCheckThread = new Runnable()
(procInfo.processName.equals(processName))) {
break;
}
}
procInfos.clear();
停止灌今後睡眠500毫秒
privatevoid stopClientStatusCheck()中(zhōng)斷線程mStatusCheckThread
private void stopClientStatusCheck() { if(mStatusCheckThread != null) { mStatusCheckThread.interrupt(); } }