Camera2开发(一)——相机初始化
从本文开始,笔者将开启一个新的项目,使用camera2接口来做一个简单的相机app。它的主要功能包含图像的拍摄,预览和保存,并在此基础上封装HDRD和HDR算法。
前言
从本文开始,笔者将开启一个新的项目,使用camera2接口来做一个简单的相机app。它的主要功能包含图像的拍摄,预览和保存,并在此基础上封装HDRD和HDR算法。
项目GitHub链接:Camera2DEMO
Camera2 API 相关知识
有关camera2框架内部一些细节可以参考这篇博客:Android Camera2教程
简单来说,一次拍照流程:
1、用户发出拍照请求CaptureRequest。CaptureRequest内部包括了本次拍照的参数配置和接收图像数据的内存空间Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等。
2、Camera2框架是一个Pipeline结构,获取了CaptureRequest的Pipeline实例称为CaptureRequestSession。一个 CameraDevice一次只能开启一个CameraCaptureSession,CameraCaptureSession将拍照请求传递给硬件,最后得到本次拍照结果CaptureResult和图像Image。CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等,图像则存入Surface。
代码实现
实现一个基于Camera2 API的相机一般分为如下几步:
1、获取相机权限:这一步要在AndroidManifest.xml文件中添加相机权限。
2、设置相机管理器:创建相机管理器实例并获取相机设备的ID。
3、打开相机:通过相机管理器打开相机设备。
4、创建预览会话:创建相机预览会话以显示实时预览图像。
5、实时预览显示:将相机预览的图像输出到指定的视图上。
但是为了方便后续图像处理算法的封装,我们会再上述步骤的基础上做些许调整。
可以看下总体流程,具体实现细节我们会慢慢描述:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_camera2_basic);
textureView = findViewById(R.id.texture);
btn_photo = findViewById(R.id.picture);
btn_photo.setOnClickListener(OnClick);
}
@Override
protected void onResume() {
super.onResume();
//开启相机线程
startCameraThread();
//textureView回调
if (!textureView.isAvailable()) {
textureView.setSurfaceTextureListener(mTextureListener);
} else {
startPreview();//开启预览
}
}
一、首先初始化要用到的变量
这里大概浏览下即可,后续用到时候还会介绍:
private static final String TAG = "Phildebug";
private TextureView textureView;//继承自View,必须在硬件加速窗口中
private HandlerThread handlerThread;//运行不应该被阻塞UI的任务的附加线程
private Handler mCameraHandler;//用于在后台运行任务
private CameraManager cameraManager;//摄像头管理器,用于检测打开摄像头
private Size previewSize;//最佳的预览尺寸
private Size mCaptureSize;//最佳的拍照尺寸
private String mCameraId;//设备ID
private CameraDevice cameraDevice;//该实例代表摄像头
private CaptureRequest.Builder captureRequestBuilder;
private CaptureRequest.Builder previewRequestBuilder;
private CaptureRequest captureRequest;//捕获请求
private CaptureRequest previewRequest;//捕获请求
private CameraCaptureSession mCameraCaptureSession;//预览拍照先通过它创建session
private CameraCaptureSession mCameraPreviewSession;//预览HDR先通过它创建session
private Button btn_photo;//拍照按钮
private ImageReader imageCaptureReader;//用于图像保存
private ImageReader imagePreviewReader;//用于图像HDRD
private static final SparseArray ORIENTATION = new SparseArray();//图片旋转方向
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {//对手机存储权限
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private int exposure = 0;//记录图像输入HDR函数的顺序
二、建立TextView监听器来初始化相机
SurfaceTexture是Android上做渲染的核心组件,它是 Surface 和 OpenGL ES 纹理的组合,用于提供输出到 GLES 纹理的 Surface。
SurfaceTexture的最常见应用场景是作为相机或者视频解码器的输出,如下:
/***
* TextureView生命周期响应
*/
TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//SurfaceTexture组件可用的时候,设置相机参数,并打开摄像头
//设置摄像头参数
setUpCamera(width, height);
//打开摄像头,默认后置
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
//尺寸发生变化的时候
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
//组件被销毁的时候
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
//组件更新的时候
}
};
当SurfaceTexture组件可用时候,就可以对相机进行初始化,然后打开相机。
三、初始化相机
使用相机之前,我们需要创建相机管理器实例,然后获取到相机设备的ID和相机的相关参数。
CameraCharacteristics类是使用Camera2 API时用于描述相机设备的特性信息的类。它提供了关于相机设备的各种参数和功能的元数据。以下是一些常见的CameraCharacteristics包含的信息:
- 相机ID(Camera ID):相机设备的唯一标识符,用于在多个相机设备中进行区分。
- 摄像头方向(Camera Facing):描述相机设备的方向,包括前置摄像头、后置摄像头等。
- 传感器尺寸(Sensor Size):相机传感器的物理尺寸,通常以毫米为单位表示。
- 最大图像输出尺寸(Max Image Output Size):相机设备能够生成的最大图像输出尺寸,通常以像素为单位表示。
- 支持的图像格式(Supported Image Formats):相机设备支持的图像格式,例如JPEG、YUV等。
- 支持的预览分辨率(Supported Preview Resolutions):相机设备支持的预览图像分辨率列表。
- 支持的拍照分辨率(Supported Capture Resolutions):相机设备支持的拍照图像分辨率列表。
- 自动对焦模式(Auto Focus Modes):相机设备支持的自动对焦模式,例如连续自动对焦、自动对焦单次触发等。
- 支持的曝光模式(Exposure Modes):相机设备支持的曝光模式,例如自动曝光、手动曝光等。
- 支持的闪光灯模式(Flash Modes):相机设备支持的闪光灯模式,例如自动闪光、打开闪光灯等。
- 支持的帧率范围(Supported Frame Rates):相机设备支持的帧率范围,用于预览和录制视频。
- 图像稳定化支持(Image Stabilization Support):相机设备是否支持图像稳定化功能。
检查是否支持手动曝光模式,是因为我们后续使用HDR算法时候,需要申请三张不同曝光度的照片,而这个曝光度需要手动设置。
为了使相机拍摄图像可以正确在手机上预览,我们还需要使用map.getOutputSizes()获取到相机设备支持的输出尺寸列表,以便应用程序可以选择最合适的尺寸来配置相机设备的输出。
设置好相机参数之后,我们还需要建立两个imageReader来分别存储和预览照片。
/***
* 设置摄像头参数
*/
private void setUpCamera(int width, int height) {
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//拿到摄像头的id
try {
for (String cameraId : cameraManager.getCameraIdList()) {
//得到摄像头的参数
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
// 检查是否支持手动控制曝光模式
int[] availableAEModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
boolean isManualExposureSupported = false;
for (int aeMode : availableAEModes) {
if (aeMode == CameraCharacteristics.CONTROL_AE_MODE_OFF) {
isManualExposureSupported = true;
break;
}
}
// 检查是否支持曝光补偿
boolean isExposureCompensationSupported = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) != null;
// 输出支持情况
Log.d(TAG, "手动曝光支持: " + isManualExposureSupported);
Log.d(TAG, "曝光补偿支持: " + isExposureCompensationSupported);
Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) { //找到摄像头能够输出的,最符合我们当前屏幕能显示的最小分辨率
previewSize = new Size(1920, 1080);
mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new Comparator<Size>() {
@Override
public int compare(Size o1, Size o2) {
return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());
}
});
}
//建立ImageReader来存储和预览照片
setUpCaptureReader();
setUpPreviewReader();
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
四、打开相机
打开相机时候,检查是否有写入手机外部存储的权限,不然到时候APP会闪退。相机成功打开之后,调用预览方法,让画面实时出现在手机屏幕上。
/***
* 打开摄像头
*/
private void openCamera() {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return;
}
try {
cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) { //摄像头打开
cameraDevice = camera;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) { //摄像头关闭
cameraDevice.close();
cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {//摄像头出现错误
cameraDevice.close();
cameraDevice = null;
}
};
关于预览的实现,我们下篇文章再见,如果您喜欢本专栏文章,还请点赞关注多多支持,下期见!
更多推荐
所有评论(0)