循環錄像的整體邏輯比較簡單,但代碼還是比較複雜的,邏輯是剛開機就開始循環錄像,每兩分鐘通過MediaRecorder錄製一段視頻,當視頻所佔內存卡的大小到達某一個預設的值時,開始自動刪除最老的視頻。
## 一:如何實現開始結束開始的邏輯的?
循環錄像的入口在surfaceView的生命週期onCreate中
當surfaceView創建的時候,初始化VideoRecorder,在此時開始循環錄像。
問題:surfaceView什麼時候被創建?
WindowManager.addView(view, wmParams);在服務裏面添加view是否會被顯示?
if (recorder == null) {
recorder = VideoRecorder.getInstance(holder, this);
recorder.clearShareImageCache();
}
----------------------------------------------------------
if (firstBoot) {
checkExternalSDcardSize();
if (checkSDcard() >= nAvailableMin) {
recorder.start(true);//開始錄像
}
firstBoot = false;
}
#### 開始錄像:
public boolean start(boolean start) {//錄像時傳過來的參數爲true
Log.i("test", "media recorder start=" + start);
if (isTestMode()) {
return false;
}
if (mCamera == null) {
return false;
}
bStart = start;
if (bStart) {//這個參數爲true是開始錄像,爲false是結束錄像
return startRecord();
} else {
return stopRecord();
}
}
若是爲true則走startRecord()方法,bStart爲全局變量,此時爲true;下面來看startRecord()方法的具體實現.
“`
public boolean startRecord() {
//如果SD卡沒有掛載則結束方法返回
if (!android.os.Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED)) {
return false;
}
// add by xrx
try {
//創建存放視頻文件的目錄
File fileDir = new File(VIDEOS_PATH_NORMAL);
if (!fileDir.exists())
fileDir.mkdirs();
fileDir = null;
//創建存放縮略圖的目錄
fileDir = new File(THUMBNAIL_PATH);
if (!fileDir.exists())
fileDir.mkdirs();
fileDir = null;
//在一段視頻錄製完成之前,格式爲時間.temp的格式
szVideoFile = VIDEOS_PATH_NORMAL
+ new SimpleDateFormat(“yyyyMMdd_HHmmss”, Locale.CHINA)
.format(new Date()) + “.temp”;
//準備預覽,設置參數
CameraUtils.prepareRecord(surfaceHolder, RECORD_VIDEO_WIDTH,
RECORD_VIDEO_HEIGHT, persistUtils.getRecordAudioEnable(),
30, nBitRate, 1000 * 60 * persistUtils.getRecordTime(),
szVideoFile);
//開啓錄像,若是開啓成功,則返回true,否則返回false
if (CameraUtils.startRecord(this, this)) {
bRecording = true;//爲true則當前正在錄像
if (recorderListener != null) {
recorderListener.onStarted();
}
}
isUploadImage1 = false;
isUploadImage2 = false;
return true;
} catch (Exception e) {
start(false);
}
return false;
}
“`
由上代碼可見,錄像的具體邏輯實現是在CameraUtils裏面實現的,具體的方法是CameraUtils.prepareRecord和CameraUtils.startRecord方法,點進去看一下,最長時長具體是怎麼起作用的:
控制時長的代碼如下:
/**
* Sets the maximum duration (in ms) of the recording session.
* Call this after setOutFormat() but before prepare().
* After recording reaches the specified duration, a notification
* will be sent to the {@link android.media.MediaRecorder.OnInfoListener}
* with a "what" code of {@link #MEDIA_RECORDER_INFO_MAX_DURATION_REACHED}
* and recording will be stopped. Stopping happens asynchronously, there
* is no guarantee that the recorder will have stopped by the time the
* listener is notified.
*
* @param max_duration_ms the maximum duration in ms (if zero or negative, disables the duration limit)
*
*/
public native void setMaxDuration(int max_duration_ms) throws IllegalArgumentException;
由註釋可見,當達到最大時長時會回調OnInfoListener的方法onInfo,方法的具體實現是在VideoRecorder中,代碼如下:
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
switch (what) {
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: {
stopRecord();
notyfyMediaAdd(new File(szVideoFile));
}
break;
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: {
stopRecord();
notyfyMediaAdd(new File(szVideoFile));
}
break;
case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN: {
start(false);
}
break;
}
}
可見,當達到最大時長時,調用了 stopRecord()方法,隨即循環錄像停止,代碼如下:
private boolean stopRecord() {
boolean bRet = false;
CameraUtils.stopRecord();
if (szVideoFile != null) {
if (szVideoFile.endsWith("temp")) {
File videoFile = new File(szVideoFile);
szVideoFile = szVideoFile.replace("temp", "3gp");
videoFile.renameTo(new File(szVideoFile));
// add by xrx 獲取視頻第一幀對應的縮略圖
getVideoThumbnailFile(szVideoFile);
CarRecorderDebug.printfRECORDERLog("new file renameTo "
+ szVideoFile);
}
}
try {
mCamera.lock();
CarRecorderDebug.printfRECORDERLog("mCamera.lock() ");
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
checkLockFile();
bRet = true;
bRecording = false;
if (recorderListener != null) {
recorderListener.onStoped(bStart);
}
return bRet;
}
由上代碼可見在完成錄製之後先處理縮略圖又調用了recorderListener的onStoped(bStart)方法,方法的具體實現是在PreviewService中完成的,代碼如下:
@Override
public void onStoped(boolean bStart) {
// recordIcon.setVisibility(View.GONE);
setRecording(false);
CarRecorderDebug
.printfRECORDERLog("setRecorderListener onStoped");
recordButton.setImageResource(R.drawable.start_button);
if (bStart)
if (isSDExists(PreviewService.this)) {
recorder.start(true);
}
}
由上代碼可見,當停止時根據bStart參數來判斷要不要進行下一次錄像,這個參數是當錄像開始時傳入的,爲true,詳見VideoRecorder的start(boolean isStart)方法,這時又一次開始錄像。
二:如何 實現錄像的自動刪除的?
代碼如下:
Handler updateTime = new Handler();
Runnable updateTimeThread = new Runnable() {
@Override
public void run() {
long available = checkSDcard();
if (available < 0)
available = 0;
timeText.setText(new SimpleDateFormat(
getString(R.string.carrecorder_time_format), Locale.CHINA)
.format(new Date()));
//使用post實際是讓代碼在主線程中執行
updateTime.postDelayed(updateTimeThread, 1000);
}
};
以上代碼的邏輯實際是實現一個定時循環的效果:在一個子線程裏面通過handler發送消息可指定在多久之後執行,handler收到消息執行業務邏輯,當時間一到,再次執行子線程的邏輯,以此循環
自動刪除錄像的邏輯放在checkSDcard()方法中,代碼如下:
public long checkSDcard() {
File rootPath = new File(recorder.SD_ROOT);
if (!isSDExists(this)) {
showWarning(true, getString(R.string.cardverify));
return -1;
}
checkExternalSDcardSize();
long available = rootPath.getFreeSpace() / 1024 / 1024;
Log.i("test", "recorder available=" + available);
while ((available = rootPath.getFreeSpace() / 1024 / 1024) < nAvailableMin && deleteFile()) {
available = rootPath.getFreeSpace() / 1024 / 1024;
Log.i("test", "recorder available=" + available);
}
if (available < nAvailableMin) {
showWarning(true, getString(R.string.lowstorage));
} else {
showWarning(false, null);
}
return available;
}
該方法返回一個可用的內存大小,每次要進行判斷內存的大小
while ((available = rootPath.getFreeSpace() / 1024 / 1024) < nAvailableMin && deleteFile()) {
available = rootPath.getFreeSpace() / 1024 / 1024;
Log.i("test", "recorder available=" + available);
}
具體可見while循環中的條件,當可用的磁盤可用內存大於安全內存(代碼中爲360M)的時候,不進行刪除,當小於可用內存的時候開始刪除文件,刪除的方法較爲簡單,不在贅述。