Camera2适用是Android5.0(API 级别 21)或更高版本的所有设备
总览
1、加入权限
(所需要的的权限有:读写文件权限、拍摄照片和录制视频权限、 录音权限)
1.1 系统Android6.0以下
- 系统Android6.0以下的版本的直接在AndroidManifest.xml加入*
// 读写权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
// 拍摄照片和录制视频权限
<uses-permission android:name="android.permission.CAMERA"/>
// 录音权限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
1.2 系统Android6.0以上或者更高版本
- 系统Android6.0或者更高版本需要动态获取权限(一般应用安装第一次打开获取,只获取一次就好)*
//先把所需要的的权限用数组装起来
//READ_CALENDAR、WRITE_EXTERNAL_STORAGE : 读写权限
//CAMERA : 拍摄照片和录制视频权限
//RECORD_AUDIO: 录音权限
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_CALENDAR",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO"};
public void verifyStoragePermissions(Activity activity) {
//然后通过其中的一个函数来申请
int checkSelfPermission = ActivityCompat.checkSelfPermission(activity, PERMISSIONS_STORAGE[0]);
if (checkSelfPermission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
}
}
2、实现功能列表
2.1 Camera2基础(入门篇)
2.1.1 显示预览图
- 利用渲染图层中TextureView.SurfaceTextureListener监听,以下为监听的方法解析
方法 | 解析 |
---|---|
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) | 当TextureView准备好使用Surface的SurfaceTexture时调用 (在这个方法里面做打开相机操作) |
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) | SurfaceTexture的缓冲区大小更改时调用 |
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) | 在将SurfaceTexture要销毁指定的对象时调用 |
onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) | SurfaceTexture通过更新指定的值 时调用 |
- 接收有关摄像头设备状态的更新CameraDevice.StateCallback(其中包括:摄像头打开,摄像头关闭,打开摄像头错误原因)
方法 | 解析 |
---|---|
onOpened( CameraDevice camera) | 照相机设备完成打开时调用的方法 |
onDisconnected(CameraDevice camera) | 当照相机设备不再可用时调用的方法(一般是其他应用占用了会回调,释放资源,调用camera.close()) |
onError(CameraDevice camera, int error) | 相机设备遇到严重错误时调用的方法(一般会释放资源,调用camera.close()) |
- 在onOpened(CameraDevice camera) 得到CameraCaptureSession(一个用于接收有关摄像机捕获会话状态更新的回调对象)
/**
* 渲染图层
*/
private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
surface = new Surface(surfaceTexture);
openCameraBase();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
};
/**
* 摄像头状态回调
*/
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
//成员变量
device = camera;
try {
device.createCaptureSession(Collections.singletonList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
cameraCaptureSession = session;
try {
//创建一个请求
//TEMPLATE_PREVIEW:创建适合摄像机预览窗口的请求
//TEMPLATE_MANUAL:用于捕获参数的直接应用程序控制的基本模板
//TEMPLATE_RECORD:创建适合视频录制的请求
//TEMPLATE_STILL_CAPTURE:创建适合静态图像捕获的请求
//TEMPLATE_VIDEO_SNAPSHOT:创建一个适合在录制视频时捕获静态图像的请求
//TEMPLATE_ZERO_SHUTTER_LAG:创建一个适合零快门滞后仍然捕获的请求
builder = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//在此请求的目标列表中添加一个表面
builder.addTarget(surface);
cameraCaptureSession.setRepeatingRequest(builder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Log.i(TAG, "onDisconnected: " + "摄像头已打开");
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
device.close();
Log.i(TAG, "onDisconnected: " + "摄像头已关闭");
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
}
};
/**
* 打开摄像头
*/
private void openCameraBase() {
//摄像头管理 获取系统服务
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//判断系统摄像机权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//权限处理
verifyStoragePermissions(getContext());
}
try {
//打开摄像头
assert cameraManager != null;
cameraManager.openCamera(cameraDirection, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
注意:当切换到后台再进来会发现预览图画面卡着问题(是因为相机关闭了,渲染图层TextureView.SurfaceTextureListener没有清除图层,所以会以为画面卡着),解决方案如下:
/**
* 防止其他应用占用摄像头,所以每一次进去都新打开摄像头
*/
@Override
protected void onResume() {
super.onResume();
openCameraBase();
}
/**
* (关闭相机)释放资源
*/
@Override
protected void onPause() {
super.onPause();
if (device != null) {
device.close();
}
}
2.1.1 摄像头方向切换
在 cameraManager.openCamera(cameraDirection, stateCallback, null);中cameraDirection是控制摄像头的方向(摄像头方向:“1”:为前置 “0”:为后置 (默认值为0))
//切换摄像头
binding.button2.setOnClickListener(v -> {
if (device.getId().equals(index)) {
cameraDirection = "1";
} else {
cameraDirection = "0";
}
//先关闭,再打开
device.close();
openCameraBase();
});
注意:记得先关闭当前摄像头,再打开要切换的摄像头
2.1.2 预览图片出现拉伸情况处理
- 一、自定义TextureView,重写宽高
- 二、初始化控件
private int ratioW = 0;
private int ratioH = 0;
public void setAspectRation(int width, int height){
if (width < 0 || height < 0){
throw new IllegalArgumentException("width or height can not be negative.");
}
ratioW = height;
ratioH = width;
//请求重新布局
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == ratioW || 0 == ratioH){
//未设定宽高比,使用预览窗口默认宽高
setMeasuredDimension(width, height);
}else {
//设定宽高比,调整预览窗口大小(调整后窗口大小不超过默认值)
if (width < height * ratioW / ratioH){
setMeasuredDimension(width, width * ratioH / ratioW);
}else {
setMeasuredDimension(height * ratioW / ratioH, height);
}
}
}
注意:用法在跟TextureView控件一样,其他没有变