大家好,又见面了。在前几篇中,我们通过2种方式实现了仿抖音的翻页切换视频,仿抖音列表播放视频功能,这一篇,我们来说说视频的录制。
- 【Android 进阶】仿抖音系列之翻页上下滑切换视频(一)
- 【Android 进阶】仿抖音系列之列表播放视频(二)
- 【Android 进阶】仿抖音系列之列表播放视频(三)
- 【Android 进阶】仿抖音系列之翻页上下滑切换视频(四)
- 【Android 进阶】仿抖音系列之视频预览和录制(五)
主流的视频录制,一般都采用的是FFmpeg 例如 腾讯短视频,由于FFmpeg的学习成本较大,这里我们就说说系统自带的MediaRecorder。
首先,需要实现摄像头的预览,这里我们就用SurfaceView 。
<android.support.constraint.ConstraintLayout 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=".activity.RecordActivity">
<SurfaceView
android:id="@+id/sv_record"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"
app:layout_constraintBottom_toBottomOf="parent" />
<Button
android:id="@+id/btn_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="switch"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
android.support.constraint.ConstraintLayout>
-
- 实现SurfaceHolder.Callback,重写surfaceCreated、surfaceChanged、surfaceDestroyed 3个方法
其中surfaceCreated是SurfaceView创建成功时回调,可以在这里开始预览;surfaceChanged是SurfaceView变化时回调,这里不做处理;surfaceDestroyed 是SurfaceView销毁时回调,可以在这里释放资源
surfaceHolder = svRecord.getHolder();
surfaceHolder.addCallback(this);
svRecord.setFocusable(true);
svRecord.setKeepScreenOn(true);
svRecord.setFocusableInTouchMode(true);
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceHolder = holder;
requestPermision();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
surfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreview();
startRecord();
}
-
- 开始预览,首先是请求权限,这里使用的是easypermissions,也可以使用其他的封装库
private void requestPermision() {
String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
if (EasyPermissions.hasPermissions(this, perms)) {
startPreview();
} else {
EasyPermissions.requestPermissions(this, "我们的app需要以下权限",
RC_STORAGE, perms);
}
}
需要实现EasyPermissions.PermissionCallbacks,以及处理授权回调
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
startPreview();
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
finish();
}
private void startPreview() {
if (svRecord == null || surfaceHolder == null) {
return;
}
if (camera == null) {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
currentCameraType = 1;
btnSwitch.setText("后");
}
try {
camera.setPreviewDisplay(surfaceHolder);
Camera.Parameters parameters = camera.getParameters();
camera.setDisplayOrientation(90);
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes != null) {
for (String mode : focusModes) {
mode.contains("continuous-video");
parameters.setFocusMode("continuous-video");
}
}
List<Camera.Size> sizes = parameters.getSupportedVideoSizes();
if (sizes.size() > 0) {
size = sizes.get(sizes.size() - 1);
}
camera.setParameters(parameters);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
其中,Camera.CameraInfo.CAMERA_FACING_BACK 代表后置摄像头,保险起见,应该检查设备是否有后置摄像头,这里就不检查了;还需要注意,当时后置时,应该旋转摄像头90度,否则预览是斜的
-
- 切换摄像头,这里如上代码所见,使用了一个int 型变量currentCameraType来记录前后摄像头;
stopPreview();
if (currentCameraType == 1) {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
currentCameraType = 2;
btnSwitch.setText("前");
} else {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
currentCameraType = 1;
btnSwitch.setText("后");
}
startPreview();
private void stopPreview() {
if (camera == null) {
return;
}
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
需要注意,需要先停止预览,切换摄像头之后,再开始预览;
到这里摄像头已经实现了摄像头预览。
开始录制视频
private void startRecord() {
if (mediaRecorder == null) {
mediaRecorder = new MediaRecorder();
}
temFile = getTemFile();
try {
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoSize(size.width, size.height);
mediaRecorder.setVideoFrameRate(24);
mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
mediaRecorder.setOutputFile(temFile.getAbsolutePath());
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
if (currentCameraType == 1) {
mediaRecorder.setOrientationHint(90);
} else {
mediaRecorder.setOrientationHint(270);
}
mediaRecorder.prepare();
mediaRecorder.start();
isRecording = true;
showtoast("开始录制");
} catch (Exception e) {
e.printStackTrace();
}
}
private File getTemFile() {
String basePath = Environment.getExternalStorageDirectory().getPath() + "/doudemo/";
File baseFile = new File(basePath);
if (!baseFile.exists()) {
baseFile.mkdirs();
}
File temp = new File(basePath + System.currentTimeMillis() + ".mp4");
return temp;
}
这里的坑还是比较多的
- 1.首先需要解锁相机,调用camera.unlock();
- 2.关于视频的size ,应该通过parameters.getSupportedVideoSizes(); 获取该手机支持的宽高,如果设置手机不支持,会报错;
- 3.注意各个方法调用顺序,否则会报一些奇怪的错,无奈…
- 4.摄像机角度问题,后置时,旋转90度,前置时,旋转270度
停止录制,需要锁定相机,需要预览时,跳到预览(播放)界面
private void stopRecord(boolean delete) {
if (mediaRecorder == null) {
return;
}
if (myTimer != null) {
myTimer.cancel();
}
try {
mediaRecorder.stop();
} catch (Exception e) {
e.printStackTrace();
}
mediaRecorder.reset();
mediaRecorder.release();
mediaRecorder = null;
if (camera != null) {
camera.lock();
}
isRecording = false;
if (delete) {
if (temFile != null && temFile.exists()) {
temFile.delete();
}
} else {
stopPreview();
Intent intent = new Intent(RecordActivity.this, PrepareActivity.class);
intent.putExtra(PrepareActivity.VIDEO_PATH, temFile.getPath());
startActivity(intent);
}
showtoast("停止录制");
}
最后,献上完整代码。Github