前些天分析了一下FM的流程以及主要类,接下来我们分析一下FM的录音功能;
首先看下流程图:
Fm录音时,当点击了录音按钮,会发一个广播出去,源码在FMRadioService.java中
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);
}
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 publicstatic final StringACTION_FM = "codeaurora.intent.action.FM";
当FMRadioservice类的private void sendRecordServiceIntent(int action)方法发送一个广播并附带一个开关录音的状态int值
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);
}
State状态控制FMRecordingService.java类service启动与关闭
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);
}
}
}
}
Fm接收广播的action
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";
onCreat()方法里注册广播接受机制,一个广播是录音状态,一个是关闭fm状态
public void onCreate() {
super.onCreate();
Log.d(TAG, "FMRecording Service onCreate");
registerRecordingListner();
registerShutdownListner();
registerStorageMediaListener();
}
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;
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;
}
录音不为空先设置为停止录音从新设置释放资源
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
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();
}
}