MediaRecorder API不支持暂停续录。故MediaRecorder原生API实现暂停续录可通直点关按钮并拼接视频
实现。setProfile可设录视频质量,但此时不可再设setOutputFormat、setAudioEncoder、setVideoEncoder属性,设或致不兼容。
拼接过程避卡顿。每录都独生一视频文件,头点暂停生文件A,不处理;第二次点暂停有文件A和文件B,也不处理;再录时,录过程合并A、B文件为C文件后删A、B文件。用户再点暂停生该阶段D文件及上次合生C文件。即最多有两文件,最后停录时合并最后两文件即可。
拼接过程避卡顿。每录都独生一视频文件,头点暂停生文件A,不处理;第二次点暂停有文件A和文件B,合并后更名合并文件名为A并删B文件。用户再点暂停生该阶段C文件及上次合生并更名后A文件。即最多有两文件,最后停录时合并最后两文件(即A与xxx)即可。
app->libs 官网
isoviewer-1.0-RC-28.jar
只可27或28,其它报错
清单文件
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
VideoUtils
package util;
import android.media.MediaMetadataRetriever;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.TimeToSampleBox;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* @decs: VideoUtils
* @date: 2018/8/27 10:44
* @version: v 1.0
*/
public final class VideoUtils {
public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
Movie movie = MovieCreator.build(src.getAbsolutePath());
// remove all tracks we will create new tracks from the old
List
布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".dataentry.add.VideoRecordAfterNougatActivity">
<SurfaceView
android:id="@+id/svVideoRecordNougat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/d12">
<ImageView
android:id="@+id/ivVideoRecordNougatLight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:alpha="0"
android:contentDescription="@string/toDo"
app:srcCompat="@drawable/ic_light_off" />
<Chronometer
android:id="@+id/ctVideoRecordNougat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:fontFamily="sans-serif-light"
android:textColor="@color/background"
android:textSize="@dimen/s13"
tools:targetApi="jelly_bean" />
RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/viewfinder_mask"
android:padding="@dimen/d24">
<ImageView
android:id="@+id/ivVideoRecordNougatControl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:contentDescription="@string/toDo"
app:srcCompat="@drawable/ic_video_record" />
<ImageView
android:id="@+id/ivVideoRecordNougatPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:alpha="0"
android:contentDescription="@string/toDo"
app:srcCompat="@drawable/ic_video_record_pause" />
RelativeLayout>
RelativeLayout>
主代码
package com.self.zsp.dfs.dataentry.add;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Chronometer;
import android.widget.ImageView;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.self.zsp.dfs.R;
import java.io.File;
import java.io.IOException;
import base.BaseActivity;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import util.AnimationUtils;
import util.DateUtils;
import util.LightControl;
import util.LogUtils;
import util.VideoUtils;
import value.Flag;
import value.Magic;
import widget.ZsDialog;
/**
* @decs: 录视频(7.0前)
* @date: 2018/8/27 10:34
* @version: v 1.0
*/
public class VideoRecordBeforeNougatActivity extends BaseActivity {
private static final int STATE_INIT = 0;
private static final int STATE_RECORDING = 1;
private static final int STATE_PAUSE = 2;
@BindView(R.id.svVideoRecordNougat)
SurfaceView svVideoRecordNougat;
@BindView(R.id.ivVideoRecordNougatLight)
ImageView ivVideoRecordNougatLight;
@BindView(R.id.ctVideoRecordNougat)
Chronometer ctVideoRecordNougat;
@BindView(R.id.ivVideoRecordNougatControl)
ImageView ivVideoRecordNougatControl;
@BindView(R.id.ivVideoRecordNougatPause)
ImageView ivVideoRecordNougatPause;
/**
* Camera、SurfaceHolder、MediaRecorder
*/
private Camera camera;
private SurfaceHolder surfaceHolder;
private MediaRecorder mediaRecorder;
/**
* 录标识
*/
private int recordState;
/**
* 录暂停间隔
*/
private long pauseTime = 0;
/**
* 路径
*/
private String currentVideoFilePath;
private String saveVideoFilePath;
/**
* SurfaceHolder.Callback
*/
private SurfaceHolder.Callback mSurfaceCallBack = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
stepCamera();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
if (surfaceHolder.getSurface() == null) {
return;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
releaseCamera();
}
};
/**
* MediaRecorder.OnInfoListener
*/
private MediaRecorder.OnInfoListener onInfoListener = new MediaRecorder.OnInfoListener() {
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
recordAchieve(true);
}
}
};
/**
* MediaRecorder.OnErrorListener
*/
private MediaRecorder.OnErrorListener onErrorListener = new MediaRecorder.OnErrorListener() {
@Override
public void onError(MediaRecorder mediaRecorder, int what, int extra) {
try {
if (mediaRecorder != null) {
mediaRecorder.reset();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
@Override
protected void initContentView(Bundle savedInstanceState) {
setContentView(R.layout.activity_video_record_nougat);
ButterKnife.bind(this);
}
@Override
protected void stepUI() {
}
@Override
protected void initConfiguration() {
stepSurfaceHolder();
stepCamera();
}
@Override
protected void initData() {
}
@Override
protected void startLogic() {
}
@Override
protected void setListener() {
}
/**
* 初始SurfaceHolder
*/
private void stepSurfaceHolder() {
surfaceHolder = svVideoRecordNougat.getHolder();
// SurfaceHolder无需维护自己缓冲区
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// 分辨率
surfaceHolder.setFixedSize(320, 280);
// 该组件不允屏幕自关
surfaceHolder.setKeepScreenOn(true);
// 回调接口
surfaceHolder.addCallback(mSurfaceCallBack);
}
/**
* 初始摄像头
*/
private void stepCamera() {
if (camera != null) {
releaseCamera();
}
camera = Camera.open();
if (camera == null) {
toastShort(getString(R.string.cameraNotObtain));
return;
}
try {
// 相机绑SurfaceHolder
camera.setPreviewDisplay(surfaceHolder);
// 配CameraParams
cameraParamsConfig();
// 启相机预览
camera.startPreview();
} catch (IOException e) {
// 某些机型兼容出错(适配特定机型)
LogUtils.e("Error starting camera preview: " + e.getMessage());
}
}
/**
* 配CameraParams
*/
private void cameraParamsConfig() {
Camera.Parameters params = camera.getParameters();
// 相机横竖屏(竖屏转90°)
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
params.set("orientation", "portrait");
camera.setDisplayOrientation(90);
} else {
params.set("orientation", "landscape");
camera.setDisplayOrientation(0);
}
// 聚焦模式
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
// 缩Recording启时
params.setRecordingHint(true);
// 影像稳定能力
if (params.isVideoStabilizationSupported()) {
params.setVideoStabilization(true);
}
camera.setParameters(params);
}
/**
* 配MediaRecorder
*/
private void configMediaRecorder() {
if (mediaRecorder == null) {
mediaRecorder = new MediaRecorder();
}
mediaRecorder.reset();
mediaRecorder.setCamera(camera);
// SurfaceView预览
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
// 采集声音
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 采集图像
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 视频、音频输出格式(mp4)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
// 音频编码格式
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 图像编码格式
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 立体声
mediaRecorder.setAudioChannels(2);
// 最大录时(毫秒)
mediaRecorder.setMaxDuration(20 * 1000);
// 最大录大小(字节)
/*mediaRecorder.setMaxFileSize(1024 * 1024);*/
// 音频一秒含多少数据位
CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
mediaRecorder.setAudioEncodingBitRate(44100);
if (camcorderProfile.videoBitRate > Magic.INT_ER * Magic.INT_YLES * Magic.INT_YLES) {
mediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024);
} else {
mediaRecorder.setVideoEncodingBitRate(1024 * 1024);
}
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
// 观看存后视频角度(顺时)(默逆90度,顺时图像正常显)
mediaRecorder.setOrientationHint(90);
// 录像分辨率
mediaRecorder.setVideoSize(352, 288);
// 录像视频输出地址
mediaRecorder.setOutputFile(currentVideoFilePath);
// 信息监听
mediaRecorder.setOnInfoListener(onInfoListener);
// 错误监听
mediaRecorder.setOnErrorListener(onErrorListener);
}
/**
* 开始
*/
private boolean start() {
// 录前相机解锁
camera.unlock();
// 配MediaRecorder
configMediaRecorder();
try {
mediaRecorder.prepare();
mediaRecorder.start();
} catch (IOException e) {
return false;
}
return true;
}
/**
* 停止
*/
private void stop() {
// 设后不crash
mediaRecorder.setOnErrorListener(null);
mediaRecorder.setPreviewDisplay(null);
// 停录
mediaRecorder.stop();
mediaRecorder.reset();
// 释放
mediaRecorder.release();
mediaRecorder = null;
}
/**
* 录完
*
* @param effective 有效否
*/
private void recordAchieve(boolean effective) {
// 停录加锁
stop();
camera.lock();
// 合并视频(一次录制从第二次录制状点停止开始合并)
if (saveVideoFilePath == null) {
saveVideoFilePath = currentVideoFilePath;
} else {
videoFileMerge();
}
if (effective) {
// 延迟一秒(视频合完)操作
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
finish();
}
}, 1000);
} else {
finish();
}
}
/**
* 释放摄像头资源
*/
private void releaseCamera() {
if (camera != null) {
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
}
/**
* 更新控制UI
*/
private void refreshControlUI() {
if (recordState == STATE_INIT) {
ivVideoRecordNougatControl.setEnabled(false);
ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_recording);
ivVideoRecordNougatPause.setEnabled(false);
// 归零开始计时
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
ctVideoRecordNougat.start();
// 一秒后可更
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatLight, 100);
AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatPause, 100);
ivVideoRecordNougatControl.setEnabled(true);
ivVideoRecordNougatPause.setVisibility(View.VISIBLE);
ivVideoRecordNougatPause.setEnabled(true);
}
}, 1000);
} else if (recordState == STATE_RECORDING) {
// 停止计时
ctVideoRecordNougat.stop();
// UI
ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
ivVideoRecordNougatPause.setVisibility(View.INVISIBLE);
ivVideoRecordNougatPause.setEnabled(false);
// 录暂停间隔
pauseTime = 0;
}
}
/**
* 更新暂停UI
*/
private void refreshPauseUI() {
if (recordState == STATE_RECORDING) {
ctVideoRecordNougat.stop();
ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_two);
pauseTime = SystemClock.elapsedRealtime();
} else if (recordState == STATE_PAUSE) {
ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_pause);
if (pauseTime == 0) {
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
} else {
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime() - (pauseTime - ctVideoRecordNougat.getBase()));
}
ctVideoRecordNougat.start();
}
}
/**
* 视频路径
*
* @return 路径
*/
private String videoFilePath() {
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo";
File file1 = new File(path);
if (!file1.exists()) {
file1.mkdirs();
}
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo" + File.separator;
}
/**
* 视频名
*
* @return 名
*/
private String videoFileName() {
return getString(R.string.videoTwo) + DateUtils.timeStamp() + ".mp4";
}
/**
* 视频合并
*/
private void videoFileMerge() {
new Thread(new Runnable() {
@Override
public void run() {
try {
String[] str = new String[]{saveVideoFilePath, currentVideoFilePath};
// 合并两视频至append.mp4
VideoUtils.videoAppend(videoFilePath() + "append.mp4", str);
File resultFile = new File(saveVideoFilePath);
File appendFile = new File(videoFilePath() + "append.mp4");
// 再移合成append.mp4至saveVideoFilePath
appendFile.renameTo(resultFile);
if (resultFile.exists()) {
appendFile.delete();
new File(currentVideoFilePath).delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
@OnClick({R.id.ivVideoRecordNougatControl, R.id.ivVideoRecordNougatPause, R.id.ivVideoRecordNougatLight})
public void onViewClicked(View view) {
switch (view.getId()) {
/*
控制
*/
case R.id.ivVideoRecordNougatControl:
if (recordState == STATE_INIT) {
// 视频存路径(configMediaRecorder设)
currentVideoFilePath = videoFilePath() + videoFileName();
// 开录
if (start()) {
refreshControlUI();
recordState = STATE_RECORDING;
}
} else if (recordState == STATE_RECORDING) {
recordAchieve(true);
} else if (recordState == STATE_PAUSE) {
camera.lock();
ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
finish();
}
break;
/*
暂停
*/
case R.id.ivVideoRecordNougatPause:
if (recordState == STATE_RECORDING) {
refreshPauseUI();
recordState = STATE_PAUSE;
// 取自动对焦
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
VideoRecordBeforeNougatActivity.this.camera.cancelAutoFocus();
}
}
});
stop();
// 合并视频(一次录制从第二次点暂停开始合并)
if (saveVideoFilePath == null) {
saveVideoFilePath = currentVideoFilePath;
} else {
videoFileMerge();
}
} else if (recordState == STATE_PAUSE) {
// 视频存路径(configMediaRecorder设)
currentVideoFilePath = videoFilePath() + videoFileName();
// 继录
if (start()) {
refreshPauseUI();
recordState = STATE_RECORDING;
}
}
break;
/*
闪光灯
*/
case R.id.ivVideoRecordNougatLight:
if (LightControl.isLightOn(camera)) {
LightControl.lightOff(camera);
ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_off));
} else {
LightControl.lightOn(camera);
ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_on));
}
break;
default:
break;
}
}
/**
* 返
*
* @param keyCode 键码值
* @param event 按键事件
* @return boolean
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
ZsDialog.materialContentDialogTwoClick(this, R.string.videoRecordingHint, R.string.continueRecord, R.string.back, R.color.fontHint)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
recordAchieve(false);
}
}).cancelable(false).show();
return true;
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
}
压缩
baseHintDialogCreate(this, 5, getString(R.string.compressing), false, this).show();
videoCompress(path);
/**
* 视频压缩(10:1)
*
* @param videoPath 视频路径
*/
public void videoCompress(final String videoPath) {
final FfmpegTool ffmpegTool = FfmpegTool.getInstance(this);
File file = new File(videoPath);
if (file.exists()) {
LogUtils.e("视频压缩中");
// 压缩存路径
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dfs" + File.separator + "VideoCompress";
File file1 = new File(path);
if (!file1.exists()) {
file1.mkdirs();
}
ffmpegTool.compressVideo(videoPath, path + File.separator, 3, new FfmpegTool.VideoResult() {
@Override
public void clipResult(int i, String s, String s1, boolean b, int i1) {
if (!b) {
baseHintDialogChange("视频压缩失败", getString(R.string.know), 1);
}
String[] dataStr = s1.split("/");
StringBuilder stringBuilder = new StringBuilder(Environment.getExternalStorageDirectory().getAbsolutePath());
for (int ii = Magic.INT_SI; ii < dataStr.length; ii++) {
stringBuilder.append("/").append(dataStr[ii]);
}
videoFileCompressPath = s1;
LogUtils.e("视频压缩成功" + stringBuilder);
LogUtils.e("压缩前视频", s);
LogUtils.e("压缩后视频", s1);
baseHintDialogDestroy();
}
});
} else {
baseHintDialogChange("未找到视频,重新操作", getString(R.string.know), 3);
}
}
MediaRecorder API支持暂停续录。
app
->libs
isoviewer-1.0-RC-28.jar
清单文件
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
布局
如上
主代码
package com.self.zsp.dfs.dataentry.add;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Chronometer;
import android.widget.ImageView;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.self.zsp.dfs.R;
import java.io.File;
import java.io.IOException;
import base.BaseActivity;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import util.AnimationUtils;
import util.DateUtils;
import util.LightControl;
import util.LogUtils;
import value.Flag;
import value.Magic;
import widget.ZsDialog;
/**
* @decs: 录视频(7.0后)
* @date: 2018/8/16 16:36
* @version: v 1.0
*/
public class VideoRecordAfterNougatActivity extends BaseActivity {
private static final int STATE_INIT = 0;
private static final int STATE_RECORDING = 1;
private static final int STATE_PAUSE = 2;
@BindView(R.id.svVideoRecordNougat)
SurfaceView svVideoRecordNougat;
@BindView(R.id.ivVideoRecordNougatLight)
ImageView ivVideoRecordNougatLight;
@BindView(R.id.ctVideoRecordNougat)
Chronometer ctVideoRecordNougat;
@BindView(R.id.ivVideoRecordNougatControl)
ImageView ivVideoRecordNougatControl;
@BindView(R.id.ivVideoRecordNougatPause)
ImageView ivVideoRecordNougatPause;
/**
* Camera、SurfaceHolder、MediaRecorder
*/
private Camera camera;
private SurfaceHolder surfaceHolder;
private MediaRecorder mediaRecorder;
/**
* 录标识
*/
private int recordState;
/**
* 录暂停间隔
*/
private long pauseTime = 0;
/**
* 路径
*/
private String saveVideoFilePath;
/**
* SurfaceHolder.Callback
*/
private SurfaceHolder.Callback mSurfaceCallBack = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
stepCamera();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
if (surfaceHolder.getSurface() == null) {
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
releaseCamera();
}
};
/**
* MediaRecorder.OnInfoListener
*/
private MediaRecorder.OnInfoListener onInfoListener = new MediaRecorder.OnInfoListener() {
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
stop(true);
}
}
};
/**
* MediaRecorder.OnErrorListener
*/
private MediaRecorder.OnErrorListener onErrorListener = new MediaRecorder.OnErrorListener() {
@Override
public void onError(MediaRecorder mediaRecorder, int what, int extra) {
try {
if (mediaRecorder != null) {
mediaRecorder.reset();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
@Override
protected void initContentView(Bundle savedInstanceState) {
setContentView(R.layout.activity_video_record_nougat);
ButterKnife.bind(this);
}
@Override
protected void stepUI() {
}
@Override
protected void initConfiguration() {
stepSurfaceHolder();
stepCamera();
}
@Override
protected void initData() {
}
@Override
protected void startLogic() {
}
@Override
protected void setListener() {
}
/**
* 初始SurfaceHolder
*/
private void stepSurfaceHolder() {
surfaceHolder = svVideoRecordNougat.getHolder();
// SurfaceHolder无需维护自己缓冲区
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// 分辨率
surfaceHolder.setFixedSize(320, 280);
// 该组件不允屏幕自关
surfaceHolder.setKeepScreenOn(true);
// 回调接口
surfaceHolder.addCallback(mSurfaceCallBack);
}
/**
* 初始摄像头
*/
private void stepCamera() {
if (camera != null) {
releaseCamera();
}
camera = Camera.open();
if (camera == null) {
toastShort(getString(R.string.cameraNotObtain));
return;
}
try {
// 相机绑SurfaceHolder
camera.setPreviewDisplay(surfaceHolder);
// 配CameraParams
cameraParamsConfig();
// 启相机预览
camera.startPreview();
} catch (IOException e) {
// 某些机型兼容出错(适配特定机型)
LogUtils.e("Error starting camera preview: " + e.getMessage());
}
}
/**
* 配CameraParams
*/
private void cameraParamsConfig() {
Camera.Parameters params = camera.getParameters();
// 相机横竖屏(竖屏转90°)
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
params.set("orientation", "portrait");
camera.setDisplayOrientation(90);
} else {
params.set("orientation", "landscape");
camera.setDisplayOrientation(0);
}
// 聚焦模式
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
// 缩Recording启时
params.setRecordingHint(true);
// 影像稳定能力
if (params.isVideoStabilizationSupported()) {
params.setVideoStabilization(true);
}
camera.setParameters(params);
}
/**
* 配MediaRecorder
*/
private void configMediaRecorder() {
if (mediaRecorder == null) {
mediaRecorder = new MediaRecorder();
}
mediaRecorder.reset();
mediaRecorder.setCamera(camera);
// SurfaceView预览
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
// 采集声音
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 采集图像
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 视频、音频输出格式(mp4)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
// 音频编码格式
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 图像编码格式
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 立体声
mediaRecorder.setAudioChannels(2);
// 最大录时(毫秒)
mediaRecorder.setMaxDuration(20 * 1000);
// 最大录大小(字节)
/*mediaRecorder.setMaxFileSize(1024 * 1024);*/
// 音频一秒含多少数据位
CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
mediaRecorder.setAudioEncodingBitRate(44100);
if (camcorderProfile.videoBitRate > Magic.INT_ER * Magic.INT_YLES * Magic.INT_YLES) {
mediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024);
} else {
mediaRecorder.setVideoEncodingBitRate(1024 * 1024);
}
mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
// 观看存后视频角度(顺时)(默逆90度,顺时图像正常显)
mediaRecorder.setOrientationHint(90);
// 录像分辨率
mediaRecorder.setVideoSize(352, 288);
// 录像视频输出地址
mediaRecorder.setOutputFile(saveVideoFilePath);
// 信息监听
mediaRecorder.setOnInfoListener(onInfoListener);
// 错误监听
mediaRecorder.setOnErrorListener(onErrorListener);
}
/**
* 开始
*/
private boolean start() {
// 录前相机解锁
camera.unlock();
// 配MediaRecorder
configMediaRecorder();
try {
mediaRecorder.prepare();
mediaRecorder.start();
} catch (IOException e) {
return false;
}
return true;
}
/**
* 停止
*
* @param effective 有效否
*/
private void stop(boolean effective) {
// 停止计时
ctVideoRecordNougat.stop();
ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
// 闪光灯
if (LightControl.isLightOn(camera)) {
LightControl.lightOff(camera);
}
// 设后不crash
mediaRecorder.setOnErrorListener(null);
mediaRecorder.setPreviewDisplay(null);
// 停录
mediaRecorder.stop();
mediaRecorder.reset();
// 释放
mediaRecorder.release();
mediaRecorder = null;
// 相机加锁
camera.lock();
if (effective) {
setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
finish();
} else {
finish();
}
}
/**
* 释放摄像头资源
*/
private void releaseCamera() {
if (camera != null) {
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
}
/**
* 更新控制UI
*/
private void refreshControlUI() {
ivVideoRecordNougatControl.setEnabled(false);
ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_recording);
// 归零开始计时
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
ctVideoRecordNougat.start();
// 一秒后可更
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatLight, 100);
AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatPause, 100);
ivVideoRecordNougatControl.setEnabled(true);
}
}, 1000);
}
/**
* 更新暂停UI
*/
private void refreshPauseUI() {
if (recordState == STATE_RECORDING) {
ctVideoRecordNougat.stop();
ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_two);
pauseTime = SystemClock.elapsedRealtime();
} else if (recordState == STATE_PAUSE) {
ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_pause);
if (pauseTime == 0) {
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
} else {
ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime() - (pauseTime - ctVideoRecordNougat.getBase()));
}
ctVideoRecordNougat.start();
}
}
/**
* 视频路径
*
* @return 路径
*/
private String videoFilePath() {
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo";
File file1 = new File(path);
if (!file1.exists()) {
file1.mkdirs();
}
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo" + File.separator;
}
@TargetApi(Build.VERSION_CODES.N)
@OnClick({R.id.ivVideoRecordNougatControl, R.id.ivVideoRecordNougatPause, R.id.ivVideoRecordNougatLight})
public void onViewClicked(View view) {
switch (view.getId()) {
/*
控制
*/
case R.id.ivVideoRecordNougatControl:
if (recordState == STATE_INIT) {
// 视频存路径(configMediaRecorder设)
saveVideoFilePath = videoFilePath() + getString(R.string.videoTwo) + DateUtils.timeStamp() + ".mp4";
// 开录
if (start()) {
refreshControlUI();
recordState = STATE_RECORDING;
}
} else if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
stop(true);
}
break;
/*
暂停
*/
case R.id.ivVideoRecordNougatPause:
if (recordState == STATE_RECORDING) {
refreshPauseUI();
recordState = STATE_PAUSE;
// 取自动对焦
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
VideoRecordAfterNougatActivity.this.camera.cancelAutoFocus();
}
}
});
mediaRecorder.pause();
} else if (recordState == STATE_PAUSE) {
refreshPauseUI();
recordState = STATE_RECORDING;
mediaRecorder.resume();
}
break;
/*
闪光灯
*/
case R.id.ivVideoRecordNougatLight:
if (LightControl.isLightOn(camera)) {
LightControl.lightOff(camera);
ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_off));
} else {
LightControl.lightOn(camera);
ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_on));
}
break;
default:
break;
}
}
/**
* 返
*
* @param keyCode 键码值
* @param event 按键事件
* @return boolean
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
ZsDialog.materialContentDialogTwoClick(this, R.string.videoRecordingHint, R.string.continueRecord, R.string.back, R.color.fontHint)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
stop(false);
}
}).cancelable(false).show();
return true;
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
}
压缩
如上
于6.0错,需适配
java.lang.NoSuchMethodError: No virtual method resume()V in class Landroid/media/MediaRecorder; or its super classes (declaration of 'android.media.MediaRecorder' appears in /system/framework/framework.jar)