一篇文章带你了解Android 最新Camera框架

这篇文章主要分下面几点来展开讲解:
1)Android 最新Camera 整体框架;
2)Android Camera2 和HAL3 的基本了解;
3)Camera2 介绍;
本文所写的内容基于Android 9.0

一、Android最新Camera 整体框架

Android Camera整体框架主要包括三个进程:app进程camera server进程hal进程(provider进程)。进程之间的通信都是通过binder实现,其中app和camera server通信使用 AIDL(Android Interface Definition Language) ,camera server和hal(provider进程)通信使用HIDL(HAL interface definition language) 。

Android上面的框架分级,基本都是类似的,应用层-> framework层->Hal层,我们ps看下设备上实际的进程情况,如下图所示,可以看到有cameraserver和provider进程。cameraservice是负责app和framework层的通信,而provider进程则是负责framework和hal层之间的通信。


(附:Android 8.0 重新设计了 Android 操作系统框架(在一个名为“Treble”的项目中),以便让制造商能够以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。

Android O之后使用Treble的架构,为了解决Android系统的碎片化问题和提高系统更新的效率,减少了framework 和HAL 的耦合性,进而引出了HIDL 的概念。

HIDL 全称为HAL interface definition language(发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。

HIDL 的目标是,框架可以在无需重新构建 HAL 的情况下进行替换。HAL 将由供应商或 SOC 制造商构建,放置在设备的 /vendor 分区中,这样一来,框架就可以在其自己的分区中通过 OTA 进行替换,而无需重新编译 HAL,这也是Project Treble框架设计而诞生的。)

如下图所示,展示了Android Camera的最新框架,我们先大概看下图片流程,对整体框架有个基本了解。


Camera HAL3框架01
Camera HAL3框架02

二、Android Camera2 和HAL3 的基本了解

1) Camera2 接口什么时候开始引入的?

Android 5.0开始,Google 引入了一套全新的相机框架 Camera2(android.hardware.camera2)并且废弃了旧的相机框架 Camera1(android.hardware.Camera)
不了解的同学,可能会有疑问,为啥要废弃Camera1接口?基本原因是,camera1接口过于简单,没法满足更加复杂的相机应用场景。为了给应用层提供更多的相机控制权限,从而构建出更高质量的相机应用程序,Google才推出了Camera2 接口。下面可以看下和Camera1比较,Camera2有哪些高级特性。

2)一些只有 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 操作,并且监听本这次拍照是否点亮闪光灯。
3)何为HAL3?

为了配合Camera2 的使用,Android Hal层Camera框架也做了相对应的改动,也就是HAL3。Camera1接口对应的是调用的HAL1框架。

4)一些概念

关于Camera2 和Hal3,有些基本概念我们得了解下~~
我们先来看下Camera2 API涉及到哪些类,下面会对各个类的使用进行讲解~~


Camera2API类
1) Pipeline

Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了 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 )Supported Hardware Level

相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以 Camera2 定义了一个叫做 Supported Hardware Level 的重要概念,其作用是将不同设备上的 Camera2 根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上 Camera2 的支持情况。截止到 Android P 为止,从低到高一共有 LEGACY、LIMITED、FULL 和 LEVEL_3 四个级别:

  1. LEGACY:向后兼容的级别,处于该级别的设备意味着它只支持 Camera1 的功能,不具备任何 Camera2 高级特性。
  2. LIMITED:除了支持 Camera1 的基础功能之外,还支持部分 Camera2 高级特性的级别。
  3. FULL:支持所有 Camera2 的高级特性。
  4. LEVEL_3:新增更多 Camera2 高级特性,例如 YUV 数据的后处理等。
3 )Capture

相机的所有操作和参数配置最终都是服务于图像捕获,例如对焦是为了让某一个区域的图像更加清晰,调节曝光补偿是为了调节图像的亮度。因此,在 Camera2 里面所有的相机操作和参数配置都被抽象成 Capture(捕获),所以不要简单的把 Capture 直接理解成是拍照,因为 Capture 操作可能仅仅是为了让预览画面更清晰而进行对焦而已。如果你熟悉 Camera1,那你可能会问 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告诉你,它们都是通过 Capture 来实现的。

Capture 从执行方式上又被细分为【单次模式】、【多次模式】和【重复模式】三种,我们来一一解释下:

  • 单次模式(One-shot):指的是只执行一次的 Capture 操作,例如设置闪光灯模式、对焦模式和拍一张照片等。多个一次性模式的 Capture 会进入队列按顺序执行。
  • 多次模式(Burst):指的是连续多次执行指定的 Capture 操作,该模式和多次执行单次模式的最大区别是连续多次 Capture 期间不允许插入其他任何 Capture 操作,例如连续拍摄 100 张照片,在拍摄这 100 张照片期间任何新的 Capture 请求都会排队等待,直到拍完 100 张照片。多组多次模式的 Capture 会进入队列按顺序执行。
  • 重复模式(Repeating):指的是不断重复执行指定的 Capture 操作,当有其他模式的 Capture 提交时会暂停该模式,转而执行其他被模式的 Capture,当其他模式的 Capture 执行完毕后又会自动恢复继续执行该模式的 Capture,例如显示预览画面就是不断 Capture 获取每一帧画面。该模式的 Capture 是全局唯一的,也就是新提交的重复模式 Capture 会覆盖旧的重复模式 Capture。

