携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天, 点击查看活动详情
💡Google在Camera2例子中没有具体指明预览帧的获取,即Camera1 setPreviewCallback 类似功能,需要通过实现Camera2 api 中使用 ImageReader 类间接获取帧数据.
一、Camera2简介
在Google推出Android 5.0的时候, Android Camera API 版本升级到了API2), 之前使用的API1(android.hardware.camera)就被标为 Deprecated 了. Camera API2相较于API1有很大不同, 并且API2是为了配合HAL3进行使用的, API2有很多API1不支持的特性
1.1.基本架构
如上图所示, Camera APP 通过CameraCaptureSession发送CaptureRequest, CameraDevices收到请求后返回对应数据到对应的Surface,预览数据一般都是到TextureView, 拍照数据则在ImageReader中, 整体来说就是一个请求--响应过程, 请求完成后, 可以在回调中查询到相应的请求参数和CameraDevice当前状态, 总的来说, Camera2中预览/拍照/录像数据统一由Surface来接收, CaptureRequest代表请求控制的Camera参数, CameraMetadata(CaptureResult)则表示当前返回帧中Camera使用的参数以及当前状态.
1.2.使用流程
1.通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager. 2.调用CameraManager .open()方法在回调中得到CameraDevice. 3.通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession. 4.构建CaptureRequest, 有三种模式可选 预览/拍照/录像. 5.通过 CameraCaptureSession发送CaptureRequest, capture表示只发一次请求, setRepeatingRequest表示不断发送请求. 6.拍照数据可以在ImageReader.OnImageAvailableListener回调中获取, CaptureCallback中则可获取拍照实际的参数和Camera当前状态.
二、获取帧
2.1.新建
新建一个 ImageReader 对象,用来接收预览帧数据: private ImageReader imageReader;
2.2.初始化
初始化好预览大小,比较只有预览大小确定后,才知道得使用一块多大的内存来接收这块数据:
1.mPreviewImageReader = ImageReader.newInstance(
2. mPreviewSize.getWidth(), // 宽度
3. mPreviewSize.getHeight(), // 高度
4. ImageFormat.YUV_420_888, // 图像格式
2); // 用户能同时得到的最大图像数
图像格式可以在 ImageFormat 类中查看,值得注意的是,并不是所有的格式在 Camera2 的预览中都支持,例如 ImageFormat.NV21 便不再支持。另外就是预览不建议使用 ImageFormat.JPEG 这样的格式,因为转码会消耗大量的性能,推荐使用 ImageFormat.YUV_420_888 之类的格式。
2.3.释放
不再需要ImageReader 时,需要手动释放它,在 release camera 的时机释放:
1.if (mPreviewImageReader != null) {
2. mPreviewImageReader.close();
3. mPreviewImageReader = null;
2.4.添加到请求
在预览的 CaptureRequest.Builder 中添加一个目标到该 ImageReader,就可以获取到相应的帧数据
1. private void initPreviewRequest() {
2. try {
3. mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
4. mPreviewRequestBuilder.addTarget(mPreviewSurface); // 设置预览输出的 Surface
5. mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface()); // 设置预览回调的 Surface
6. mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mPreviewImageReader.getSurface()),
7. new CameraCaptureSession.StateCallback() {
9. @Override
10. public void onConfigured(CameraCaptureSession session) {
11. mCaptureSession = session;
12. startPreview();
13. }
15. @Override
16. public void onConfigureFailed(CameraCaptureSession session) {
17. Log.e(TAG, "ConfigureFailed. session: mCaptureSession");
18. }
19. }, mBackgroundHandler); // handle 传入 null 表示使用当前线程的 Looper
20. } catch (CameraAccessException e) {
21. e.printStackTrace();
22. }
23.}
2.5. 添加回调监听
给这个 ImageReader 添加一个回调监听事件,就可以在回调方法中获取到相应的预览帧数据:
1.mPreviewImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
2. @Override
3. public void onImageAvailable(ImageReader reader) {
4. // do something
6.}, null);
2.6.数据处理
上述 onImageAvailable(ImageReader reader) 回调得到并不是byte[] 数组,而是一个 ImageReader 对象,需要通过这个 ImageReader获取Image,从Image中得到YUV分量的颜色数据,再按指定格式拼起来:
1.private static byte[] getDataFromImage(Image image) {
2. Rect crop = image.getCropRect();
3. int format = image.getFormat();
4. int width = crop.width();
5. int height = crop.height();
6. Image.Plane[] planes = image.getPlanes();
7. byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
8. byte[] rowData = new byte[planes[0].getRowStride()];
9. int channelOffset = 0;
10. int outputStride = 1;
11. for (int i = 0; i < planes.length; i++) {
12. switch (i) {
13. case 0:
14. channelOffset = 0;
15. outputStride = 1;
16. break;
17. case 1:
18. channelOffset = width * height + 1;
19. outputStride = 2;
20. break;
21. case 2:
23. channelOffset = width * height;
24. outputStride = 2;
25. break;
26. }
27. ByteBuffer buffer = planes[i].getBuffer();
28. int rowStride = planes[i].getRowStride();
29. int pixelStride = planes[i].getPixelStride();
30. Log.v(TAG, "pixelStride " + pixelStride);
31. Log.v(TAG, "rowStride " + rowStride);
32. Log.v(TAG, "width " + width);
33. Log.v(TAG, "height " + height);
34. Log.v(TAG, "buffer size " + buffer.remaining());
35. int shift = (i == 0) ? 0 : 1;
36. int w = width >> shift;
37. int h = height >> shift;
38. buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
39. for (int row = 0; row < h; row++) {
40. int length;
41. if (pixelStride == 1 && outputStride == 1) {
42. length = w;
43. buffer.get(data, channelOffset, length);
44. channelOffset += length;
45. } else {
46. length = (w - 1) * pixelStride + 1;
47. buffer.get(rowData, 0, length);
48. for (int col = 0; col < w; col++) {
49. data[channelOffset] = rowData[col * pixelStride];
50. channelOffset += outputStride;
51. }
52. }
53. if (row < h - 1) {
54. buffer.position(buffer.position() + rowStride - length);
55. }
56. }
57. }
58. return data;
59. }
复制代码