Camera2 二开关相机

2023-05-16

1 创建相机项目

正如前所说的,我们会开发一个具有完整相机功能的应用程序,所以第一步要做的就是创建一个相机项目,这里我用 AS 创建了一个叫 Camera2Sample 的项目,并且有一个 Activity 叫 MainActivity。我们使用的开发语言是 Kotlin,所以如果你对 Kotlin 还不熟悉的话,建议你先去学习下 Kotlin 的基础知识。

为了降低源码的阅读难度,我不打算引入任何的第三方库,不去关注性能问题,也不进行任何模式上的设计,大部分的代码我都会写在这个 MainActivity 里面,所有的功能的实现都尽可能简化,让阅读者可以只关注重点。

2 注册相关权限

在使用相机 API 之前,必须在 AndroidManifest.xml 注册相机权限 android.permission.CAMERA,声明我们开发的应用程序需要相机权限,另外如果你有保存照片的操作,那么读写 SD 卡的权限也是必须的:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.darylgo.camera.sample">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

需要注意的是 6.0 以上的系统需要我们在程序运行的时候进行动态权限申请,所以我们需要在程序启动的时候去检查权限,有任何一个必要的权限被用户拒绝时,我们就弹窗提示用户程序因为权限被拒绝而无法正常工作:

class MainActivity : AppCompatActivity() {

    companion object {
        private const val REQUEST_PERMISSION_CODE: Int = 1
        private val REQUIRED_PERMISSIONS: Array<String> = arrayOf(
                android.Manifest.permission.CAMERA,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
    }

    /**
     * 判断我们需要的权限是否被授予,只要有一个没有授权,我们都会返回 false,并且进行权限申请操作。
     *
     * @return true 权限都被授权
     */
    private fun checkRequiredPermissions(): Boolean {
        val deniedPermissions = mutableListOf<String>()
        for (permission in REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(permission)
            }
        }
        if (deniedPermissions.isEmpty().not()) {
            requestPermissions(deniedPermissions.toTypedArray(), REQUEST_PERMISSION_CODE)
        }
        return deniedPermissions.isEmpty()
    }
}

3 配置相机特性要求

你一定不希望用户在一台没有任何相机的手机上安装你的相机应用程序吧,因为那样做是没有意义的。所以接下来要做的就是在 AndroidManifest.xml 中配置一些程序运行时必要的相机特性,如果这些特性不支持,那么用户在安装 apk 的时候就会因为条件不符合而无法安装。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.darylgo.camera.sample">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

</manifest>

我们通过 <uses-feature> 标签声明了我们的应用程序必须在具有相机的手机上才能运行。另外你还可以配置更多的特性要求,例如必须支持自动对焦的相机才能运行你的应用程序,更多的特性可以在 官方文档 上查询。

4 获取 CameraManager 实例

CameraManager 是一个负责查询和建立相机连接的系统服务,可以说 CameraManager 是 Camera2 使用流程的起点,所以首先我们要通过 getSystemService() 获取 CameraManager 实例:

private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }

5 获取相机 ID 列表

接下来我们要获取所有可用的相机 ID 列表,这个 ID 列表的长度也代表有多少个相机可以使用。使用的 API 是 CameraManager.getCameraIdList(),它会返回一个包含所有可用相机 ID 的字符串数组:

val cameraIdList = cameraManager.cameraIdList

注意:Kotlin 会将很多 Java API 的 getter 直接转换成 Kotlin 的 property 语法,所以你会看到 getCameraIdList() 被转换成了 cameraIdList,后续会有很多类似的转换,这里提前说明下,避免误解。

6 根据相机 ID 获取 CameraCharacteristics

CameraCharacteristics 是相机信息的提供者,通过它我们可以获取所有相机信息,这里我们需要根据摄像头的方向筛选出前置和后置摄像头,并且要求相机的 Hardware Level 必须是 FULL 及以上,所以首先我们要获取所有相机的 CameraCharacteristics 实例,涉及的 API 是 CameraManager.getCameraCharacteristics(),它会根据你指定的相机 ID 返回对应的相机信息:

