循环录像的整体逻辑比较简单,但代码还是比较复杂的,逻辑是刚开机就开始循环录像,每两分钟通过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)的时候,不进行删除,当小于可用内存的时候开始删除文件,删除的方法较为简单,不在赘述。