一,FM主要类介绍
FmMainActivity.java 主界面
FmService.java 核心
FmNative.java 调用JNI和底层通信
二,FM主要功能介绍
1,收音播放:
1 ) 搜台
2)收藏
3)耳机/外放播放
4 ) 切台
5)定时关闭
6)飞行模式
2,录音播放
三,流程分析
1,不插入耳机
不插入耳机启动FM,是不能播放FM,会提示 “请插入耳机”
代码流程分析:
在FmMainActivity.java-->onCreate()中会加载布局
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
initUiComponent();
......
}
FmMainActivity.java-->initUiComponent()
private void initUiComponent() {
......
mMainLayout = (LinearLayout) findViewById(R.id.main_view);
mNoHeadsetLayout = (RelativeLayout) findViewById(R.id.no_headset);
......
}
这里加载了两个布局no_headset和main_view,main_view是插入耳机正常收听时的界面,no_headset是不插入耳机时的提示界面,我们现在先分析no_headset
在FmMainActivity.java-->changeToNoHeadsetLayout()中会将mMainLayout隐藏掉,将mNoHeadsetLayout设置成visible
/**
* change to no headset layout
*/
private void changeToNoHeadsetLayout() {
......
mMainLayout.setVisibility(View.GONE);
......
mNoHeadsetLayout.setVisibility(View.VISIBLE);
......
}
接下来看下changeToNoHeadsetLayout()是在哪儿调用的
@Override
public void onStart() {
super.onStart();
......
} else if (isAntennaAvailable()) {
changeToMainLayout();
} else {
changeToNoHeadsetLayout();
}
......
}
可以看到是通过isAntennaAvailable()来判断是否有耳机
FmMainActivity.java-->isAntennaAvailable()
private boolean isAntennaAvailable() {
if (FmUtils.supportShortAntenna) {
return true;
} else {
return isWiredHeadsetOn();
//mAudioManager.isWiredHeadsetOn();
}
}
FmMainActivity.java-->isWiredHeadsetOn()
private boolean isWiredHeadsetOn() {
//bug939562 return true ,Only wired headset is plugged in
AudioDeviceInfo[] audioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo info : audioDeviceInfo) {
//wired headset(with mic or not) is pluged in
if ((info.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET)||(info.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES)){
Log.d(TAG,"Wired headset is exist");
return true;
}
}
return false;
//return true ,type-c or wired headset is plugged in
//mAudioManager.isWiredHeadsetOn();
}
可以看到启动app的时候,是通过AudioDeviceInfo的tpye来判断是否插入了耳机
2.插入耳机
到插入耳机的时候,会切换到main_view这个界面,下面看下这个流程
再看耳机插入监听流程前,需要先讲下FmService的启动流程
@Override
public void onStart() {
super.onStart();
......
if (null == startForegroundService(new Intent(FmMainActivity.this, FmService.class))) {
Log.e(TAG, "onStart, cannot start FM service");
return;
}
mIsServiceStarted = true;
mIsServiceBinded = bindService(new Intent(FmMainActivity.this, FmService.class),
mServiceConnection, Context.BIND_AUTO_CREATE);
......
}
在FmService onBind()成功后,后就会调用ServiceConnection里的onServiceConnected()方法
private final ServiceConnection mServiceConnection = new ServiceConnection() {
/**
* called by system when bind service
*
* @param className component name
* @param service service binder
*/
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ((FmService.ServiceBinder) service).getService();
if (null == mService) {
Log.e(TAG, "onServiceConnected, mService is null");
finish();
return;
}
Log.d(TAG, "onServiceConnected, mService is not null");
refreshRDSVisiable();
mService.registerFmRadioListener(mFmRadioListener);
mService.setFmActivityForground(mIsActivityForeground);
// bug568587, new feature FM new UI
mService.setTimerListenr(new MyTimeCountListener());
if (!mService.isServiceInited()) {
mService.initService();
powerUpFm();
} else {
if (mService.isDeviceOpen()) {
updateMenuStatus();
} else {
// Normal case will not come here
// Need to exit FM for this case
exitService();
finish();
}
}
}
/**
* When unbind service will call this method
*
* @param className The component name
*/
@Override
public void onServiceDisconnected(ComponentName className) {
}
};
在这里会调用FmService.java-->registerFmRadioListener()监听mFmRadioListener
private FmListener mFmRadioListener = new FmListener() {
@Override
public void onCallBack(Bundle bundle) {
int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
if (flag == FmListener.MSGID_FM_EXIT) {
mHandler.removeCallbacksAndMessages(null);
}
// remove tag message first, avoid too many same messages in queue.
Message msg = mHandler.obtainMessage(flag);
msg.setData(bundle);
mHandler.removeMessages(flag);
mHandler.sendMessage(msg);
}
};
FmListener是个接口,FmMainActivity.java实现了这个接口
再反过来看FmService.java-->registerFmRadioListener()
public void registerFmRadioListener(FmListener callback) {
synchronized (mRecords) {
// register callback in AudioProfileService, if the callback is
// exist, just replace the event.
Record record = null;
int hashCode = callback.hashCode();
final int n = mRecords.size();
for (int i = 0; i < n; i++) {
record = mRecords.get(i);
if (hashCode == record.mHashCode) {
return;
}
}
record = new Record();
record.mHashCode = hashCode;
record.mCallback = callback;
mRecords.add(record);
}
}
这里会把FmListener add到一个list里,因为在FmFavoriteActivity FmMainActivity FmRecordActivity 中都会注册监听这个接口
这套监听机制介绍后,就要具体讲FmService.java监听到耳机插拔事件怎么通知给FmMainActivity,让它更新界面了
在FmService.java中有个内部广播接收器FmServiceBroadcastReceiver extends BroadcastReceiver,在FmServiceBroadcastReceiver里会监听Intent.ACTION_HEADSET_PLUG事件
监听到后,调用switchAntennaAsync()
public void switchAntennaAsync(int antenna) {
final int bundleSize = 1;
mFmServiceHandler.removeMessages(FmListener.MSGID_SWITCH_ANTENNA);
Bundle bundle = new Bundle(bundleSize);
bundle.putInt(FmListener.SWITCH_ANTENNA_VALUE, antenna);
Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_SWITCH_ANTENNA);
msg.setData(bundle);
mFmServiceHandler.sendMessage(msg);
}
case FmListener.MSGID_SWITCH_ANTENNA:
bundle = msg.getData();
int value = bundle.getInt(FmListener.SWITCH_ANTENNA_VALUE);
// if ear phone insert, need dismiss plugin earphone
// dialog
// if earphone plug out and it is not play recorder
// state, show plug dialog.
bundle.putInt(FmListener.CALLBACK_FLAG,
FmListener.MSGID_SWITCH_ANTENNA);
bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, (0 == value));
notifyActivityStateChanged(bundle);
break;
private void notifyActivityStateChanged(Bundle bundle) {
if (!mRecords.isEmpty()) {
synchronized (mRecords) {
Iterator<Record> iterator = mRecords.iterator();
while (iterator.hasNext()) {
Record record = (Record) iterator.next();
FmListener listener = record.mCallback;
if (listener == null) {
iterator.remove();
return;
}
listener.onCallBack(bundle);
}
}
}
}
到这里就回到了FmMainActivity.java-->FmListener .java-->onCallBack()
private FmListener mFmRadioListener = new FmListener() {
@Override
public void onCallBack(Bundle bundle) {
int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
if (flag == FmListener.MSGID_FM_EXIT) {
mHandler.removeCallbacksAndMessages(null);
}
// remove tag message first, avoid too many same messages in queue.
Message msg = mHandler.obtainMessage(flag);
msg.setData(bundle);
mHandler.removeMessages(flag);
mHandler.sendMessage(msg);
}
};
从bundle里传过来是MSGID_SWITCH_ANTENNA
case FmListener.MSGID_SWITCH_ANTENNA:
......
if (hasAntenna) {
cancelNoHeadsetAnimation();
if (mIsActivityForeground) {
playMainAnimation();
} else {
changeToMainLayout();
}
......
2.搜台功能
点击FmFavoriteActivity.java界面的menu菜单,点击刷新
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.fm_station_list_refresh:
if ((null != mService) && (!mService.isSeeking())) {
onOptionsItemSelectedScan(false);
}
break;
}
return super.onOptionsItemSelected(item);
}
FmFavoriteActivity.java-->onOptionsItemSelectedScan()
private void onOptionsItemSelectedScan(boolean fromCurrent) {
if (null != mService) {
showTipView(true);
if (fromCurrent) {
mService.startScanAsyncFromCurrent(mCurrentStationItem.stationFreq);
} else {
mService.startScanAsync();
}
}
}
FmService.java-->startScanAsync()
/**
* Scan stations
*/
public void startScanAsync() {
mFmServiceHandler.removeMessages(FmListener.MSGID_SCAN_FINISHED);
mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_SCAN_FINISHED);
}
// start scan
case FmListener.MSGID_SCAN_FINISHED:
......
stations = startScan(start_freq);
/**
* @}
*/
}
private int[] startScan(int start_freq) {
......
stations = mFmManager.autoScan(start_freq);
......
return stations;
}
FmManagerSelect.java -->autoScan()
public int[] autoScan(int start_freq) {
if (mIsUseBrcmFmChip) {
return mFmManagerForBrcm.autoScanStation(start_freq);
} else {
short[] stationsInShort = null;
int[] stations = null;
stationsInShort = FmNative.autoScan(start_freq);
if (null != stationsInShort) {
int size = stationsInShort.length;
stations = new int[size];
for (int i = 0; i < size; i++) {
stations[i] = stationsInShort[i];
}
}
return stations;
}
}
这里就调用到FmNative.java-->autoScan()
static native short[] autoScan(int start_freq);
扫描成功后,会将扫描到的数据插入到数据库中
private int[] updateStations(int[] stations) {
Log.d(TAG, "updateStations.firstValidstation:" + Arrays.toString(stations));
int firstValidstation = mCurrentStationItem.stationFreq;
FmStation.cleanSearchedStations(mContext);
int stationNum = batchInsertStationToDb(stations, null);
int searchedNum = (stations == null ? 0 : stations.length);
Log.d(TAG, "updateStations.firstValidstation:" + firstValidstation +
",searchedNum:" + searchedNum);
return (new int[]{
firstValidstation, searchedNum
});
}
private int batchInsertStationToDb(int[] stations, List<FmStationItem> favoriteList) {
if (null == stations) return 0;
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ContentResolver resolver = mContext.getContentResolver();
for (int i = 0; i < stations.length; i++) {
if (!FmUtils.isValidStation(stations[i]) || FmStation.isFavoriteStation(mContext, stations[i])) {
Log.d(TAG, "station is favorite : " + stations[i]);
continue;
}
ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(Station.CONTENT_URI);
op.withYieldAllowed(false);
Log.d(TAG, "station : " + stations[i]);
ContentValues values = new ContentValues();
values.clear();
values.put(Station.FREQUENCY, stations[i]);
values.put(Station.IS_SHOW, Station.IS_SCAN);
op.withValues(values);
ops.add(op.build());
}
Log.d(TAG, "ops size : " + ops.size());
int stationNum = ops.size();
if (stationNum > 0) {
try {
ContentProviderResult[] result = resolver.applyBatch(FmStation.AUTHORITY, ops);
ops.clear();
Log.d(TAG, "batch opreate db result count : " + result.length);
} catch (Exception e) {
Log.d(TAG, "Batch operate exception");
e.printStackTrace();
}
} else {
mContext.getContentResolver().notifyChange(FmStation.Station.CONTENT_URI, null);
}
return stationNum;
}
搜索成功后,调用FmService.java-->notifyCurrentActivityStateChanged()通知FmFavoriteActivity.java更新界面
private void refreshFmFavorityList(Cursor cursor) {
if (cursor == null) {
return;
}
mFavoriteList.clear();
mOtherList.clear();
mAllStationSet.clear();
for (int i = 0; i < cursor.getCount(); i++) {
FmStationItem item = new FmStationItem();
if (cursor.moveToFirst()) {
cursor.moveToPosition(i);
item.stationFreq = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.FREQUENCY));
item.stationName = cursor.getString(cursor
.getColumnIndex(FmStation.Station.STATION_NAME));
item.rt = cursor.getString(cursor
.getColumnIndex(FmStation.Station.RADIO_TEXT));
item.isFavorite = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.IS_FAVORITE));
item.isShow = cursor.getInt(cursor
.getColumnIndex(FmStation.Station.IS_SHOW));
item.ps = cursor.getString(cursor
.getColumnIndex(FmStation.Station.PROGRAM_SERVICE));
if (item.isFavorite == 0) {
mOtherList.add(item);
} else {
mFavoriteList.add(item);
}
mAllStationSet.add(item.stationFreq);
}
}
checkSelectedStationSet();
mMyAdapter.notifyDataSetChanged();
Log.d(TAG, "refreshFmFavorityList mAllStationSet.size:" + mAllStationSet.size());
}
通过MyFavoriteAdapter的notifyDataSetChanged()来刷新界面
3.收藏
viewHolder.mImgLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* bug568587, new feature FM new UI @{ */
if (0 == isFavorite) {
showToast(getString(R.string.toast_channel_added));
addFavorite(stationFreq);
} else {
showToast(getString(R.string.toast_channel_deleted));
deleteFromFavorite(stationFreq);
}
/* @} */
}
});
/**
* Add searched station as favorite station
*/
public void addFavorite(int stationFreq) {
// TODO it's on UI thread, change to sub thread
// update the station name and station type in database
// according the frequency
operateFmFavoriteStation(stationFreq, ACTION_ADD_FAVORITE_TYPE);
}
private void operateFmFavoriteStation(int stationFreq, int type) {
MyAsyncTask operateTask = new MyAsyncTask();
operateTask.execute(ACTION_OPERATE_TYPE, stationFreq, type);
}
class MyAsyncTask extends AsyncTask<Integer, Void, Cursor> {
@Override
protected Cursor doInBackground(Integer... params) {
Log.d(TAG, "operate database type:" + params[0]);
if (params[0] == ACTION_QUERY_TYPE) {
Cursor cursor = getData();
return cursor;
} else {
Log.d(TAG, "stationFreq=" + params[1]);
operateData(params[1], params[2]);
return null;
}
}
@Override
protected void onPostExecute(Cursor cursor) {
refreshFmFavorityList(cursor);
if (cursor != null) {
cursor.close();
}
}
}
private void operateData(int stationFreq, int type) {
switch (type) {
case ACTION_ADD_FAVORITE_TYPE:
FmStation.addToFavorite(mContext, stationFreq);
break;
case ACTION_REMOVE_FROM_FAVORITE_TYPE:
FmStation.removeFromFavorite(mContext, stationFreq);
break;
}
/**
* update db to mark it is a favorite frequency
*
* @param context The context
* @param frequency The target frequency
*/
public static void addToFavorite(Context context, int frequency) {
ContentValues values = new ContentValues(1);
values.put(Station.IS_FAVORITE, true);
values.put(Station.IS_SHOW,Station.IS_SCAN);
context.getContentResolver().update(
Station.CONTENT_URI,
values,
Station.FREQUENCY + "=?",
new String[] {
String.valueOf(frequency)
});
}
4.播放流程
FmMainActivity.java
case R.id.play_button:
/**
* bug500046, for monkey test.
*
* @{
*/
if (null == mService) {
Log.e(TAG, "onClick case playbutton, mService is null");
return;
}
/**
* @}
*/
if (mService.getPowerStatus() == FmService.POWER_UP) {
if(mService.isMuted()){
mService.setMuteAsync(false,false);
}else{
powerDownFm();
}
} else {
powerUpFm();
}
break;
default:
Log.d(TAG, "mButtonClickListener.onClick, invalid view id");
break;
FmMainActivity.java -- >powerUpFm()
/**
* Power up FM
*/
private void powerUpFm() {
if(FmUtils.isAirplane(this)){
changeToAirPlaneLayout();
}else{
refreshImageButton(false);
refreshActionMenuItem(false);
refreshPopupMenuItem(false);
refreshPlayButton(false);
mService.powerUpAsync(FmUtils.computeFrequency(mCurrentStationItem.stationFreq));
}
}
发送MSGID_POWERUP_FINISHED
public void powerUpAsync(float frequency) {
final int bundleSize = 1;
mFmServiceHandler.removeMessages(FmListener.MSGID_POWERUP_FINISHED);
mFmServiceHandler.removeMessages(FmListener.MSGID_POWERDOWN_FINISHED);
Bundle bundle = new Bundle(bundleSize);
bundle.putFloat(FM_FREQUENCY, frequency);
Message msg = mFmServiceHandler.obtainMessage(FmListener.MSGID_POWERUP_FINISHED);
msg.setData(bundle);
mFmServiceHandler.sendMessage(msg);
}
FmService.java -->powerUpAsync()
// power up
case FmListener.MSGID_POWERUP_FINISHED:
bundle = msg.getData();
handlePowerUp(bundle);
break;
FmService.java -->handlePowerUp()
private void handlePowerUp(Bundle bundle) {
boolean isPowerUp = false;
boolean isSwitch = true;
float curFrequency = bundle.getFloat(FM_FREQUENCY);
if (FmUtils.isAirplane(this)) {
Log.d(TAG, "handlePowerUp, airplane is on");
return;
}
boolean isAntennaAvailable = isAntennaAvailable();
if (!FmUtils.supportShortAntenna) {
if (!isAntennaAvailable) {
Log.d(TAG, "handlePowerUp, earphone is not ready");
bundle = new Bundle(2);
bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_SWITCH_ANTENNA);
bundle.putBoolean(FmListener.KEY_IS_SWITCH_ANTENNA, false);
notifyActivityStateChanged(bundle);
return;
}
} else {
Log.d(TAG, "handlePowerUp: wether earphone is plugged in -->" + isAntennaAvailable);
switchAntenna(isAntennaAvailable ? 0 : 1);
}
if (powerUp(curFrequency)) {
if (FmUtils.isFirstTimePlayFm(mContext)) {
isPowerUp = firstPlaying(curFrequency);
FmUtils.setIsFirstTimePlayFm(mContext);
} else {
/**
* Bug546461 Tune radio again after power down then power up for brcm. Orig:
* isPowerUp = playFrequency(curFrequency);
* @{
*/
if (mFmManager.tuneRadioAgain(curFrequency)) {
isPowerUp = playFrequency(curFrequency);
}
/**
* @}
*/
}
mPausedByTransientLossOfFocus = false;
}
bundle = new Bundle(1);
bundle.putInt(FmListener.CALLBACK_FLAG, FmListener.MSGID_POWERUP_FINISHED);
notifyActivityStateChanged(bundle);
}
FmService.java -->playFrequency()
private boolean playFrequency(float frequency) {
Log.d(TAG, "playFrequency");
updatePlayingNotification();
// Start the RDS thread if RDS is supported.
// RDS function should be open status.
if (isRdsSupported() && FmUtils.isRDSOpen(mContext)) {
startRdsThread();
}
//bug492835, FM audio route change. if (!mWakeLock.isHeld()) { mWakeLock.acquire(); }
if (mIsSpeakerUsed) {
setForceUse(mIsSpeakerUsed);
}
enableFmAudio(true);
setRds(true);
setMute(false);
return (mPowerStatus == POWER_UP);
}
FmService.java -->enableFmAudio()
private void enableFmAudio(boolean enable) {
Log.d(TAG, "enableFmAudio:" + enable);
if (enable) {
if ((mPowerStatus != POWER_UP) || !mIsAudioFocusHeld) {
Log.d(TAG, "enableFmAudio, current not available return.mIsAudioFocusHeld:"
+ mIsAudioFocusHeld);
return;
}
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
mAudioManager.listAudioPatches(patches);
if (mAudioPatch == null) {
Log.d(TAG, "mAudioPatch == null");
if (isPatchMixerToEarphone(patches)) {
int status;
stopRender();
status = createAudioPatch();
if (status != AudioManager.SUCCESS) {
Log.d(TAG, "enableFmAudio: fallback as createAudioPatch failed");
startRender();
}
} else {
startRender();
}
}
} else {
releaseAudioPatch();
stopRender();
}
}
FmService.java -->startRender()
private synchronized void startRender() {
//bug492835, FM audio route change.
mFmManager.setAudioPathEnable(AudioPath.FM_AUDIO_PATH_HEADSET, true);
mMediaSessionManager.setOnMediaKeyListener(mMediaKeyListener, null);
Log.d(TAG, "startRender,setOnMediaKeyListener");
mIsRender = true;
synchronized (mRenderLock) {
mRenderLock.notify();
}
}
FmManagerSelect.java -->setAudioPathEnable()
public boolean setAudioPathEnable(AudioPath path, boolean enable) {
if (mIsUseBrcmFmChip) {
if (enable) {
mFmManagerForBrcm.setAudioMode(FmAudioModeForBrcm.FM_AUDIO_MODE_BLEND);
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
// AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
} else {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
// AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
}
return mFmManagerForBrcm.setAudioPath(convertAudioPathForBrcm(path));
} else {
if (enable) {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE, "", "");
return true;
} else {
//AudioSystem.setDeviceConnectionState(AudioManager.DEVICE_OUT_FM_HEADSET,
mAudioManager.setDeviceConnectionStateForFM(AudioManager.DEVICE_OUT_FM_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "");
return true;
}
}
}
AudioManager.java-->setDeviceConnectionStateForFM()
public void setDeviceConnectionStateForFM(int device, int state,
String device_address, String device_name) {
final IAudioService service = getService();
try {
service.setDeviceConnectionStateForFM(device, state, device_address, device_name,mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setDeviceConnectionStateForFM ", e);
}
}
二,录音
录音是FmRecordActivity
点击start,调用 mService.startRecordingAsync();
/**
* Start recording
*/
public void startRecordingAsync() {
mFmServiceHandler.removeMessages(FmListener.MSGID_STARTRECORDING_FINISHED);
mFmServiceHandler.sendEmptyMessage(FmListener.MSGID_STARTRECORDING_FINISHED);
}
case FmListener.MSGID_STARTRECORDING_FINISHED:
startRecording();
break;
public void startRecording(Context context) {
mRecordTime = 0;
Uri dir = null;
FileDescriptor fd = null;
Uri externalStorageUri=null;
/**
* Bug529776 Check the main card state Original Android code:
*
* @{ // Check external storage if
* (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
* Log.e(TAG, "startRecording, no external storage available");
* setError(ERROR_SDCARD_NOT_PRESENT); return; } /** @}
*/
if (FmUtils.isExternalStorage()) {
externalStorageUri =FmUtils.getCurrentAccessUri(FmUtils.FM_RECORD_STORAGE_PATH_NAME);
}
String recordingSdcard = FmUtils.getDefaultStoragePath();
Log.d(TAG,"externalStorageUri="+externalStorageUri+" recordingSdcard="+recordingSdcard);
/**
* bug474747, recording path selection.
*
* @{
*/
if (recordingSdcard == null
|| recordingSdcard.isEmpty()
|| (FmUtils.FM_RECORD_STORAGE_PATH_SDCARD.equals(FmUtils.FM_RECORD_STORAGE_PATH_NAME) && !Environment.MEDIA_MOUNTED
.equals(EnvironmentEx.getExternalStoragePathState()))) {
Log.e(TAG, "startRecording, no sdcard storage available");
setError(ERROR_SDCARD_NOT_PRESENT);
return;
}
/**
* @}
*/
// check whether have sufficient storage space, if not will notify
// caller error message
if (!FmUtils.hasEnoughSpace(recordingSdcard)) {
setError(ERROR_SDCARD_INSUFFICIENT_SPACE);
Log.e(TAG, "startRecording, SD card does not have sufficient space!!");
return;
}
// get external storage directory
File sdDir = new File(recordingSdcard);
File recordingDir = new File(sdDir, FM_RECORD_FOLDER);
// exist a file named FM Recording, so can't create FM recording folder
if (recordingDir.exists() && !recordingDir.isDirectory()) {
Log.e(TAG, "startRecording, a file with name \"" + FM_RECORD_FOLDER + "\" already exists!!");
setError(ERROR_SDCARD_WRITE_FAILED);
return;
} else if (!recordingDir.exists()) { // try to create recording folder
if (FmUtils.isExternalStorage()) {
dir = getPathUri(context,recordingDir,externalStorageUri);
} else {
boolean mkdirResult = recordingDir.mkdir();
if (!mkdirResult) { // create recording file failed
setError(ERROR_RECORDER_INTERNAL);
return;
}
}
} else {
if (FmUtils.isExternalStorage()) {
dir = getPathUri(context,recordingDir,externalStorageUri);
}
}
// create recording temporary file
long curTime = System.currentTimeMillis();
Date date = new Date(curTime);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMddyyyy_HHmmss",
Locale.ENGLISH);
String time = simpleDateFormat.format(date);
StringBuilder stringBuilder = new StringBuilder();
/**
* bug474741, recording format selection.
*
* @{
*/
if (GLOBAL_RECORD_FORMAT_FLAG == 1) {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_3GPP).append(".tmp");
} else if(GLOBAL_RECORD_FORMAT_FLAG == 2) {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_MP3).append(".tmp");
} else {
stringBuilder.append(".").append(time).append(RECORDING_FILE_EXTENSION_AMR).append(".tmp");
}
/**
* @}
*/
String name = stringBuilder.toString();
mRecordFile = new File(recordingDir, name);
if (FmUtils.isExternalStorage()) {
try {
Uri fileDoc = DocumentsContract.createDocument(context.getContentResolver(),dir
,DocumentsContract.Document.COLUMN_MIME_TYPE,name);
FmUtils.saveTmpFileUri(fileDoc.toString());
mPfd = context.getContentResolver().openFileDescriptor(fileDoc,"w");
fd = mPfd.getFileDescriptor();
if(fd == null) {
Log.e(TAG,"fd is NULL");
throw new IllegalArgumentException("Memory related error");
}
} catch(FileNotFoundException e) {
Log.d(TAG,""+e);
}
} else {
try {
if (mRecordFile.createNewFile()) {
Log.d(TAG, "startRecording, createNewFile success with path "
+ mRecordFile.getPath());
}
} catch (IOException e) {
Log.e(TAG, "startRecording, IOException while createTempFile: " + e);
e.printStackTrace();
setError(ERROR_SDCARD_WRITE_FAILED);
return;
}
}
// set record parameter and start recording
try {
mRecorder = new MediaRecorder();
mRecorder.setOnErrorListener(this);
mRecorder.setOnInfoListener(this);
// mRecorder.setAudioSource(MediaRecorder.AudioSource.RADIO_TUNER);
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
/**
* bug474741, recording format selection. Original Android code:
* mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
* mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); final int samplingRate =
* 44100; mRecorder.setAudioSamplingRate(samplingRate); final int bitRate = 128000;
* mRecorder.setAudioEncodingBitRate(bitRate); final int audiochannels = 2;
* mRecorder.setAudioChannels(audiochannels);
*
* @{
*/
if (1 == GLOBAL_RECORD_FORMAT_FLAG) {
Log.d(TAG, "global_record_format: 3gpp");
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(128000);
} else if(2 == GLOBAL_RECORD_FORMAT_FLAG) {
Log.d(TAG, "global_record_format: mp3");
mRecorder.setOutputFormat(11);
mRecorder.setAudioEncoder(7);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(320000);
mRecorder.setAudioChannels(2);
} else {
Log.d(TAG, "global_record_format: amr_nb");
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setAudioSamplingRate(8000);
mRecorder.setAudioEncodingBitRate(5900);
}
/**
* @}
*/
if (FmUtils.isExternalStorage()) {
mRecorder.setOutputFile(fd);
} else {
mRecorder.setOutputFile(mRecordFile.getAbsolutePath());
}
mRecorder.prepare();
mRecordStartTime = SystemClock.elapsedRealtime();
mRecorder.start();
mIsRecordingFileSaved = false;
mFileListener= new FileListener(recordingDir.getPath());
mFileListener.startWatching();
} catch (IllegalStateException e) {
Log.e(TAG, "startRecording, IllegalStateException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
} catch (IOException e) {
Log.e(TAG, "startRecording, IOException while starting recording!", e);
setError(ERROR_RECORDER_INTERNAL);
return;
}
setState(STATE_RECORDING);
}
/**
*Listening whether recording file is delete or not.
*@}
*/
private class FileListener extends FileObserver {
public FileListener(String path) {
super(path, FileObserver.MOVED_FROM | FileObserver.DELETE);
Log.d(TAG, "FileListener path="+path);
}
@Override
public void onEvent(int event, String path) {
Log.d(TAG, "onEvent: event = "+event+"; path = "+path);
switch (event) {
case FileObserver.MOVED_FROM:
case FileObserver.DELETE:
if (path == null) {
return ;
}
if ( getTmpFileName().equals(path)) {
Log.d(TAG, "recording tmp file is deleted");
discardRecording();
}
break;
default:
break;
}
}
}
录音结束
private void stopRecorder() {
......
mRecorder.stop();
......
}