Camera2是谷歌继Camera1之后的升级版本,从Android 5.0开始(API Level 21)就可以进行使用了。就调用而言,Camera2拥有对照相机更多更深的控制权,一些Camera1不支持或支持起来比较麻烦的功能Camera2都能支持。
CameraManager.getCameraCharacteristics(@NonNull String cameraId)
CameraCaptureSession.capture(@NonNull CaptureRequest request,
@Nullable CaptureCallback listener, @Nullable Handler handler)
// 多张拍照
CameraCaptureSession .captureBurst(@NonNull List<CaptureRequest> requests,
@Nullable CaptureCallback listener, @Nullable Handler handler)
// 连拍
CameraCaptureSession.setRepeatingRequest(@NonNull CaptureRequest request,
@Nullable CaptureCallback listener, @Nullable Handler handler)
CaptureRequest.set(@NonNull Key<T> key, T value)
// 曝光时间
CaptureRequest.Builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, long value);
// 感光度
CaptureRequest.Builder.set(CaptureRequest.SENSOR_SENSITIVITY, int value);
Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下图为官方Pipeline 的工作流程:
在拍摄之前创建用于从Pipeline(管道,可以简单理解为获取图片数据的通道)中获取图片的CaptureRequest,CaptureRequest中存在用于获取图片数据的Surface。
一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。
相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以 Camera2 定义了一个叫做 Supported Hardware Level 的重要概念,其作用是将不同设备上的 Camera2 根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上 Camera2 的支持情况。截止到 Android P 为止,从低到高一共有 LEGACY、LIMITED、FULL 和 LEVEL_3 四个级别:
CameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
在 Camera2 里面所有的相机操作和参数配置都被抽象成 Capture(捕获),所以不要简单的把 Capture 直接理解成是拍照,因为 Capture 操作可能仅仅是为了让预览画面更清晰而进行对焦而已。
Capture 从执行方式上又被细分为【单次模式】、【多次模式】和【重复模式】三种
CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
先看应用实例图:
一. 要实现类似这样的一个横屏照相机,首先要绘制预览图片的承接页,相机的预览是一个频繁的操作并且会产生实时数据,从性能角度考虑,这里可以采用SurfaceView实现照片预览。并且SurfaceView内置ViewOutlineProvider,支持轮廓裁剪,例如实现上图圆角效果:
// SurfaceView圆角效果
private void setSurfaceViewCorner(final float radius) {
SurfaceView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
int leftMargin = 0;
int topMargin = 0;
Rect selfRect = new Rect(leftMargin, topMargin,
rect.right - rect.left - leftMargin,
rect.bottom - rect.top - topMargin);
outline.setRoundRect(selfRect, radius);
}
});
SurfaceView.setClipToOutline(true);
}
二. 其次预览照片要设置一个可操作Handler,这里的Handler可以使用子线程Handler完成,例如从HandlerThread中取Handler。
Handler handler = new Handler(new HandlerThread("HandlerCamera2").getLooper());
除此之外预览要设置CaptureRequest预览模式:
CaptureRequest.Builder captureRequestBuilder = CameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
三. 关于ImageReader:
ImageReader中包含Surface,拍照数据通过Surface发送给ImageReader,类似一个队列,需要通过acquireLatestImage()或者acquireNextImage()方法取出Image。
Image类允许应用通过一个或多个ByteBuffers直接访问Image的像素数据, ByteBuffer包含在Image.Plane类中,同时包含了这些像素数据的配置信息。因为是作为提供raw数据使用的,所以Image不像Bitmap类可以直接填充到UI上使用。
ImageReader部分重要API:
private void initImageReader() {
if (mImageReader == null) {
try {
CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
Size size = getImgSize(cameraCharacteristics);
int width = size.getWidth();
int height = size.getHeight();
Logger.info("Camera2Executor", "realWidth = " + width, false);
Logger.info("Camera2Executor", "realHeight = " + height, false);
// 设置图片大小
mImageReader = ImageReader.newInstance(width, height, mImageFormat, 2);
ImageReader.OnImageAvailableListener listener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
// 获取照片数据
Image image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
// 字节码转文件
if (TextUtils.isEmpty(filePath)) {
filePath = mContext.getExternalCacheDir().getAbsolutePath()
+ File.separator + System.currentTimeMillis() + ".jpg";
} else {
String temp = filePath.toLowerCase(Locale.ROOT);
if (!(temp.endsWith(".jpeg")
|| temp.endsWith(".jpg")
|| temp.endsWith(".png")
|| temp.endsWith(".webp"))) {
filePath = mContext.getExternalCacheDir().getAbsolutePath()
+ File.separator + System.currentTimeMillis() + ".jpg";
}
}
Logger.info("Camera2Executor", "filePath = " + filePath, false);
File file = new File(filePath);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(bytes);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// // 字节码转Bitmap
// Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (mCamera2Listener != null && file.exists()) {
mCamera2Listener.onCameraSuccess(file);
}
image.close();
}
};
mImageReader.setOnImageAvailableListener(listener, uiHandler);
} catch (CameraAccessException e) {
// TOD
}
}
}
四. 关于摄像头方向
CameraId:
CameraCharacteristics.LENS_FACING_FRONT 通常表示后置摄像头;
CameraCharacteristics.LENS_FACING_BACK 通常表示前置摄像头。
五. 关于照片方向
/**
* 获取照片方向角度
*/
private int getRotation(CameraCharacteristics cameraCharacteristics) {
int displayRotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
switch (displayRotation) {
case Surface.ROTATION_0:
displayRotation = 90;
break;
case Surface.ROTATION_90:
displayRotation = 0;
break;
case Surface.ROTATION_180:
displayRotation = 270;
break;
case Surface.ROTATION_270:
displayRotation = 180;
break;
}
int sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
return (displayRotation + sensorOrientation + 270) % 360;
}
六. 关于照片时照片大小计算
/**
* 获取图片大小,取最接近当前屏幕的宽高比的尺寸
*/
private Size getImgSize(CameraCharacteristics cameraCharacteristics) {
// 计算当前屏幕的宽高比
int screenWidth = ScreenUtil.getScreenW(mContext);
int screenHeight = ScreenUtil.getScreenH(mContext);
float screenRatio = screenWidth * 100f / screenHeight;
Logger.info("Camera2Executor", "screenWidth = " + screenWidth, false);
Logger.info("Camera2Executor", "screenHeight = " + screenHeight, false);
Logger.info("Camera2Executor", "screenRatio = " + screenRatio, false);
// 获取相机支持的所有尺寸
StreamConfigurationMap map = cameraCharacteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizeList = map.getOutputSizes(mImageFormat);
// 对比尺寸
Map<Float, Size> tempMap = new HashMap<>();
List<Float> tempList = new ArrayList<>();
for (Size item : sizeList) {
int width = item.getWidth();
int height = item.getHeight();
float ratio = width * 100f / height;
float ratioDiff = Math.abs(ratio - screenRatio);
Logger.info("Camera2Executor", "width = " + width, false);
Logger.info("Camera2Executor", "height = " + height, false);
Logger.info("Camera2Executor", "ratioDiff = " + ratioDiff, false);
tempMap.put(ratioDiff, item);
tempList.add(ratioDiff);
}
Collections.sort(tempList);
// 取出最合适尺寸
return tempMap.get(tempList.get(0));
}
七. 常用功能方法补充
CaptureRequest.Builder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[] {new MeteringRectangle(Rect rect, int meteringWeight)});
CaptureRequest.Builder.set(CaptureRequest.SCALER_CROP_REGION, CaptureRequest.Builder.get(CaptureRequest.SCALER_CROP_REGION));
补充说明:如果SurfaceView预览时有拉伸,可以设置SurfaceView的渲染宽高来达到适配效果:
public void setFixedSize(int width, int height)
最后整体流程图:
Camera2 API 采集视频并SurfaceView、TextureView 预览
Camera SurfaceView 预览拍照
Camera2教程之打开相机、开启预览、实现PreviewCallback、拍照
Android Camera、Camera2详解
自定义Camera系列之:SurfaceView + Camera2
Camera2 教程 · 第一章 · 概览
ImageReader获得预览数据
彻底搞懂摄影中的感光度、曝光指数和增益