概览
Android 进行录像的一个简单方法是:创建一个MediaStore.ACTION_VIDEO_CAPTURE的Intent来调起一个已有的相机应用。
但有时候我们需要自己定义录制界面,或者需要一些高级的功能,那么Intent方式就不能满足我们的要求的。
因此,我们可以使用Android framework为我们提供的Camera和Camera2 APIs来直接操作相机设备。
本章我们主要介绍,如何使用Camera 、Camera2以及MediaRecorder来录制视频。
权限设置
请求使用相机的权限:
<uses-permission android:name="android.permission.CAMERA" />
在配置文件中声明使用相机功能:
<uses-feature android:name="android.hardware.camera" />
具体相机功能可以查看以下链接:
在配置文件中声明使用相机的自动对焦和闪光灯功能:
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.camera.flash" android:required="false"/>
请求访问闪光灯权限:
<uses-permission android:name="android.permission.FLASHLIGHT"/>
要在SD卡上存储获取读取视频 :
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
录制视频和音频权限:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
注意:Android6.0及以上需动态权限申请
Camera
涉及相关功能类:
Camera:管理相机API(开启、关闭预览、设置相机参数)
Camera.CameraInfo:相机信息(前置or后置摄像头等)
//i为摄像头的id,通过 Camera.getNumberOfCameras()
Camera.getCameraInfo(i, mCameraInfo);//获取对于摄像头的相机信息
Camera.Parameters :相机特性 (自动对焦、闪光、预览大小、图片格式、图片大小等)
Camera2
涉及相关功能类:
CameraManager:摄像头管理类,用于检测、打开系统摄像头,通过getCameraCharacteristics(cameraId)
可以获取摄像头特征
CameraCharacteristics :相机特性类,例如,是否支持自动调焦,是否支持zoom,是否支持闪光灯一系列特征
CameraDevice:相机设备,类似早期的camera类
CameraRequest:一次捕获的请求,可以设置一些列的参数,用于控制预览和拍照参数,例如:对焦模式,曝光模式,zoom参数等等
CameraCaptureSession:用于创建预览、拍照的Session类。通过它的setRepeatingRequest()
方法请求连续预览 , 通过它的capture()
方法控制拍照动作。
MediaRecorder
用于录制视频和音频的一个类。基于下图的状态机类控制录制过程:
[图片上传失败...(image-4b940a-1541081884311)]
状态说明:
Initial:当new MediaRecorder()或者在除Released状态外的其它状态通过调用reset()方法后,MediaRecorder进入Initial状态
Initialized:在Initial状态下,通过设定视频源setVideoSource()或者音频源setAudioSource()之后,状态切换为Initialized状态
DataSourceConfigured:在Initialized状态下,可以通过setOutputFormat()方法设置输出格式,此时
MediaRecorder转换为DataSourceConfigured状态。 数据源配置状态,这期间可以设定编码方式、输出文件、视频大小、视频帧率、预览显示等等。
Prepared:装备就绪状态,在DataSourceConfigured状态下,调用prepare()方法进入该状态。
Recording:录制状态,在Prepared状态下,调用start()方法进入该状态。通过stop()方法或reset()方法回到Initial状态。
Released:释放状态,可以通过在Initial状态调用release()方法来进入这个状态,这时将会释放和MediaRecorder对象关联的所有资源。
Error:错误状态,当错误发生的时候进入这个状态,它可以通过reset()方法进入Initial状态。
注意:使用MediaRecorder进行视频音频录制时,必须严格按照上面的状态图规定的函数先后调用顺序,在不同的状态调用不同的函数,否则会抛异常IllegalStateException 。
VCamera
概述:提供视频录制、后滤镜、炫酷 MV 主题、后期强大的 FFmpeg命令行支持,可实现水印、音量控制等诸多功能
音视频单独采集
ts片段转mp4
ffmpeg -i concat:/sdcard/WeiXinRecordedDemo/1531060034234/0.ts -c copy -bsf:a aac_adtstoasc -y /sdcard/WeiXinRecordedDemo/1531060034234/0.mp4
预览功能
预览一般步骤流程:
流程说明:
- 监测和访问相机:检查相机是否存在,若存在则请求访问
- 获取、设置相关参数
- 设置一个预览类,如SurfaceView or TextureView
- 设置监听,一旦Surface可用,我们就可以开启预览。当Surface销毁我们就关闭预览
- 界面销毁时,释放预览相关资源和正确释放相机
Camera和Camera2各自是如何实现预览功能:
请求访问相机:
Camera实现:
//获取设备物理可用Camera
int count = Camera.getNumberOfCameras();
//检测是哪个相机
for (int i = 0; i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == mFacing) {
mCameraId = i;
return true;
}
}
//开启
mCamera = Camera.open(mCameraId);
Camera2实现:
//获取设备物理可用Camera
final String[] ids = mCameraManager.getCameraIdList();
...省略部分代码
//找到我们想要的那个Camera
for (String id : ids) {
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
...
Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING);
if (internal == null) {
return false;
}
if (internal == internalFacing) {
mCameraId = id;
mCameraCharacteristics = characteristics;
return true;
}
}
通过CameraManager对象,调用openCamera方法开启相机:
try {
mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null);
return true;
} catch (CameraAccessException e) {
//throw new RuntimeException("Failed to open camera: " + mCameraId, e);
return false;
}
需要三个参数:
String cameraId:相机设备的唯一标识符
CameraDevice.StateCallback callback:相机打开的回调方法
Handler handler:回调方法在哪个线程处理
获取相关参数:
相机特性:
1. Camera.Parameters mCameraParameters = mCamera.getParameters()
2. CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
获取支持的预览大小列表、视频大小列表、图片大小列表:
1. mCameraParameters = mCamera.getParameters();
List<Size> previewSizeList = mCameraParameters.getSupportedPreviewSizes();
视频列表:
List<Size> videoSizeList = mCameraParameters.getSupportedVideoSizes();
图片列表:
List<Size> pictureSizeList = mCameraParameters.getSupportedPictureSizes()
2. StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] previewSizeList = map.getOutputSizes(mPreview.getOutputClass());
视频列表:
Size[] videoSizeList = map.getOutputSizes(MediaRecorder.class)
图片列表:
Size[] pictureSizeList = map.getOutputSizes(SurfaceTexture.class)
设置参数:
Camera主要设置在Camera.Parameters
mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mCamera.setParameters(mCameraParameters);
Camera2主要设置在CaptureRequest.Builder
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
设置预览View:
Camera实现:
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
...
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
...
} else {//Android 4.0(API 14)及以上
mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
}
} catch (IOException e) {
//throw new RuntimeException(e);
}
Camera2实现:
try {
mPreview.setBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//Camera2 API有效
Surface surface = mPreview.getSurface();
//图像内容将输出到Preview的surface和ImageReader的surface中
mPreviewRequestBuilder.addTarget(surface);
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
} catch (CameraAccessException e) {
}
开启预览:
Camera实现:
mCamera.startPreview();
Camera2实现:
//调用CameraDevicea的createCaptureRequest创建一个请求
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), mSessionCallback, null);
//调用如下方法才能不断预览
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
接收预览每帧数据:
//Camera实现:
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
mCallback.onPreviewData(data);//预览每帧数据回调
}
});
//Camera2
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.YUV_420_888, /*maxImages*/2);//如果是预览则图片格式不要设置为JPEG,否则会一直拍照,造成预览卡
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
mCallback.onPreviewData(bytes);//预览每帧数据回调
image.close();//记得关闭,否则 抛出异常java.lang.IllegalStateException: maxImages (2) has // already been acquired, call #close before acquiring more.
}
};
停止预览:
//Camera实现:
if (mCamera != null) {
mCamera.stopPreview();
}
//Camera2
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
正确释放相机资源:
//Camera实现:
public void release() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
//Camera2
public void release() {
if (mCamera != null) {
mCamera.close();
mCamera = null;
}
}
视频录制
Camera实现视频录制,必须严格按照一定的调用顺序。具体可查看Capturing videos
- 开启相机:Camera.open()
- 设置预览View:setPreviewDiaplay()
- 开启预览:startPreview()
- 开始录制:
- unlock camera
- 配置MediaRecorder
- MediaRecorder prepare
- 开启录制
- 停止录制
- 停止预览
- 释放相机
Camera和Camera2使用MediaRecorder录制视频的流程对比:
网友评论