/**
 * 判断相机的 Hardware Level 是否大于等于指定的 Level。
 */
fun CameraCharacteristics.isHardwareLevelSupported(requiredLevel: Int): Boolean {
    val sortedLevels = intArrayOf(
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    )
    val deviceLevel = this[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]
    if (requiredLevel == deviceLevel) {
        return true
    }
    for (sortedLevel in sortedLevels) {
        if (requiredLevel == sortedLevel) {
            return true
        } else if (deviceLevel == sortedLevel) {
            return false
        }
    }
    return false
}
// 遍历所有可用的摄像头 ID,只取出其中的前置和后置摄像头信息。
val cameraIdList = cameraManager.cameraIdList
cameraIdList.forEach { cameraId ->
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
    if (cameraCharacteristics.isHardwareLevelSupported(REQUIRED_SUPPORTED_HARDWARE_LEVEL)) {
        if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
            frontCameraId = cameraId
            frontCameraCharacteristics = cameraCharacteristics
        } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
            backCameraId = cameraId
            backCameraCharacteristics = cameraCharacteristics
        }
    }
}

7 开启相机

接下来我们要做的就是调用 CameraManager.openCamera() 方法开启相机了,该方法要求我们传递两个参数,一个是相机 ID,一个是监听相机状态的 CameraStateCallback。当相机被成功开启的时候会通过 CameraStateCallback.onOpened() 方法回调一个 CameraDevice 实例给你,否则的话会通过 CameraStateCallback.onError() 方法回调一个 CameraDevice 实例和一个错误码给你。onOpened() 和 onError() 其实都意味着相机已经被开启了,唯一的区别是 onError() 表示开启过程中出了问题,你必须把传递给你的 CameraDevice 关闭,而不是继续使用它,具体的 API 介绍可以自行查看文档。另外,你必须确保在开启相机之前已经被授予了相机权限,否则会抛权限异常。一个比较稳妥的做法就是每次开启相机之前检查相机权限。下面是主要代码片段:

private data class OpenCameraMessage(val cameraId: String, val cameraStateCallback: CameraStateCallback)

@SuppressLint("MissingPermission")
override fun handleMessage(msg: Message): Boolean {
    when (msg.what) {
        MSG_OPEN_CAMERA -> {
            val openCameraMessage = msg.obj as OpenCameraMessage
            val cameraId = openCameraMessage.cameraId
            val cameraStateCallback = openCameraMessage.cameraStateCallback
            cameraManager.openCamera(cameraId, cameraStateCallback, cameraHandler)
            Log.d(TAG, "Handle message: MSG_OPEN_CAMERA")
        }
    }
    return false
}

private fun openCamera() {
    // 有限选择后置摄像头,其次才是前置摄像头。
    val cameraId = backCameraId ?: frontCameraId
    if (cameraId != null) {
        val openCameraMessage = OpenCameraMessage(cameraId, CameraStateCallback())
        cameraHandler?.obtainMessage(MSG_OPEN_CAMERA, openCameraMessage)?.sendToTarget()
    } else {
        throw RuntimeException("Camera id must not be null.")
    }
}
private inner class CameraStateCallback : CameraDevice.StateCallback() {
    @WorkerThread
    override fun onOpened(camera: CameraDevice) {
        cameraDevice = camera
        runOnUiThread { Toast.makeText(this@MainActivity, "相机已开启", Toast.LENGTH_SHORT).show() }
    }

    @WorkerThread
    override fun onError(camera: CameraDevice, error: Int) {
        camera.close()
        cameraDevice = null
    }
}

8 关闭相机

