之前我们说了音频的录制和播放,那么现在说说视频的录制。其实视频的录制还是用的MediaRecorder,并且配合Camera进行操作。所以这里我们详细说明一下视频录制的做法。至于视频播放,由于涉及到全屏切换,生命周期管理等一系列问题,这里不写博客说了,感兴趣的同学可以在我的资源中找到我的框架,里面有关于视频的播放案例。
好了,我们开始录制视频的学习。
1.媒体播放器MediaRecorder常用方法(录音与录像通用)
- reset:重置录制资源。
- prepare:准备录制。
- start:开始录制。
- stop:结束录制。
- release:释放录制资源。
- setOnErrorListener:设置错误监听器。可监听服务器异常和未知错误的事件。需要实现接口MediaRecorder.OnErrorListener的onError方法。
- setOnInfoListener:设置信息监听器。可监听录制结束事件,包括达到录制时长或达到录制大小。需要实现接口MediaRecorder.OnInfoListener的onInfo方法。
- setMaxDuration:设置可录制的最大时长,单位毫秒。
- setMaxFileSize:设置可录制的最大文件大小,单位字节。
- setOutputFile:设置输出文件的路径。
2.MeidaRecorder用于录像的常用方法
- setCamera:设置相机对象。
- setPreviewDisplay:设置预览界面。预览界面对象可通过SurfaceHolder对象的getSurface方法获得。
- setOrientationHint:设置预览的角度。跟拍照一样设置为90,表示界面从水平方向到垂直方向旋转90度。
- setVideoSource:设置视频来源。一般使用VideoSource.CAMERA表示摄像头。
- setOutputFormat:设置媒体输出格式。媒体输出格式的取值见表1。
表1 | |||
OutputFormat类的输出格式 | 格式分类 | 扩展名 | 格式说明 |
AMR_NB | 音频 | .amr | 窄带格式 |
AMR_WB | 音频 | .amr | 宽带格式 |
AAC_ADTS | 音频 | .aac | 高级的音频传输流格式 |
MPEG_4 | 视频 | .mp4 | MPEG4格式 |
THREE_GPP | 视频 | .3gp | 3GP格式 |
- setVideoEncoder:设置视频编码器。一般使用VideoEncoder.MPEG_4_SP表示MPEG4编码。该方法在setOutputFormat方法之后调用,否则会报错java.lang.IllegalStateException。
- setVideoSize:设置视频的分辨率。
- setVideoFrameRate:设置视频每秒录制的帧数。越大视频越连贯,当然最终生成的视频文件也越大。
- setVideoEncodingBitRate:设置视频每秒录制的字节数。越大视频越清晰,setVideoFrameRate与setVideoEncodingBitRate设置一个即可。
3.代码示例
权限申请
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
布局代码activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fr_root"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/bt_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="录制视频"/>
</RelativeLayout>
MainActivity.java
public class MainActivity extends WaterPermissionActivity implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {
private TextView bt_record;
private FrameLayout fr_root;
private SurfaceHolder mHolder; // 声明一个表面持有者对象
private Camera mCamera; // 声明一个相机对象
private MediaRecorder mMediaRecorder;
private String moviePath;//视频路径
@Override
protected MvcBaseModel getModelImp() {
return null;
}
@Override
protected int getContentLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initWidget() {
fr_root = findViewById(R.id.fr_root);
bt_record = findViewById(R.id.bt_record);
bt_record.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//按下,开始录制
getPath();//初始化视频路径
initRecord();//开始录制
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//松开,停止录制
cancelRecord(); // 取消录制操作
freeCamera(); // 释放相机资源
}
return true;
}
});
requestPermission(WRITE_EXTERNAL_STORAGE);
}
@Override
protected void doSDWrite() {
requestPermission(READ_EXTERNAL_STORAGE);
}
@Override
protected void doSDRead() {
requestPermission(CAMERA);
}
@Override
protected void doCamera() {
requestPermission(RECORD);
}
@Override
protected void doRecord() {
//这里为了请求好动态权限再让Surface初始化才这么弄得,可以在进入录制页面就请求好动态权限,就不用这么写了。
SurfaceView surfaceView = new SurfaceView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
surfaceView.setLayoutParams(params);
fr_root.addView(surfaceView);
// 获取表面视图的表面持有者
mHolder = surfaceView.getHolder();
// 给表面持有者添加表面变更监听器
mHolder.addCallback(mSurfaceCallback);
}
/**
* 初始化录制操作,开始录制调用这个方法
*/
private void initRecord() {
mMediaRecorder = new MediaRecorder(); // 创建一个媒体录制器
mMediaRecorder.setCamera(mCamera); // 设置媒体录制器的摄像头
mMediaRecorder.setOnErrorListener(this); // 设置媒体录制器的错误监听器
mMediaRecorder.setOnInfoListener(this); // 设置媒体录制器的信息监听器
mMediaRecorder.setPreviewDisplay(mHolder.getSurface()); // 设置媒体录制器的预览界面
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // 设置视频源为摄像头
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 设置媒体的输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 设置媒体的音频编码器
// 如果录像报错:MediaRecorder start failed: -19
// 试试把setVideoSize和setVideoFrameRate注释掉,因为尺寸设置必须为摄像头所支持,否则报错
mMediaRecorder.setVideoSize(2280, 1080); // 设置视频的分辨率
// mMediaRecorder.setVideoFrameRate(16); // 设置视频每秒录制的帧数
// setVideoFrameRate与setVideoEncodingBitRate设置其一即可
mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 512); // 设置视频每秒录制的字节数
mMediaRecorder.setOrientationHint(90); // 输出旋转90度,也就是保持竖屏录制
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); // 设置媒体的视频编码器
mMediaRecorder.setMaxDuration(10 * 1000); // 设置媒体的最大录制时长
// mMediaRecorder.setMaxFileSize(1024*1024*10); // 设置媒体的最大文件大小
// setMaxFileSize与setMaxDuration设置其一即可
mMediaRecorder.setOutputFile(moviePath); // 设置媒体文件的保存路径
try {
mMediaRecorder.prepare(); // 媒体录制器准备就绪
mMediaRecorder.start(); // 媒体录制器开始录制
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 定义一个表面持有者的变更监听器
*/
private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
// 在表面视图创建时触发
public void surfaceCreated(SurfaceHolder holder) {
initCamera(); // 初始化相机
}
// 在表面视图变更时触发
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// 在表面视图销毁时触发
public void surfaceDestroyed(SurfaceHolder holder) {
freeCamera(); // 释放相机资源
}
};
/**
* 释放相机资源
*/
private void freeCamera() {
if (mCamera != null) {
mCamera.stopPreview(); // 停止预览
mCamera.lock(); // 锁定相机,即关闭相机
mCamera.release(); // 释放相机资源
mCamera = null;
}
}
/**
* 初始化相机操作
*/
private void initCamera() {
if (mCamera != null) {
freeCamera();
}
try {
// 打开摄像头,默认后置摄像头
mCamera = Camera.open();
// 设置相机的展示角度
mCamera.setDisplayOrientation(90);
// 设置相机的预览界面
mCamera.setPreviewDisplay(mHolder);
// 开始预览画面
mCamera.startPreview();
// 解锁相机,即打开相机
mCamera.unlock();
} catch (Exception e) {
e.printStackTrace();
freeCamera();
}
}
/**
* 录制前创建一个空文件并获取路径
*/
private void getPath() {
List<String> list = new ArrayList<>();
list.add("record");
String dirPath = PathGetUtil.getLongwayPath(this, list);
File fileDir = new File(dirPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File fileVoice = new File(dirPath, "movie" + System.currentTimeMillis() + ".mp4");
if (!fileVoice.exists()) {
try {
fileVoice.createNewFile();
moviePath = fileVoice.getPath();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onError(MediaRecorder mr, int what, int extra) {
if (mr != null) {
mr.reset(); // 重置媒体录制器
}
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
// 录制达到最大时长,或者达到文件大小限制,都停止录制
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
|| what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
cancelRecord();
}
}
/**
* 取消录制操作,停止录制的时候调用该方法
*/
private void cancelRecord() {
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null); // 错误监听器置空
mMediaRecorder.setPreviewDisplay(null); // 预览界面置空
try {
mMediaRecorder.stop(); // 媒体录制器停止录制
} catch (Exception e) {
e.printStackTrace();
}
mMediaRecorder.release(); // 媒体录制器释放资源
mMediaRecorder = null;
}
}
}
同样的,我们录制出来的视频由于没有进行处理,视频质量不是很高,很不清晰。所以如果大家想更加清晰的话,可以考虑把设置的Camera部分换成Camera2或者更好用的CameraX。我这里就不进行具体操作了,原理都是一样的,大家感兴趣可以进行尝试。