Android开发 Camera2获取输出SurfaceTexture

目录

一、Camera2概述

1 Pipeline

2 CameraManager

3 CameraDevice

4 CameraCharacteristics

5 CameraCaptureSession

6 CaptureRequest

7 Surface

8 CaptureResult

三、Camera2的特性

1 Camera2 才支持的高级特性

2 Camera1 迁移到 Camera2 

 二、示例源码


一、Camera2概述

1 Pipeline

Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了 Pipeline 的工作流程,我们会通过一个简单的例子详细解释这张图

为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:

  1. 创建一个用于从 Pipeline 获取图片的 CaptureRequest。
  2. 修改 CaptureRequest 的闪光灯配置,让闪光灯在拍照过程中亮起来。
  3. 创建两个不同尺寸的 Surface 用于接收图片数据,并且将它们添加到 CaptureRequest 中。
  4. 发送配置好的 CaptureRequest 到 Pipeline 中等待它返回拍照结果。

一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。

2 CameraManager

CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个 CameraManager 的关键功能:

  1. 将相机信息封装到 CameraCharacteristics 中,并提获取 CameraCharacteristics 实例的方式。
  2. 根据指定的相机 ID 连接相机设备。
  3. 提供将闪光灯设置成手电筒模式的快捷方式。

3 CameraDevice

CameraDevice 代表当前连接的相机设备,它的职责有以下四个:

  1. 根据指定的参数创建 CameraCaptureSession。
  2. 根据指定的模板创建 CaptureRequest。
  3. 关闭相机设备。
  4. 监听相机设备的状态,例如断开连接、开启成功和开启失败等。

熟悉 Camera1 的人可能会说 CameraDevice 就是 Camera1 的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的 CameraCaptureSession。

4 CameraCharacteristics

CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等等。如果你对 Camera1 比较熟悉,那么 CameraCharacteristics 有点像 Camera1 的 Camera.CameraInfo 或者 Camera.Parameters

5 CameraCaptureSession

CameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。

6 CaptureRequest

CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。

7 Surface

Surface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。

8 CaptureResult

CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。

三、Camera2的特性

1 Camera2 才支持的高级特性

如果要我给出强有力的理由解释为什么要使用 Camera2,那么通过 Camera2 提供的高级特性可以构建出更加高质量的相机应用程序应该是最佳理由了。

  1. 在开启相机之前检查相机信息
    出于某些原因,你可能需要先检查相机信息再决定是否开启相机,例如检查闪光灯是否可用。在 Caemra1 上,你无法在开机相机之前检查详细的相机信息,因为这些信息都是通过一个已经开启的相机实例提供的。在 Camera2 上,我们有了和相机实例完全剥离的 CameraCharacteristics 实例专门提供相机信息,所以我们可以在不开启相机的前提下检查几乎所有的相机信息。

  2. 在不开启预览的情况下拍照
    在 Camera1 上,开启预览是一个很重要的环节,因为只有在开启预览之后才能进行拍照,因此即使显示预览画面与实际业务需求相违背的时候,你也不得不开启预览。而 Camera2 则不强制要求你必须先开启预览才能拍照。

  3. 一次拍摄多张不同格式和尺寸的图片
    在 Camera1 上,一次只能拍摄一张图片,更不同谈多张不同格式和尺寸的图片了。而 Camera2 则支持一次拍摄多张图片,甚至是多张格式和尺寸都不同的图片。例如你可以同时拍摄一张 1440x1080 的 JPEG 图片和一张全尺寸的 RAW 图片。

  4. 控制曝光时间
    在暗环境下拍照的时候,如果能够适当延长曝光时间,就可以让图像画面的亮度得到提高。在 Camera2 上,你可以在规定的曝光时长范围内配置拍照的曝光时间,从而实现拍摄长曝光图片,你甚至可以延长每一帧预览画面的曝光时间让整个预览画面在暗环境下也能保证一定的亮度。而在 Camera1 上你只能 YY 一下。

  5. 连拍
    连拍 30 张图片这样的功能在 Camera2 出现之前恐怕只有系统相机才能做到了(通过 OpenGL 截取预览画面的做法除外),也可能是出于这个原因,市面上的第三方相机无一例外都不支持连拍。有了 Camera2,你完全可以让你的相机应用程序支持连拍功能,甚至是连续拍 30 张使用不同曝光时间的图片。

  6. 灵活的 3A 控制
    3A(AF、AE、AWB)的控制在 Camera2 上得到了最大化的放权,应用层可以根据业务需求灵活配置 3A 流程并且实时获取 3A 状态,而 Camera1 在 3A 的控制和监控方面提供的接口则要少了很多。例如你可以在拍照前进行 AE 操作,并且监听本这次拍照是否点亮闪光灯。