和其他硬件资源的使用一样,当我们不再需要使用相机时记得调用 CameraDevice.close() 方法及时关闭相机回收资源。关闭相机的操作至关重要,因为如果你一直占用相机资源,其他基于相机开发的功能都会无法正常使用,严重情况下直接导致其他相机相关的 APP 无法正常使用,当相机被完全关闭的时候会通过 CameraStateCallback.onCllosed() 方法通知你相机已经被关闭。那么在什么时候关闭相机最合适呢?我个人的建议是在 onPause() 的时候就一定要关闭相机,因为在这个时候相机页面已经不是用户关注的焦点,大部分情况下已经可以关闭相机了。

@SuppressLint("MissingPermission")
override fun handleMessage(msg: Message): Boolean {
    when (msg.what) {
        MSG_CLOSE_CAMERA -> {
            cameraDevice?.close()
            Log.d(TAG, "Handle message: MSG_CLOSE_CAMERA")
        }
    }
    return false
}

override fun onPause() {
    super.onPause()
    closeCamera()
}

private fun closeCamera() {
    cameraHandler?.sendEmptyMessage(MSG_CLOSE_CAMERA)
}
private inner class CameraStateCallback : CameraDevice.StateCallback() {
    @WorkerThread
    override fun onClosed(camera: CameraDevice) {
        cameraDevice = null
        runOnUiThread { Toast.makeText(this@MainActivity, "相机已关闭", Toast.LENGTH_SHORT).show() }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Camera2 二开关相机 的相关文章

  • Android 端处理 YUV 数据 - Libyuv 的编译与使用

    在 Android 系统上 Camera 输出的图像一般为 NV21 YUV420SP 系列 格式 当我们想进行录像处理时 会面临两个问题 问题 1 图像的旋转问题 后置镜头 需要旋转 90 前置镜头 需要旋转 270 然后再进行镜像处理
  • YUV420P旋转

    YUV420与YUV420P YUV 和我们熟知的 RGB 类似 xff0c 是一种颜色编码格式 它主要用于电视系统和模拟视频邻域 xff08 如 Camera 系统 xff09 YUV 包含三个分量 xff0c 其中 Y 表示明亮度 xf
  • android-camera方向

    1 概念解释 自然方向 xff1a 指当宽比高短时 xff0c 我们看到的手机的方向 xff08 竖屏 xff09 xff0c 就是自然方向 2 相机图像传感器采集图像的方向 由于手机Camera拍摄到的图片来自相机的图像传感器 xff0c

随机推荐

  • clion创建第一个C项目

    点击new project 选择C Executable 输入路径 Language standard C99 main c include lt stdio h gt void main char string 61 34 I love
  • VMware Ubuntu虚拟机忘记密码

    前言 xff1a 在VMware运行Ubuntu虚拟机时 xff0c 开机之后忘记密码怎么办 xff1f 环境 xff1a Ubuntu版本 xff1a ubuntu 16 04 6 server amd64 xff1b VMware版本
  • Ubuntu18.04安装matlabR2019A

    载安装包和破解文件 链接 https pan baidu com s 1X09GAchToEqyMRol3msGAA 密码 wak6 下载完成后解压 右击 iso镜像文件 xff0c 选择使用其他程序打开 选择磁盘映像挂载器 打开后会在桌面
  • 03-对抗样本攻击

    对抗样本攻击 Github xff1a https github com Gary11111 03 GAN 研究背景 尽管深度学习在很多计算机视觉领域的任务上表现出色 xff0c Szegedy第一次发现了深度神经网络在图像分类领域存在有意
  • 冒泡排序算法

    冒泡排序是非常好理解的 xff0c 以从小到大排序为例 xff0c 每一轮排序就找出未排序序列中最大值放在最后 设数组的长度为N xff1a xff08 1 xff09 比较前后相邻的二个数据 xff0c 如果前面数据大于后面的数据 xff
  • 将一个分支上的commit 转移到另一个分支上git cherry-pick <commit id>

    使用 cherry pick 根据git 文档 xff1a Apply the changes introduced by some existing commits 就是对已经存在的commit 进行apply 可以理解为再次提交 xff
  • Git 如何查看和修改用户名、邮箱

    用户名和邮箱地址是本地git客户端的一个变量 xff0c 不随git库而改变 每次commit都会用用户名和邮箱纪录 1 查看用户名和地址 git config user name git config user email 2 修改用户名
  • Android PendingIntent

    在Android中 xff0c 我们常常使用PendingIntent来表达一种 留待日后处理 的意思 从这个角度来说 xff0c PendingIntent可以被理解为一种特殊的异步处理机制 不过 xff0c 单就命名而言 xff0c P
  • .CR2格式文件怎么快速批量转换成JPG等格式

    打开需要转换的CR2文件夹 用FSViewer exe看图软件打开CR2文件 xff0c 然后再双击打开已打开的图片并选中所有需要转换的CR2文件 点击工具 选择批量转换选中的图像 若是右边窗口中没有文件 xff0c 则需要手动点击 全部添
  • 利用AlarmManager完成精准的轮询

    问题分析 想起轮询我们一般会想起利用Handler和Timer xff0c 然而AlarmManager相比于Handler和Timer有优势 xff0c 具体的分析我参考了一个大神的博客 xff1a 最近在做一个需求 xff1a 客户端按
  • Intellij IDEA junit 使用之org.junit不存在

    1 同理打开 xff1a File gt project Structure gt librabries gt 点击左上角 43 号 gt From Maven 2 输入 junit 4 12 3 确定即可 在build gradle文件中
  • Android studio显示你的主机中的软件中止了一个已建立的连接

    Android studio在run的时候显示你的主机中的软件中止了一个已建立的连接 出现的场景 是在run项目的时候出现了这样的 如下图 当我把热点关了就好了 再也不会出现类似的情况了
  • 设计模式、重构、编程规范等的经典书籍书籍推荐

    有关设计模式 重构 编程规范等的经典书籍很多 xff0c 有很多你应该已经听说过 甚至看过 今天 xff0c 我就结合我的经验 xff0c 对这些书籍进行一个整理和点评 你可以据此来选择适合你的书籍 xff0c 结合着专栏一块儿来学习 xf
  • Android打开或者关闭GPS

    打开和关闭gps 需要系统权限 android permission WRITE SECURE SETTINGS 打开或者关闭gps public void openGPS boolean open if Build VERSION SDK
  • 【Erro】安装homebrew报错curl: (7) Failed to connect to raw.githubusercontent.com port 443: Operation

    下载brew bin bash c 34 curl fsSL https raw githubusercontent com Homebrew install master install sh 34 出现这样的错误 xff1f curl
  • ChatGPT火爆科研圈,登上《Nature》《Science》正刊

    ChatGPT火出圈了 xff0c 几乎涉及到各行各业的每个领域 xff0c 科研圈更甚 Science 期刊主编H HOLDEN THORP发表关于ChatGPT的社论 xff1a ChatGPT is fun but not an au
  • 解决 Unable to determine application id: com.android.tools.idea.run.ApkProvisionException

    问题 xff1a Unable to determine application id com android tools idea run ApkProvisionException Error loading build artifac
  • Android SurfaceView预览变形完美解决方法

    这个问题百度上一搜一大把 xff0c 基本上都是说找到和SurfaceView的比例相近的camera预览尺寸 xff0c 但是发现预览时候还是差了点意思 xff0c 具体看下面这个回调就知道是为什么了 64 Override public
  • Camera2 教程 一概览

    从 Android 5 0 开始 xff0c Google 引入了一套全新的相机框架 Camera2 xff08 android hardware camera2 xff09 并且废弃了旧的相机框架 Camera1 xff08 androi
  • Camera2 二开关相机

    1 创建相机项目 正如前所说的 xff0c 我们会开发一个具有完整相机功能的应用程序 xff0c 所以第一步要做的就是创建一个相机项目 xff0c 这里我用 AS 创建了一个叫 Camera2Sample 的项目 xff0c 并且有一个 A