CarRecorder源碼解析二(循環錄像分析)

循環錄像的整體邏輯比較簡單,但代碼還是比較複雜的,邏輯是剛開機就開始循環錄像,每兩分鐘通過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)的時候,不進行刪除,當小於可用內存的時候開始刪除文件,刪除的方法較爲簡單,不在贅述。

發佈了48 篇原創文章 · 獲贊 11 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章