我们举个例子来进一步说明上面三种模式,假设我们的相机应用程序开启了预览,所以会提交一个重复模式的 Capture 用于不断获取预览画面,然后我们提交一个单次模式的 Capture,接着我们又提交了一组连续三次的多次模式的 Capture,这些不同模式的 Capture 会按照下图所示被执行:


下面是几个重要的注意事项:

  1. 无论 Capture 以何种模式被提交,它们都是按顺序串行执行的,不存在并行执行的情况。
  2. 重复模式是一个比较特殊的模式,因为它会保留我们提交的 CaptureRequest 对象用于不断重复执行 Capture 操作,所以大多数情况下重复模式的 CaptureRequest 和其他模式的 CaptureRequest 是独立的,这就会导致重复模式的参数和其他模式的参数会有一定的差异,例如重复模式不会配置 CaptureRequest.AF_TRIGGER_START,因为这会导致相机不断触发对焦的操作。
  3. 如果某一次的 Capture 没有配置预览的 Surface,例如拍照的时候,就会导致本次 Capture 不会将画面输出到预览的 Surface 上,进而导致预览画面卡顿的情况,所以大部分情况下我们都会将预览的 Surface 添加到所有的 CaptureRequest 里。
4) CameraManager

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

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

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

6 ) CameraDevice

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

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

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

7) Surface

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

8) CameraCaptureSession

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

9 ) CaptureRequest

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

10) CaptureResult

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

11) Request的整体处理流程
request的整体处理流程

三、 代码实战:如何拍摄单张照片

拍摄单张照片是最简单的拍照模式,它使用的就是单次模式的 Capture,我们会使用 ImageReader 创建一个接收照片的 Surface,并且把它添加到 CaptureRequest 里提交给相机进行拍照,最后通过 ImageReader 的回调获取 Image 对象,进而获取 JPEG 图像数据进行保存。

1) 定义回调接口

当拍照完成的时候我们会得到两个数据对象,一个是通过 onImageAvailable() 回调给我们的存储图像数据的 Image,一个是通过 onCaptureCompleted() 回调给我们的存储拍照信息的 CaptureResult,它们是一一对应的,所以我们定义了如下两个回调接口:

private val captureResults: BlockingQueue<CaptureResult> = LinkedBlockingDeque()

private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
    @MainThread
    override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
        super.onCaptureCompleted(session, request, result)
        captureResults.put(result)
    }
}

private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {
    @WorkerThread
    override fun onImageAvailable(imageReader: ImageReader) {
        val image = imageReader.acquireNextImage()
        val captureResult = captureResults.take()
        if (image != null && captureResult != null) {
            // Save image into sdcard.
        }
    }
}

2) 创建 ImageReader

创建 ImageReader 需要我们指定照片的大小,所以首先我们要获取支持的照片尺寸列表,并且从中筛选出合适的尺寸,假设我们要求照片的尺寸最大不能超过 4032x3024,并且比例必须是 4:3,所以会有如下筛选尺寸的代码片段:

@WorkerThread
private fun getOptimalSize(cameraCharacteristics: CameraCharacteristics, clazz: Class<*>, maxWidth: Int, maxHeight: Int): Size? {
    val streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
    val supportedSizes = streamConfigurationMap?.getOutputSizes(clazz)
    return getOptimalSize(supportedSizes, maxWidth, maxHeight)
}

@AnyThread
private fun getOptimalSize(supportedSizes: Array<Size>?, maxWidth: Int, maxHeight: Int): Size? {
    val aspectRatio = maxWidth.toFloat() / maxHeight
    if (supportedSizes != null) {
        for (size in supportedSizes) {
            if (size.width.toFloat() / size.height == aspectRatio && size.height <= maxHeight && size.width <= maxWidth) {
                return size
            }
        }
    }
    return null
}

接着我们就可以筛选出合适的尺寸,然后创建一个图像格式是 JPEG 的 ImageReader 对象,并且获取它的 Surface:

val imageSize = getOptimalSize(cameraCharacteristics, ImageReader::class.java, maxWidth, maxHeight)!!
jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)
jpegSurface = jpegImageReader?.surface

3) 创建 CaptureRequest

接下来我们使用 TEMPLATE_STILL_CAPTURE 模板创建一个用于拍照的 CaptureRequest.Builder 对象,并且添加拍照的 Surface 和预览的 Surface 到其中:

captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureImageRequestBuilder.addTarget(previewDataSurface)
captureImageRequestBuilder.addTarget(jpegSurface)

附:

  1. Google Android Camera Demo 代码:
    https://github.com/android/camera-samples
  2. Google文档,Android Camera 版本介绍
    https://source.android.google.cn/devices/camera/versioning
  3. Google 开发者文档,Camera2 API 介绍
    https://developer.android.google.cn/reference/android/hardware/camera2/package-summary.html

*本人从事Android Camera相关开发已有5年,
*目前在深圳上班,
*小伙伴记得点我头像,看【个人介绍】进行关注哦,希望和更多的小伙伴一起交流 ~

-------- 2021.03.10 深圳 18:08

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容