• Camera2的使用【详细】


    目录

    1.获取权限

    2. 获取指定相机ID

    (1)获取相机管理者CameraManager

    (2)获取相机ID列表

    (3)获取相机特征CameraCharacteristics

    (4)获取相机朝向

    3.获取相机输出尺寸

    (1)根据相机ID获取相机特征

    (2)获取输出流配置

    (3)获取输出尺寸数组(参数为输出目标类)

    (4)选择输出尺寸

    4. SurfaceView及其他控件的准备

    (1)获取视图

    (2)根据相机输出尺寸设置SurfaceView尺寸

    (3)添加回调

    5. 创建ImageReader用于处理捕获的图片

    (1)创建ImageReader

    (2)设置图片可用监听器OnImageAvailableListener

    6. 创建相机设备状态回调、通道并发送循环预览请求

    (1)创建相机设备状态回调CameraDevice.StateCallback

    (2)创建捕获请求建造者CaptureRequest.Builder(参数:模板_预览)

    (3)创建捕获通道CaptureSession

    (4)在捕获通道完成后要设置循环请求(预览捕获请求)

    7. 启动相机

    8. 拍摄照片

    (1)创建捕获请求建造者CaptureRequest.Builder(参数:模板_静态捕获)

    (2)捕获通道执行捕获拍照捕获请求

    9. 常见报错

    (1) CaptureRequest contains unconfigured Input/Output Surface!

    (2) MTK零延迟的Camera机制


    1.获取权限

    camera权限是必须申请的,如果需要保存图片还需要读写权限,并动态申请。

    1. "android.permission.CAMERA"/>
    2. "android.permission.READ_EXTERNAL_STORAGE"/>
    3. "android.permission.WRITE_EXTERNAL_STORAGE"/>
    requestPermissions(new String[]{"android.permission.CAMERA","android.permission.READ_EXTERNAL_STORAGE","android.permission.WRITE_EXTERNAL_STORAGE"}, 2333);
    1. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    2. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    3. if (requestCode == 2333) {
    4. for (int i = 0; i < permissions.length; i++) {
    5. if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
    6. requestPermissions(permissions, requestCode);
    7. break;
    8. }
    9. }
    10. }
    11. }

    2. 获取指定相机ID

    流程:

    (1)获取相机管理者CameraManager

    相机管理者中存储了相机ID列表、相机设备。

    (2)获取相机ID列表

    String cameraIdList[] = cameraManager.getCameraIdList();

    (3)获取相机特征CameraCharacteristics

    CameraCharacteristics中含有相机设备的信息。

    (4)获取相机朝向

    cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);

    1. //相机ID
    2. String cameraId=null;
    3. //(1)获取相机管理者
    4. CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
    5. //(2)获取相机ID列表
    6. String cameraIdList[] = cameraManager.getCameraIdList();
    7. for (int i = 0; i < cameraIdList.length; i++) {
    8. //(3)获取相机特征
    9. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraIdList[i]);
    10. //(4)获取相机朝向---后置
    11. if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
    12. //使用该相机ID
    13. cameraId = cameraIdList[i];
    14. break;
    15. }
    16. }

    3.获取相机输出尺寸

    流程:

    (1)根据相机ID获取相机特征

    (2)获取输出流配置

    (3)获取输出尺寸数组(参数为输出目标类)

    (4)选择输出尺寸

    1. //获取相机支持尺寸
    2. int output_width,output_height;
    3. //(1)获取当前相机的特性
    4. CameraCharacteristics cameraCharacteristics=cameraManager.getCameraCharacteristics(imageId);
    5. //(2)获取输出流配置
    6. StreamConfigurationMap map=cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    7. //(3)获取输出尺寸数组(参数为输出目标类)
    8. Size[] outputSizes = map.getOutputSizes(ImageReader.class);
    9. //(4)选择输出尺寸
    10. for(int i=0;i
    11. //获取需要的输出尺寸
    12. output_height=outputSizes[i].getHeight();
    13. output_width=outputSizes[i].getWidth();
    14. //判断是否使用本尺寸代码
    15. //... ...
    16. break;
    17. }

    4. SurfaceView及其他控件的准备

    和Camera一样,Camera2也可以使用SurfaceView作为预览视图,但Camera2和Camera不同,他并不强制你拍摄前必须预览。

    SurfaceView使用时请注意,一定要在SurfaceView创建完成后再进行预览的绑定,这一点和Camera、MediaPlayer等的操作是一样的。

    但Camera绑定SurfaceView设置预览时使用的是SurfaceHolder,而Camera2使用的时Surface

    SurfaceView用于显示捕获的图片作为预览捕获请求的目标

    流程:

    (1)获取视图

    (2)根据相机输出尺寸设置SurfaceView尺寸

    (3)添加回调

    surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {

    1. //(1)获取控件
    2. SurfaceView surfaceView = findViewById(R.id.~);
    3. //(2)根据相机捕获尺寸设置SurfaceView尺寸
    4. RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) surfaceView.getLayoutParams();
    5. layoutParams.width = ~ ;
    6. layoutParams.height = ~ ;
    7. surfaceView.setLayoutParams(layoutParams);
    8. //Camera2绑定SurfaceView预览时使用的是Surface
    9. Surface surface=null;
    10. //(3)SurfaceView回调
    11. surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
    12. public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
    13. //构建成功
    14. surface = surfaceHolder.getSurface();
    15. }
    16. public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder,int i,int i1,int i2) {
    17. //SurfaceView改变
    18. }
    19. public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
    20. //构建摧毁
    21. surface = null;
    22. }
    23. });
    24. //拍照按钮
    25. Button button_takePicture=null;

    5. 创建ImageReader用于处理捕获的图片

    ImageReader用于处理捕获的图片,作为拍照捕获请求的目标。

    流程:

    (1)创建ImageReader

    创建ImageView的前两个参数为捕获图像的宽高,请设置。第三个参数是表示在 ImageReader 队列中同时最多可以有多少张图像。如果这个参数为 1,表示 ImageReader 只能容纳一张图像。当你获取一张新的图像时,如果队列已满,则会丢弃旧的图像。这有助于避免内存溢出或过多图像堆积的问题。

    (2)设置图片可用监听器OnImageAvailableListener

    用ImageReader捕获图片后将调用其中的onImageAvailable()方法。

    处理图像代码详解:

    • acquireNextImage() 方法用于从 ImageReader 中获取下一个可用的图像。acquireNextImage() 方法获取最新可用的图像,该图像会被移除并返回。如果没有可用的图像,此方法将会阻塞,直到有图像可用或者超时。返回的是一个 Image 对象,代表捕获到的图像数据。
    • Image 对象通过 getPlanes() 方法返回一个 Image.Plane[] 数组,每个平面包含了图像的一个通道(例如:红色、绿色、蓝色、透明度等)。通常,对于 JPEG 格式的图像,这里的数组中只有一个平面,即索引为 0 的平面。
    • ByteBuffer 是用来存储图像数据的容器。通过 getBuffer() 方法获取的 ByteBuffer 包含了图像数据。remaining() 方法返回当前位置与限制之间的元素数量,这里指的是剩余的可读取的字节数。
    • get(bytes) 方法从 ByteBuffer 中读取数据并将其存储到 bytes 数组中。
    1. //(1)创建ImageReader,请注意宽高
    2. ImageReader imageReader = ImageReader.newInstance(output_width, output_height, ImageFormat.JPEG, 1);
    3. //(2)设置图片可用监听器
    4. imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
    5. public void onImageAvailable(ImageReader reader) {
    6. // 处理图像数据
    7. Image image = reader.acquireNextImage();
    8. ByteBuffer byteBuffer=image.getPlanes()[0].getBuffer();
    9. byte[] bytes=new byte[byteBuffer.remaining()];
    10. byteBuffer.get(bytes);
    11. //存储
    12. File file = new File(Environment.getExternalStorageDirectory() + "/picture.jpg");
    13. try (OutputStream output = new FileOutputStream(file)) {
    14. output.write(bytes);
    15. } catch (IOException e) {
    16. e.printStackTrace();
    17. }
    18. // 处理完后记得释放资源
    19. image.close();
    20. }
    21. }, null);

    6. 创建相机设备状态回调、通道并发送循环预览请求

    流程:

    (1)创建相机设备状态回调CameraDevice.StateCallback

    CameraDevice是指具体相体设备

    (2)创建捕获请求建造者CaptureRequest.Builder(参数:模板_预览)

    捕获请求需要在相机启动成功后创建。

    添加目标为SurfaceView的Surface,需要确定此时的SurfaceView已经创建完成并已执行回调。

    (3)创建捕获通道CaptureSession

    捕获通道需要在相机启动成功后创建。

    第一个参数为所有捕获请求的目标Surface集合,包括用于预览的SurfaceView的Surface和用于拍照的ImageReader的Surface,不能将捕获请求的目标设为不在本集合中的Surface,否则会报错。

    (4)在捕获通道完成后要设置循环请求(预览捕获请求)

    cameraCaptureSession.setRepeatingRequest(captureRequestBuilder_preview.build(), null, null);

    1. //捕获通道
    2. CameraCaptureSession captureSession;
    3. // (1) 创建相机设备状态回调-CameraDevice是指具体相体设备
    4. CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    5. public void onOpened(@NonNull CameraDevice cameraDevice) {
    6. //相机设备打开
    7. myCameraDevice=cameraDevice;
    8. //捕获请求建造者
    9. CaptureRequest.Builder captureRequestBuilder_preview;
    10. try {
    11. // (2) 创建捕获请求建设者,参数:模板_预览
    12. captureRequestBuilder_preview = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    13. //等待SurfaceView创建完成并回调后将它的Surface设为目标
    14. while (true) {
    15. if (surface != null) {
    16. //添加目标
    17. captureRequestBuilder_preview.addTarget(surface);
    18. break;
    19. }
    20. Thread.sleep(40);
    21. }
    22. } catch (CameraAccessException e) {
    23. throw new RuntimeException(e);
    24. } catch (InterruptedException e) {
    25. throw new RuntimeException(e);
    26. }
    27. // (3) 创建捕获通道 - 捕获通道需要在相机启动后再创建
    28. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    29. try {
    30. //创建目标合集
    31. List list=new ArrayList();
    32. list.add(surface);
    33. list.add(imageReader.getSurface());
    34. cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
    35. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
    36. //捕获通道配置完成
    37. captureSession=cameraCaptureSession;
    38. // (4) 配置完成,可以开始预览 - 设置循环请求
    39. try {
    40. cameraCaptureSession.setRepeatingRequest(captureRequestBuilder_preview.build(), null, null);
    41. } catch (CameraAccessException e) {
    42. throw new RuntimeException(e);
    43. }
    44. }
    45. public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
    46. //配置失败
    47. }
    48. }, null);
    49. } catch (CameraAccessException e) {
    50. throw new RuntimeException(e);
    51. }
    52. }
    53. }
    54. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
    55. //相机设备断开
    56. if (myCameraDevice != null) {
    57. myCameraDevice.close();
    58. myCameraDevice = null;
    59. }
    60. }
    61. public void onError(@NonNull CameraDevice cameraDevice, int i) {
    62. //相机设备错误
    63. if (myCameraDevice != null) {
    64. myCameraDevice.close();
    65. myCameraDevice = null;
    66. }
    67. }
    68. };

    7. 启动相机

    使用相机管理者CameraManager的openCamera启动指定相机,参数为相机ID、相机状态回调、Handler。请注意第三个参数Handler用于指定回调方法在哪个线程上执行。通常情况下,相机相关的操作需要在主线程(UI 线程)执行,以确保与 UI 的交互是安全的。该参数不能为null

    1. //Handler
    2. Handler handler_ui = new Handler() {
    3. public void handleMessage(@NonNull Message msg) {
    4. }
    5. };
    6. //启动相机
    7. cameraManager.openCamera(cameraId, stateCallback, handler_ui);

    8. 拍摄照片

    流程:

    (1)创建捕获请求建造者CaptureRequest.Builder(参数:模板_静态捕获)

    (2)捕获通道执行捕获拍照捕获请求

    1. button_take.setOnClickListener(new View.OnClickListener() {
    2. public void onClick(View view) {
    3. try {
    4. //(1)创建拍照的捕获请求,参数:模板_静态_捕获
    5. CaptureRequest.Builder captureRequestBuilder = myCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
    6. //用于保存拍照的Surface(在通道创建时已在目标集合中)
    7. Surface captureSurface = imageReader.getSurface();
    8. captureRequestBuilder.addTarget(captureSurface);
    9. //(2)捕获通道执行捕获请求
    10. captureSession.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
    11. public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
    12. super.onCaptureStarted(session, request, timestamp, frameNumber);
    13. }
    14. },null);
    15. } catch (CameraAccessException e) {
    16. throw new RuntimeException(e);
    17. }
    18. }
    19. });

    9. 常见报错

    (1) CaptureRequest contains unconfigured Input/Output Surface!

    出现这个错误的原因是我们在一次捕获capture(也就是拍照的时候),cameraCaptureSession.capture(captureRequestBuilder.build(),CaptureCallback,Handler);这个captureRequestBuilder的目标Surface必须在创建通道时,是创建通道的第一个参数(目标List集合)中的子集,否则就报异常了。

    (2) MTK零延迟的Camera机制

    预览与拍照模式传给hal层的metadata键值对的key必须保持同步,也就是说,在同一次session会话中,你给预览传了什么样的metadata键值对的key,在拍照时同样也要传这个key,哪怕你不用这个metadata去调用底层工作,value可以传0或者任何底层不接受的值,切记这个key是必须传,否则打开相机的回调函数CameraDevice.StateCallback中onError就会被底层调用,从而导致相机无法打开。

  • 相关阅读:
    Remix 开发小技巧(三)
    Feature Pyramid Networks for Object Detection(2017.4)
    Vue-组件及组件间的通信方式
    iwebsec靶场 SQL注入漏洞通关笔记1- 数字型注入
    EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!
    MySQL-幻读与事务调度
    Mysql事务的隔离级别——关于脏读、幻读、可重复读
    TensorFlow学习笔记--(1)张量的随机生成
    项目进度管理
    Java基础教程:多线程(4)-----线程的生命周期
  • 原文地址:https://blog.csdn.net/m0_57150356/article/details/134465093