2 Camera1 迁移到 Camera2 

如果你熟悉 Camera1,并且打算从 Camera1 迁移到 Camera2 :

  • Camera1 严格区分了预览和拍照两个流程,而 Camera2 则把这两个流程都抽象成了 Capture 行为,只不过一个是不断重复的 Capture,一个是一次性的 Capture 而已,所以建议你不要带着过多的 Camera1 思维使用 Camera2,避免因为思维上的束缚而无法充分利用 Camera2 灵活的 API。

  • 如同 Camera1 一样,Camera2 的一些 API 调用也会耗时,所以建议你使用独立的线程执行所有的相机操作,尽量避免直接在主线程调用 Camera2 的 API,HandlerThread 是一个不错的选择。

  • Camera2 所有的相机操作都可以注册相关的回调接口,然后在不同的回调方法里写业务逻辑,这可能会让你的代码因为不够线性而错综复杂,建议你可以尝试使用子线程的阻塞方式来尽可能地保证代码的线性执行(熟悉 Dart 的人一定很喜欢它的 async 和 await 操作)。例如在子线程阻塞等待 CaptureResult,然后继续执行后续的操作,而不是将代码拆分到到 CaptureCallback.onCaptureCompleted() 方法里。

  • 你可以认为 Camera1 是 Camera2 的一个子集,也就是说 Camera1 能完成的事情 Camera2 一定能做,反过来则不一定能实现。

  • 如果你的应用程序需要同时兼容 Camera1 和 Camera2,建议分开维护,因为 Camera1 的 API 设计很可能让 Camera2 灵活的 API 无法得到充分的发挥,另外将两个设计上完全不兼容的东西搅和在一起带来的痛苦可能远大于其带来便利性。

  • 官方说 Camera2 的性能会更好,起码在较早期的一些机器上运行 Camera2 的性能并没有比 Camera1 好。

  • 当设备的 Supported Hardware Level 低于 FULL 的时候,建议还是使用 Camera1,因为 FULL 级别以下的 Camera2 能提供的功能几乎和 Camera1 一样,所以倒不如选择更加稳定的 Camera1。

 二、示例源码


class Camera2Helper private constructor(val mContext: Context) {
    companion object {
        const val DEFAULT_HEIGHT = 1920
        const val DEFAULT_WIDTH = 1080
        const val CAMERA_ID_FRONT = "1"
        const val CAMERA_ID_BACK = "0"

        fun build(mContext: Context): Camera2Helper {

            return Camera2Helper(mContext)
        }
    }

    var currentHeight = DEFAULT_HEIGHT  //默认设置的预览高度
    var currentWidth = DEFAULT_WIDTH //默认设置的预览宽度
    var cameraID = CAMERA_ID_FRONT //默认设置的前置摄像头

    //相机设备
    private var mCameraDevice: CameraDevice? = null

    //预览纹理
    private var mSurfaceTexture: SurfaceTexture? = null
    private var mCameraHandler: Handler? = null
    private var mCameraThread: HandlerThread? = null
    private var mCameraSession: CameraCaptureSession? = null
    private lateinit var mCaptureRequestBuild: CaptureRequest.Builder

    /**
     * 开始
     */
    fun onStart(mSurfaceTexture: SurfaceTexture) {
        this.mSurfaceTexture = mSurfaceTexture
        val mCameraThread = HandlerThread("CameraHandler")
        this.mCameraThread = mCameraThread
        mCameraThread.start()
        mCameraHandler = Handler(mCameraThread.looper)
        openCamera(mContext)
    }


    /**
     * 重新开始
     */
    fun onRestart() {
        //避免误调用
        onStop()
        val mCameraThread = HandlerThread("CameraHandler")
        this.mCameraThread = mCameraThread
        mCameraThread.start()
        mCameraHandler = Handler(mCameraThread.looper)
        openCamera(mContext)
    }

    /**
     * 暂停释放
     */
    fun onStop() {
        mCameraSession?.stopRepeating()
        mCameraDevice?.close()
        mCameraThread?.quitSafely()
        try {
            mCameraThread?.join()
            mCameraSession = null
            mCameraDevice = null
            mCameraHandler = null
            mCameraThread = null;
        } catch (e: InterruptedException) {
            Log.e("Camera2Helper", e.toString())
        }
    }

