Android Camera目前的整体架构很清晰的,主要分为以下几块:
- Camera APP
代码在packages/apps/Camera2/目录下
是Camera的上层应用端,自己写Camera App的话,可以借鉴此部分代码
- Camera Framework API
这部分是提供给上层APP调用的API
代码在frameworks/base/core/java/android/hardware/camera2/目录下
- libCameraService
代码在frameworks/av/services/camera/libcameraservice目录下
Camera服务层代码,负责连接上层的Framework API和底层的HAL
- HAL层
代码在/hardware/interfaces/camera/目录下
上层APP的简单调用流程
首先,对于一般人而已,Camera APP是直接用他们交互的部分。
Camera APP通过对Framework API的调用,完成整个Camera的功能。
其中CameraManager是一切开始的地方。
它可以通过获取系统服务的方式被拿到。
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
APP层通过调用它的openCamera方法来打开对于ID的相机设备。
public void openCamera (String cameraId,
CameraDevice.StateCallback callback,
Handler handler)
此openCamera方法需要传入一个CameraDevice.StateCallback的回调接口。
如果打开成功,接口中的onOpened(CameraDevice camera)方将将被调用。
这样,就拿到了一个很关键的对象——CameraDevice。
第三个参数Handler是用来指定线程的,可以为空,为空时就是当前的调用线程,但一般来说都会为其创建一个后台线程。
如下,先新建一个HandlerThread,再用Handler的Looper创建后台Handler。
HandlerThread mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
Handler mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
在成功拿到CameraDevice之后,就可以通过它来创建对于的Session,Camera2的预览、拍照、录像等操作都是通过CameraCaptureSession完成的。
创建Session:
public abstract void createCaptureSession(@NonNull List<Surface> outputs,
@NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException;
CameraCaptureSession.StateCallback是一个回调,创建成功的话会调用onConfigured方法。
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
onConfigured方法会把创建好的CameraCaptureSession 给带回来。
这个套路和之前创建CameraDevice是一模一样的。
通过CameraCaptureSession预览:下面这种带有Repeating的请求,可以无止境的捕获图像,很适合用来预览。
public abstract int setRepeatingRequest(@NonNull CaptureRequest request,
@Nullable CaptureCallback listener, @Nullable Handler handler)
throws CameraAccessException;
这里的CaptureRequest很关键,决定了这次请求的类型,通过CaptureRequest.Builder来创建,而CameraDevice提供了创建CaptureRequest.Builder的方法。
public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
throws CameraAccessException;
这里需要传入一个模板类型作为参数。@RequestTemplate这个注解对这个模板类型做了输入限制。所以通过这个注解也可以知道能输入什么值。
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"TEMPLATE_"}, value =
{TEMPLATE_PREVIEW,
TEMPLATE_STILL_CAPTURE,
TEMPLATE_RECORD,
TEMPLATE_VIDEO_SNAPSHOT,
TEMPLATE_ZERO_SHUTTER_LAG,
TEMPLATE_MANUAL})
public @interface RequestTemplate {};
见名知意,预览的话,肯定是传入TEMPLATE_PREVIEW了。
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
这里要提一个下,CaptureRequest.Builder需要一个显示的载体——Surface,可以通过addTarget添加。
这里添加的目标Surface必须是创建CameraCaptureSession是传入的List<Surface> outputs的子集!
public void addTarget(@NonNull Surface outputTarget)
在调用完setRepeatingRequest后预览就开始了。
PS:预览的Surface一般用SurfaceView或者TextureView。早期都是用SurfaceView,目前TextureView用的多点,它更像是一般的View,像TextView那样能被缩放、平移,也能加上动画;但是TextureView只能在开启了硬件加速的Window中使用,并且消费的内存要比SurfaceView多;
通过CameraCaptureSession拍照
public abstract int capture(@NonNull CaptureRequest request,
@Nullable CaptureCallback listener, @Nullable Handler handler)
throws CameraAccessException;
和预览的套路是一样的,主要分两步:
1. 创建CaptureRequest.Builder
2. 添加目标Surface
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
不同是的,Builder参数改为了TEMPLATE_STILL_CAPTURE(静态捕获),添加的目标Surface也变成了ImageReader的Surface。
拍照的时候,通过ImageReader来保持图像是一个很不错的选择。
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
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));
}
};
private static class ImageSaver implements Runnable {
/**
* The JPEG image
*/
private final Image mImage;
/**
* The file we save the image into.
*/
private final File mFile;
ImageSaver(Image image, File file) {
mImage = image;
mFile = file;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
output.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
if (null != output) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
当然,在开始拍照前,最好停止一下之前的预览。
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
完成拍照后,再开启预览。
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
mBackgroundHandler);