【Android音视频学习之路(四)】Camera 的使用

【Android音视频学习之路(一)】如何在 Android 平台绘制一张图片

【Android音视频学习之路(二)】AudioRecord API详解及应用

【Android音视频学习之路(三)】AudioTrack 使用与详解

一、Camera 简介

这儿介绍的是Camera2,目录为android.hardware.Camera2,不再推荐使用camera因为从api21开始这个类已经被标记为过时,现在谷歌大大推荐使用android.hardware.Camera2,但是Camera2要从api21才支持。也会说明一下camera,毕竟用法简单

借助Camera可以利用设备的相机来预览画面,拍照和拍视频。要使用Camera需要在Manifest文件中添加Manifest.permission.CAMERA 权限同时如果要进行自动对焦,还需要特性声明。

 <uses-permission android:name="android.permission.CAMERA" />
 <uses-feature android:name="android.hardware.camera" />
 <uses-feature android:name="android.hardware.camera.autofocus" />
//存储权限也需要
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
复制代码

注意:如果你只是想简单的实现拍照和拍照视频功能,可以利用Intent打开系统提供的功能。MediaStore.ACTION_IMAGE_CAPTURE 拍摄照片;MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频;

基本架构

在API架构方面, Camera2和之前的Camera有很大区别, APP和底层Camera之前可以想象成用管道方式连接, 如下图:

image.png

如上图所示, Camera APP 通过CameraCaptureSession发送CaptureRequest, CameraDevices收到请求后返回对应数据到对应的Surface,预览数据一般都是到TextureView, 拍照数据则在ImageReader中, 整体来说就是一个请求–响应过程, 请求完成后, 可以在回调中查询到相应的请求参数和CameraDevice当前状态, 总的来说, Camera2中预览/拍照/录像数据统一由Surface来接收, CaptureRequest代表请求控制的Camera参数, CameraMetadata(CaptureResult)则表示当前返回帧中Camera使用的参数以及当前状态.

使用流程

image.png

  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当前状态

查询Camera2功能支持情况

因为Camera2的功能比较多,所以不是所以手机都支持完整的Camera2功能,可能有些低端机功能就不完整,如某些中低端手机使用的HAL1, 使用HAL1就会导致Camera2一些高级功能都没法使用了, 下面讲一下如何查询设备对应Camera2的支持情况

INFO_SUPPORTED_HARDWARE_LEVEL
硬件层面支持的Camera2功能等级, 主要分为5个等级:

  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY

    向后兼容模式, 如果是此等级, 基本没有额外功能, HAL层大概率就是HAL1

  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED

    有最基本的功能, 还支持一些额外的高级功能, 这些高级功能是LEVEL_FULL的子集

  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL

    支持对每一帧数据进行控制,还支持高速率的图片拍摄

  • INFO_SUPPORTED_HARDWARE_LEVEL_3

    支持YUV后处理和Raw格式图片拍摄, 还支持额外的输出流配置

  • INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL

    API28中加入的, 应该是外接的摄像头, 功能和LIMITED类似

private fun getCameraHardwareSupport() {
        // cameraCharacteristics: 相机设备的属性类,
        // 通过 CameraManager 的 getCameraCharacteristics(String cameraId) 方法获取指定相机设备的 CameraCharacteristics 对象
        val cameraManager = cameraManager ?: return
        val cameraIdList = cameraManager.cameraIdList
        // 这里直接拿第一个摄像头
        val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraIdList[0])
        val deviceLevel = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ?: return
        when(deviceLevel) {
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY -> {}
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED -> {}
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL -> {}
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 -> {}
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL -> {}
        }
    }
复制代码

具体支持什么功能还可以通过REQUEST_AVAILABLE_CAPABILITIES来查询,详情见官方文档

二、预览 Camera 数据

众所周知有两种方法能够做到这一点:SurfaceView、TextureView。(但这2种本质上都拿到surface)

  • camera的使用
private fun previewCamera() {
        requestMultiplePermissions.launch(
            arrayOf(
                Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
        )
        camera = Camera.open()
        camera?.setDisplayOrientation(90);
        binding.surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
                camera?.setPreviewDisplay(surfaceHolder)
                camera?.startPreview()
            }

            override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {

            }

            override fun surfaceDestroyed(p0: SurfaceHolder) {
                camera?.release()
            }
        })
    }
复制代码
  • camera2的使用

android 9 以上 推荐使用 SessionConfiguration 来创建 CameraCaptureSession