    /**
     * 切换摄像头
     */
    fun switchCamera() {
        cameraID = if (cameraID == CAMERA_ID_FRONT) CAMERA_ID_BACK else CAMERA_ID_FRONT
        onStop()
        onRestart()
    }

    /**
     *  开启摄像头
     */
    private fun openCamera(mContext: Context) {
        try {
            val cameraManager =
                mContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager

            if (ActivityCompat.checkSelfPermission(
                    mContext, Manifest.permission.CAMERA
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                cameraManager.openCamera(cameraID, mDeviceStateCallback, mCameraHandler)
            }
        } catch (e: CameraAccessException) {
            Log.e("Camera2Helper", e.toString())
        }
    }

    /**
     * 开启摄像头 回调
     */
    private val mDeviceStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            mCameraDevice = camera
            createCameraSession(camera)
        }

        override fun onDisconnected(camera: CameraDevice) {
            Log.e("Camera2Helper", " onDisconnected")
            onStop()
        }

        override fun onError(camera: CameraDevice, error: Int) {
            Log.e("Camera2Helper", "CameraDevice.StateCallback(): $error")
        }

    }

    /**
     * 创建CameraSession
     */
    private fun createCameraSession(mCameraDevice: CameraDevice) {
        try {
            // 我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
            mSurfaceTexture?.let {
                it.setDefaultBufferSize(currentWidth, currentHeight)
                // 输出surface。
                val surface = Surface(it)
                //构建请求
                mCaptureRequestBuild =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)  //有其他配置自行看源码
                mCaptureRequestBuild.addTarget(surface)  //必须添加 OutputConfiguration中的surface
                //版本兼容
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    val outConfig = OutputConfiguration(surface)
                    // 输出配置
                    val config = SessionConfiguration(
                        SessionConfiguration.SESSION_REGULAR,  //有其他配置自行看源码
                        listOf(outConfig),
                        ThreadPoolUtil.executor,  //线程池
                        mCaptureStateCallback
                    )
                    mCameraDevice.createCaptureSession(config)
                } else {
                    mCameraDevice.createCaptureSession(
                        mutableListOf(surface),
                        mCaptureStateCallback,
                        mCameraHandler
                    )
                }
            }
        } catch (e: CameraAccessException) {
            Log.d("Camera2Helper", e.toString())
        }
    }

    /**
     * 会话状态回调
     */
    private val mCaptureStateCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            mCameraSession = session
            //设置请求
            session.setRepeatingRequest(
                mCaptureRequestBuild.build(),
                mCaptureCallback,
                mCameraHandler
            )
        }

        override fun onConfigureFailed(session: CameraCaptureSession) {

        }
    }

    //请求回调 可以不是实现任何回调
    private val mCaptureCallback = object : CameraCaptureSession.CaptureCallback() {

    }


}

如果对您有一些意义,希望您给博主一些鼓励(点赞、关注、收藏),如果有错误欢迎大家评论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要预览摄像头角度,可以通过设置SurfaceTexture的transform matrix来实现。同样需要获取当前设备方向和相机传感器方向的差异,可以使用以下代码: ```java int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); int deviceOrientation = getWindowManager().getDefaultDisplay().getRotation(); int surfaceRotation = ORIENTATIONS.get(deviceOrientation); int rotation = (sensorOrientation - surfaceRotation + 360) % 360; ``` 其中,characteristics是当前相机设备的CameraCharacteristics实例,ORIENTATIONS是一个常量数组,包含了设备方向和Surface方向之间的映射关系。 接下来,根据计算出的rotation值,设置SurfaceTexture的transform matrix: ```java Matrix matrix = new Matrix(); RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); float scale = Math.max( (float) viewHeight / previewSize.getHeight(), (float) viewWidth / previewSize.getWidth()); matrix.postScale(scale, scale, centerX, centerY); matrix.postRotate(90 * (rotation - 2), centerX, centerY); } else if (rotation == Surface.ROTATION_180) { matrix.postRotate(180, centerX, centerY); } surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); surfaceTexture.setTransform(matrix); ``` 上述代码中,viewWidth和viewHeight分别是SurfaceTexture的宽高,previewSize是预览尺寸。根据当前方向的不同,使用不同的变换方式设置transform matrix。最后,将变换矩阵应用到SurfaceTexture上即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值