Android Camera2 使用总结
最近在做自定义相机相关的项目,网上查了资料都是有关android.hardware.Camera
的资料,开始使用的才发现这个类已经废弃了。Android 5.0(21
)之后android.hardware.Camera
就被废弃了,取而代之的是全新的android.hardware.Camera2
。Android 5.0
对拍照API进行了全新的设计,新增了全新设计的Camera v2 API
,这些API不仅大幅提高了Android
系统拍照的功能,还能支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。
demo 地址https://github.com/Hemumu/WallpaperDemo
Camera2
主要的类说明
-
CameraManager
:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用CameraManager
的getCameraCharacteristics(String)
方法即可获取指定摄像头的相关特性。 -
CameraCharacteristics
:摄像头特性。该对象通过CameraManager
来获取,用于描述特定摄像头所支持的各种特性。 -
CameraDevice
:代表系统摄像头。该类的功能类似于早期的Camera
类。 -
CameraCaptureSession
:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest()
;控制拍照的方法为capture()
。 -
CameraRequest
和CameraRequest.Builder
:当程序调用setRepeatingRequest()
方法进行预览时,或调用capture()
方法进行拍照时,都需要传入CameraRequest
参数。CameraRequest
代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest
参数进行设置。CameraRequest.Builder
则负责生成CameraRequest
对象。
开启相机预览
开启相机请一定添加相关的相机权限,判断6.0
以后添加动态权限的获取。如果相机预览出现黑屏多半就是因为没有相机权限而导致的
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
首页我们要设置相机相关的参数
CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
try {
//获取可用摄像头列表
for (String cameraId : manager.getCameraIdList()) {
//获取相机的相关参数
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
// 不使用前置摄像头。
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// 检查闪光灯是否支持。
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
mCameraId = cameraId;
Log.e(TAG," 相机可用 ");
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
//不支持Camera2API
}
}
通过getSystemService(Context.CAMERA_SERVICE);
拿到了CameraManager
返回当前可用的相机列表,在这里你可以选择使用前置还是后置摄像头。CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
可以拿到当前相机的相关参数,在这里你可以进行相关的参数检查,例如检查闪光灯是否支持等。在这里我们拿到当前相机的cameraId
后面使用。
拿到cameraId
我们就可以调用CameraManager
的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
打开相机了
CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
try {
//打开相机预览
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
添加 CameraDevice.StateCallback 监听
我们需要对相机状态就行监听,以便在相机状态发生改变的时候做相应的操作。openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
中CameraDevice.StateCallback
就是对相机的状态改变的Callback
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
//创建CameraPreviewSession
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
}
};
创建 CameraCaptureSession
在onOpened()
中我们可以拿到CameraDevice
对象,在相机打开后需要创建CameraCaptureSession
。
CameraCaptureSession
是什么呢?由于Camera2
是一套全新的API,所以它引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture
请求,而摄像头会返回 CameraMetadata
。这一切建立在一个叫作 CameraCaptureSession
的会话中。如下图:
图片来自http://wiki.jikexueyuan.com/project/android-actual-combat-skills/android-hardware-camera2-operating-guide.html
这里我们需要预览相机的内容就需要创建CameraCaptureSession
向相机发送Capture
请求预览相机内容
/**
* 为相机预览创建新的CameraCaptureSession
*/
private void createCameraPreviewSession() {
try {
//设置了一个具有输出Surface的CaptureRequest.Builder。
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
//创建一个CameraCaptureSession来进行相机预览。
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相机已经关闭
if (null == mCameraDevice) {
return;
}
// 会话准备好后,我们开始显示预览
mCaptureSession = cameraCaptureSession;
try {
// 自动对焦应
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 闪光灯
setAutoFlash(mPreviewRequestBuilder);
// 开启相机预览并添加事件
mPreviewRequest = mPreviewRequestBuilder.build();
//发送请求
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
Log.e(TAG," 开启相机预览并添加事件");
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG," onConfigureFailed 开启预览失败");
}
}, null);
} catch (CameraAccessException e) {
Log.e(TAG," CameraAccessException 开启预览失败");
e.printStackTrace();
}
}
首先我们创建了一个CaptureRequest
上面说过了我们需要跟相机通信只有通过CameraCaptureSession
。而要和CameraCaptureSession
通信就是发送请求。这里我们相当于在创建请求的一些参数。
createCaptureRequest(int);
也有很多参数可选。这里我们发送的是CameraDevice.TEMPLATE_PREVIEW
也就是告诉相机我们只需要预览。更多参数如下
- TEMPLATE_RECORD 创建适合录像的请求。
- TEMPLATE_PREVIEW 创建一个适合于相机预览窗口的请求。
- TEMPLATE_STILL_CAPTURE 创建适用于静态图像捕获的请求
- TEMPLATE_VIDEO_SNAPSHOT 在录制视频时创建适合静态图像捕获的请求。
详细信息可以参考官网的API文档。
调用创建方法createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
第一参数就是我们需要输出到的Surface
列表,这里我们可以输出到一个SurfaceView
中或者TextureView
中。第二参数是对创建过程的一个回调方法,当onConfigured
回调的时候说明CameraCaptureSession
创建成功了。现在我们可以向CameraCaptureSession
发送前面创建的好的预览相机请求了。调用mCaptureSession.setRepeatingRequest(mPreviewRequest,null, mBackgroundHandler);
这样我们就开启的相机的预览,在刚才添加的输出Surface
对应的控件中我们可以看到摄像头的预览内容了。
拍照
当我们需要拍照并且得到相应的照片数据的时候和开启相机预览相同的操作,我们只需要向CameraCaptureSession
发送我们创建好的请求就行,就像我们请求网络数据一样,封装好参数直接告诉CameraCaptureSession
需要做什么由它去和相机建立通信并执行相应的操作。
对焦
/**
* 将焦点锁定为静态图像捕获的第一步。(对焦)
*/
private void lockFocus() {
try {
// 相机对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// 修改状态
mState = STATE_WAITING_LOCK;
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
向CameraCaptureSession
发送对焦请求,并且对对焦是否成功进行监听,在mCaptureCallback中对回调进行处理
/**
* 处理与JPEG捕获有关的事件
*/
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
//处理
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
//预览状态
break;
}
case STATE_WAITING_LOCK: {
//等待对焦
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
mState = STATE_PICTURE_TAKEN;
//对焦完成
captureStillPicture();
} else {
runPrecaptureSequence();
}
}
break;
}
}
}
}
拍摄图片
对焦完成后我们就可以向CameraCaptureSession
发送请求可以拍照了
/**
*
* 拍摄静态图片。
*/
private void captureStillPicture() {
try {
if ( null == mCameraDevice) {
return;
}
// 这是用来拍摄照片的CaptureRequest.Builder。
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// 使用相同的AE和AF模式作为预览。
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
setAutoFlash(captureBuilder);
// 方向
int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
showToast("Saved: " + mFile);
Log.d(TAG, mFile.toString());
unlockFocus();
}
};
//停止连续取景
mCaptureSession.stopRepeating();
//捕获图片
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
相信看到这里代码已经不复杂了,组装好我们的请求然后用CameraCaptureSession
发送这个请求就可以了。这里需要注意的是我们怎么拿到图片数据呢? 这里要说回在创建CameraCaptureSession
时参数不是有一个输出的Surface
列表么,在列表中添加一个ImageReader
的Surface
用户获取图片数据
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),null).....
在ImageReader
中对图片获取就行监听
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//当图片可得到的时候获取图片并保存
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
}
};
调用reader.acquireNextImage()
我们就可以拿到当前的图片数据了。拍完后我们需要解锁焦点让相机回到预览状态,同样的我们发送请求就可以了
/**
* 解锁焦点
*/
private void unlockFocus() {
try {
// 重置自动对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
setAutoFlash(mPreviewRequestBuilder);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
// 将相机恢复正常的预览状态。
mState = STATE_PREVIEW;
// 打开连续取景模式
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
效果
GIF.gif之前看了鸿神推送的Android 仿火萤视频桌面 神奇的LiveWallPaper 一文中提到了用相机来做壁纸也就是透明屏幕,项目地址https://github.com/songixan/Wallpaper 查看源码发现还是用的旧的Camera
API,所以我在Demo中用Camera2
API做了透明屏幕,有兴趣的可以去看下。 ** PS:后来在同事的小米2S(5.1.1)中测试发现出错了,初步猜测是分辨率的原因,目前正在解决中。有问题大家可以私信我 谢谢~**