image.png

    private fun previewCamera2(surface: Surface) {

        val cameraIdList = cameraManager!!.cameraIdList
        requestMultiplePermissions.launch(
            arrayOf(
                Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
        )
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ALog.e("xiao", "无相机权限")
            return
        } else {
            //handler: 用于指定调用该回调的线程。如果不指定则置null,此时系统会默认使用当前线程的looper。当需要将该回调放到后台线程处理时,可通过指定该handler的looper来设置:
            cameraManager?.openCamera(cameraIdList[0], object : CameraDevice.StateCallback() {
                override fun onOpened(camera: CameraDevice) {
                    //当camera成功打开之后会回调该方法。此时camera已经就绪,可以开始对相机进行一系列的操作,
                    //可通过调用CameraCaptureSession.createCaptureSession方法来设置第一个capture session
                    onOpenCamera(camera, surface)
                }

                override fun onDisconnected(camera: CameraDevice) {
                    //当camera不再可用,或调用CameraManager.openCamera()打开相机失败时都会
                    //调用此方法.此时任何尝试调用CameraDevice方法的操作都会失败并抛出一个
                    //CameraAccessException异常。
                    //安全策略或权限的改变、可移动相机设备的物理断开、或者当该camera需要更高优
                    //先级的camera API Client时都会导致该camera设备连接断开
                }

                override fun onError(camera: CameraDevice, p1: Int) {
                    //顾名思义。当camera出错时会回调该方法
                    //应在该方法调用CameraDevice.close()和做一些其他释放相机资源的操作,防止相机出错而导致一系列问
                }

                override fun onClosed(camera: CameraDevice) {
                    super.onClosed(camera)
                    //当调用CameraDevice.close()关闭相机设备后会回调此方法。camera为别关闭的相机设备。
                    //该方法被回调执行后,任何尝试调用camera相关的操作都会失败,并且抛出一个IllegalStateException异常
                }
            }, null)
        }
    }

 private fun onOpenCamera(cameraDevice: CameraDevice, surface: Surface) {
        val stateCallback = object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
                val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
                // 将CaptureRequest的构建器与Surface对象绑定在一起
                builder.addTarget(surface)
                cameraCaptureSession.setRepeatingRequest(builder.build(), null, null)
            }

            override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
                ALog.e("xiao", "onConfigureFailed")
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // android 9以上的相机打开方式
            val sessionConfiguration = SessionConfiguration(
                SessionConfiguration.SESSION_REGULAR,
                listOf(OutputConfiguration(surface)),
                mainExecutor,
                stateCallback
            )
            cameraDevice.createCaptureSession(sessionConfiguration)
        } else {
            //创建CaptureSession会话。第一个参数 outputs 是一个 List 数组,相机会把捕捉到的图片数据传递给该参数中的 Surface 。第二个参数 StateCallback 是创建会话的状态回调。第三个参数描述了 StateCallback 被调用时所在的线程
            cameraDevice.createCaptureSession(
                listOf(surface),
                stateCallback,
                null
            )
        }
    }

复制代码

三、取到 NV21 的数据回调

Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是
YV12。Android一般默认使用YCbCr_420_SP的格式(NV21)。

NV21 是什么?

NV21 是 YUV420p 的一种存储模式。存储顺序是先存 Y,再存 U,然后再 VU 交替存储。

YUV 是啥

YUV 是一种颜色编码方法,主要应用于电视系统和模拟视频领域。其中 YUV 代表三个分量,Y 代表明亮度,U 和 V 表示的是色度。

我们可以配置数据回调的格式:

  • camera
   val parameter = camera?.parameters
   parameter?.previewFormat = ImageFormat.NV21
   camera?.parameters = parameter
   camera?.setPreviewCallback { bytes, camera ->

  }
复制代码
  • camera2
  // 要获取每一帧的预览数据,我们需要ImageReader这个类的帮助
  // maxImages 用户希望同时访问的最大映像数。这个值应该尽可能小,以限制内存的使用。一旦用户获得了maxImages Images,在通过AcquireLatestImage()或AcquireNextImage()访问新的Image之前,必须释放其中的一个Image。必须大于0。
        val reader = ImageReader.newInstance(
            binding.surfaceViewPreview.width,
            binding.surfaceViewPreview.width,
            ImageFormat.YUV_420_888, // camera2不支持NV21,官方推荐用YUV_420_888
            2
        )
        reader.setOnImageAvailableListener({ imageReader ->
            val image = imageReader.acquireNextImage()
            if (image.format == ImageFormat.YUV_420_888) {
                /**
                 * YUV_420_888是一种Y:U:V按4:1:1的比例进行采样的格式,也就是说其中每一个UV都被四个Y共享, 888表示每一个分量都是8bits
                 * NV21也是这种YUV_420的采样格式,只是其中U,V分量的排列不一样.
                 * Y,U,V的数据是分别存储在3个plane中的;
                 * plane#0为Y分量,plane#1为U分量,plane#2为V分量;
                 **/
                ALog.e("xiao", "Y: ${image.planes[0].buffer}")
                ALog.e("xiao", "U: ${image.planes[1].buffer}")
                ALog.e("xiao", "V: ${image.planes[2].buffer}")
            }
            image.close()
        }, null)

复制代码
 builder.addTarget(reader.surface)
复制代码

源码地址

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享