Android camera2 框架学习记录

安卓相机架构概览
Android系统利用分层思想,将各层的接口定义与实现分离开来,以接口作为各层的脉络连接整体框架,将具体实现的主导权交由各自有具体实现需求的平台厂商或者Android 开发者,这样既做到把控全局,也给予了众多开发者足够大的创作空间,这体现出了一个优秀的开源系统应有的胸怀和远见。其中,谷歌根据职能的不同将Camera框架一共划分成了五层,分别是App、Service、Provider、Driver以及Hardware,下面的Camera的整体架构图很清晰地显示出了其五层架构以及相互的关联接口。

Camera App
应用层处于整个框架的顶端,承担着于用户直接进行交互的责任,承接来自用户直接或者间接的比如预览/拍照/录像等一系列具体需求,一旦接收到用户相关UI操作,便会通过Camera Api v2标准接口将需求发送至Camera Framework部分,并且等待Camera Framework回传处理结果,其中包括了图像数据以及整体相机系统状态参数,之后将结果以一定方式反馈给用户,达到记录显示种种美好瞬间的目的。

Camera Framework
该层主要位于Camera App与Camera Service之间,以jar包的形式运行在App进程中,它封装了Camera Api v2接口的实现细节,暴露接口给App进行调用,进而接收来自App的请求,同时维护着请求在内部流转的业务逻辑,最终通过调用Camera AIDL跨进程接口将请求发送至Camera Service中进行处理,紧接着,等待Camera Service结果的回传,进而将最终结果发送至App。

Camera Service
该层位于Camera Framework与Camera Provider之间,作为一个独立进程存在于Android系统中,在系统启动初期会运行起来,它封装了Camera AIDL跨进程接口,提供给Framework进行调用,进而接收来自Framework的图像请求,同时内部维护着关于请求在该层的处理逻辑,最终通过调用Camera HIDL跨进程接口将请求再次下发到Camera Provider中,并且等待结果的回传,进而将结果上传至Framework中。

Camera Provider
该层位于Camera Service与Camera Driver之间,作为一个独立的进程存在于Android系统中,同时在系统启动初期被运行,提供Camera HIDL跨进程接口供Camera Service进行调用,封装了该接口的实现细节,接收来自Service的图像请求,并且内部加载了Camera HAL Module,该Module由OEM/ODM实现,遵循谷歌制定的标准Camera HAL3接口,进而通过该接口控制Camera HAL部分,最后等待Camera HAL的结果回传,紧接着Provider通过Camera HIDL接口将结果发送至Camera Service。

CamX-CHI(Camera HAL)
该部分是高通对谷歌Camera HAL3接口的实现,以so库的形式被加载至Camera Provider中,之前采用的是QCamera & MM-Camera架构,但是为了更好灵活性和可扩展性,而今高通又提出了CamX-CHI架构,该架构提供HAL3接口给Provider进行调用,接收来自Provider的请求,而内部对HAL3接口进行了实现,并且通过V4L2标准框架控制着相机驱动层,将请求下发至驱动部分,并且等待结果回传,进而上报给Camera Provider。

CamX-CHI架构由CamX和CHI两个部分组成,CamX负责一些基础服务代码的实现,不经常改动,CHI负责实现一些可扩展性和定制化的需求,方便OEM/ODM添加自己的扩展功能。CamX主要包括实现HAL3入口的hal模块,实现与V4L2驱动交互的csl模块,实现硬件node的hwl和实现软件node的swl。CHI通过抽象出Usecase、Feature、Session、Pipeline、Node的概念,使厂商可以通过实现Node接口来接入自己的算法,并通过XML文件灵活配置Usecase、Pipeline、Node的结构关系。

Camera Driver
Linux为视频采集设备制定了标准的V4L2接口,并在内核中实现了其基础框架V4L2 Core。用户空间进程可以通过V4L2接口调用相关设备功能,而不用考虑其实现细节。V4L2提出了总设备和子设备的概念,并通过media controller机制向用户空间暴露自己的硬件拓扑结构。视频采集设备驱动厂商按照V4L2 Core的要求开发自己的驱动程序,只需要实现相应的结构体和函数接口并调用注册函数注册自己就行。

在高通平台上,高通对相机驱动部分进行了实现,利用了V4L2框架的可扩展特性,设计出了一套独特的KMD框架。在该框架内部主要包含了三个部分,CRM、Camera Sync以及一系列子设备,首先,作为框架顶层管理者,CRM创建了一个V4L2主设备用来管理所有的子设备,并且暴露设备节点video0给用户空间,同时内部维护着整个底层驱动业务逻辑。其次,Camera Sync创建了一个V4L2主设备,同时暴露了设备节点video1给用户空间,主要用于向用户空间反馈图像数据处理状态。最后,子设备模块被抽象成v4l2_subdev设备,同样也暴露设备节点v4l2-subdev给用户空间进行更精细化的控制。另外,在整个框架初始化的过程中,通过media controller机制,保持了在用户空间进行枚举底层硬件设备的能力。

Camera Hardware
相机硬件处在整个相机体系的最底层,是相机系统的物理实现部分,该部分包括镜头、感光器、ISP三个最重要的模块,还有对焦马达、闪光灯、滤光片、光圈等辅助模块。镜头的作用是汇聚光线,利用光的折射性把射入的光线汇聚到感光器上。感光器的作用是负责光电转换,通过内部感光元件将接收到的光信号转换为电子信号进而通过数电转换模块转为数字信号,并最后传给ISP。ISP负责对数字图像进行一些算法处理,如白平衡、降噪、去马赛克等。

通过上面的介绍,我们可以发现,谷歌通过以上五级分层,形成了整个相机框架体系,其中层与层之间通过行业协会、开源社区或者谷歌制订的标准接口进行连接,上层通过调用标准接口下发请求到下层,下层负责对标准接口进行实现,最终将请求再次封装并调用下一层级的对外接口下发到下层。所以总得来说,谷歌使用标准接口作为骨架搭建整体框架,而其具体实现交由各层自己负责,从整体上来看,职责划分明确,界限分明,这样的设计,一来利用标准接口,保持了整个框架业务正常流转,二来极大地降低了各层耦合度,保持了各层的相互独立,最终让整个框架处于一个稳定同时高效的运行状态。

重点关注camera框架的后三层,即provider、driver、hardware,前两层大致了解流程框架。

一、App:
一个简单的相机应用流程如下:

a) 初始化
随着应用的打开,首先在MainActivity的onResume方法中去初始化用于拍照的按钮和接收数据的ImageReader,并且设置其各自回调方法。

b) 获取CameraManager/打开Camera 设备
其次在TextureView.SurfaceTextureListener中的回调方法onSurfaceTextureAvailable会被调用,在该方法中会去获取CameraManager服务,并打调用其openCamera方法打开后主摄相机设备,并通过回调接口获取该设备,紧接着调用startPreview方法。

c) 创建Camera Capture Session/下发预览需求
在startPreview方法中会去下发预览需求,主要工作有设置TextureView缓冲区大小,创建用于接收预览数据的Surface,并调用CameraDevice的createCaptureSession方法创建CameraCaptureSession,在其回调接口onConfigured方法中,去创建CaptureRequest,并调用CameraCaptureSession的setRepeatingRequest方法下发Request到相机框架中。

d) 返回预览Metadata/图像数据
在完成了预览需求的Request的下发工作后,相机框架便会不断通过传入的CaptureCallback中的onCaptureComplete方法上传Meta Data以及通过BufferQueue框架上传图像数据进行预览显示。

e) 下发拍照需求
当点击应用界面的拍照按钮的时候,会触发按钮的View.OnClickListener监听类中的onClick方法,在该方法中,初始化了一个用于拍照的CaptureRequest,并且通过调用CameraCaptureSession的capture方法下发拍照需求.

f) 返回拍照Metadata/图像数据
一旦拍照图像数据生成,便会通过回调接口CaptureCallback中的onCaptureComplete方法上传Meta Data以及通过BufferQueue框架上传图像数据到ImageReader中,触发其onImageAvailable方法,在该方法中通过ImageReader的acquireNextImage获取到拍照图像数据,并通过doByte2JpegFile将其转存外JPEG格式的图片保存在设备中。

Camera App作为整个框架体系的最上层,直接面向的主体是普通用户,其关键性不言而喻,一点点的卡顿或者停滞都会降低用户体验,所以为了保证整个框架的稳定性以及高效性,谷歌重新设计了Camera 接口Api v2,由于该接口将控制逻辑高度抽象成了一个控制视图,因此可以逐帧控制硬件参数,进而实现一系列强大的功能,比如可以直接在预览过程中,手动控制曝光、感光度、对焦以及白平衡的参数,动态地输出不同效果的图像,又比如可以利用该接口与HDR算法相配合,实现高动态成像效果。而其中,对于Api v2接口的实现是在Camera Framework中完成的,由上面分析可以看出,其内部并没有采用十分复杂的控制逻辑,整套代码流程清晰明朗,而这样的设计,进一步保证了该层的稳定性以及高效性,为整个相机框架体系的稳定奠定了坚实的基础。

谷歌提出Camera Api v2接口的同时,将其具体实现放入了Camera Framework中来完成,Framework内部负责解析来自App的请求,并且通过AIDL跨进程接口下发到Camera Service中进行处理,并且等待结果的回传。

二、Service:
Camera Service被设计成一个独立进程,作为一个服务端,处理来自Camera Framework 客户端的跨进程请求,并在内部进行一定的操作,随后作为客户端将请求再一次发送至作为服务端的Camera Provider,整个流程涉及到了两个跨进程操作,前者通过AIDL机制实现,后者通过HIDL机制实现,由于在于Camera Provider通信的过程中,Service是作为客户端存在的,所以此处我们重点关注AIDL以及Camera Service 主程序的实现。

Camera AIDL 接口:
在Android系统中,两个进程通常无法相互访问对方的内存,为了解决该问题,谷歌提出了Messager/广播以及后来的Binder,来解决这个问题,但是如果某个进程需要对另一个进程中进行多线程的并发访问,Messager和广播效果往往不是很好,所以Binder会作为主要实现方式,但是Binder的接口使用起来比较复杂,对开发者特别是初学者并不是很友好,所以为了降低跨进程开发门槛,谷歌开创性地提出了AIDL(自定义语言)机制,主动封装了Binder的实现细节,提供给开发者较为简单的使用接口,极大地提升了广大开发者的开发效率。

按照谷歌的针对AIDL机制的要求,需要服务端创建一系列*.aidl文件,并在其中定义需要提供给客户端的公共接口,并且予以实现,接下来我们来看下几个主要的aidl文件。

ICameraService.aidl定义了ICameraService 接口,实现主要通过CameraService类来实现,主要接口如下:
getNumberOfCameras: 获取系统中支持的Camera 个数
connectDevice():打开一个Camera 设备
addListener(): 添加针对Camera 设备以及闪光灯的监听对象

ICameraDeviceCallbacks.aidl文件中定义了ICameraDeviceCallbacks接口,其实现主要由Framework中的CameraDeviceCallbacks类进行实现,主要接口如下:
onResultReceived: 一旦Service收到结果数据,便会调用该接口发送至Framework
onCaptureStarted(): 一旦开始进行图像的采集,便调用该接口将部分信息以及时间戳上传至Framework
onDeviceError(): 一旦发生了错误,通过调用该接口通知Framework

ICameraDeviceUser.aidl定义了ICameraDeviceUser接口,由CameraDeviceClient最终实现,主要接口如下:

disconnect: 关闭Camera 设备
submitRequestList:发送request
beginConfigure: 开始配置Camera 设备,需要在所有关于数据流的操作之前
endConfigure: 结束关于Camera 设备的配置,该接口需要在所有Request下发之前被调用
createDefaultRequest: 创建一个具有默认配置的Request
ICameraServiceListener.aidl定义了ICameraServiceListener接口,由Framework中的CameraManagerGlobal类实现,主要接口如下:
onStatusChanged: 用于告知当前Camera 设备的状态的变更

Camera Service 主程序:
Camera Service 主程序,是随着系统启动而运行,主要目的是向外暴露AIDL接口给Framework进行调用,同时通过调用Camera Provider的HIDL接口,建立与Provider的通信,并且在内部维护从Framework以及Provider获取到的资源,并且按照一定的框架结构保持整个Service在稳定高效的状态下运行,所以接下来我们主要通过几个关键类、初始化过程以及处理来自App的请求三个部分来详细介绍下。

关键类解析
首先我们来看下几个关键类,Camera Service中主要包含了以下几个类,用于提供AIDL接口,并负责内部一系列逻辑的控制,并且通过HIDL接口保持与Provider的通信。
首先我们看下CameraService的这个类,它主要实现了AIDL中ICameraService 接口,并且暴露给Camera Framework进行调用,这个类在初始化的时候会去实例化一个CameraProviderManager对象,而在实例化的过程中,该对象会去获取系统中所有的Camera Provider,并且在其内部实例化了对应Provider个数的ProviderInfo对象,并且随着每一个ProviderInfo的实例化,将一个Camera Provider作为参数存入ProviderInfo中,并且最终将所有的ProviderInfo存入一个vec容器中进行统一管理,就这样,CameraProviderManager便达到了管理所有的Camera Provider的目的。

而对于单个ProviderInfo而言,内部会维护一个Camera Provider代理,而在系统运行初期,ProviderInfo会去向Camera Provider获取当前这设备所支持的Camera 设备,拿到Camera Provider中的ICameraDevice代理,并且依次存入提前实例化好的DeviceInfo3对象中,最后会将所有的DeviceInfo3存入一个内部容器,进行统一管理,而DeviceInfo3维护着Camera Provider中的ICameraDevice代理,保持了对Camera Provider的控制。

另外,Camera Service 中还包含了CameraDeviceClient类,该类在打开设备的时候被实例化,一次打开设备的操作对应一个该类对象,它实现了ICameraDeviceUser接口,以AIDL方式暴露接口给Camera Framework进行调用,于此同时,该类在打开设备的过程中,获取了来自Camera Framework对于ICameraDeviceCallback接口的实现代理,通过该代理可以将结果上传至Camera Framework中,其中还包含了一个Camera3Device以及FrameProcessorBase,Camera3Device主要实现了对Camera Provider 的ICameraDeviceCallbacks回调接口的实现,通过该接口接收来自Provider的结果上传,进而传给CameraDeviceClient以及FrameProcessBase,其中,Camera3Device会将事件通过notify方法给到CameraDeviceClient,而meta data以及image data 会给到FrameProcessBase,进而给到CameraDeviceClient,所以FrameProcessBase主要用于metadata以及image data的中转处理。而Camera3Device中RequestThread主要用于处理Request的接收与下发工作。

对于Camera Service而言,主要包括了两个阶段,一个是系统刚启动的时候,会通过运行其主程序将其Camera Service 服务运行起来,等待Camera Framework的下发图像需求,另一个阶段就是当用户打开相机应用的时候,会去获取相机设备,进而开始图像采集过程,接下来我们就主要以这两个阶段分别来详细介绍下内部运行逻辑。
2. 启动初始化
当系统启动的时候,会首先运行Camera Service的主程序,将整个进程运行起来,这里我们首先来看下Camera Service 是怎样运行起来的。

处理App请求
一旦用户打开了相机应用,便会去调用CameraManager的openCamera方法进而走到Framework层处理,Framework通过内部处理,最终将请求下发到Camera Service中,而在Camera Service主要做了获取相机设备属性、打开相机设备,然后App通过返回的相机设备,再次下发创建Session以及下发Request的操作,接下来我们来简单梳理下这一系列请求在Camera Service中是怎么进行处理的。
a) 获取属性
b) 打开相机设备
c) 配置数据流
d) 处理图像需求
e) 接收图像结果
三、Camera provider(硬件抽象层):
始于谷歌的Treble开源项目,基于接口与实现的分离的设计原则,谷歌加入了Camera Provider这一抽象层,该层作为一个独立进程存在于整个系统中,并且通过HIDL这一自定义语言成功地将Camera Hal Module从Camera Service中解耦出来,承担起了对Camera HAL的封装工作,纵观整个Android系统,对于Camera Provider而言,对上是通过HIDL接口负责与Camera Service的跨进程通信,对下通过标准的HAL3接口下发针对Camera的实际操作,这俨然是一个中央枢纽般的调配中心的角色,而事实上正是如此,由此看来,对Camera Provider的梳理变得尤为重要,接下来就简单介绍下Camera Provider。

Camera Provider通过提供标准的HIDL接口给Camera Service进行调用,保持与Service的正常通信,其中谷歌将HIDL接口的定义直接暴露给平台厂商进行自定义实现,其中为了极大地减轻并降低开发者的工作量和开发难度,谷歌很好地封装了其跨进程实现细节,同样地,Camera Provider通过标准的HAL3接口,向下控制着具体的Camera HAL Module,而这个接口依然交由平台厂商负责去实现,而进程内部则通过简单的函数调用,将HIDL接口与HAL3接口完美的衔接起来,由此构成了Provider整体架构。

由图中可以看出Camera Provider进程由两部分组成,一是运行在系统中的主程序通过提供了标准的HIDL接口保持了与Camera Service的跨进程通讯,二是为了进一步扩展其功能,通过dlopen方式加载了一系列So库,而其中就包括了实现了Camera HAL3接口的So库,而HAL3接口主要定义了主要用于实现图像控制的功能,其实现主要交由平台厂商或者开发者来完成,所以Camera HAL3 So库的实现各式各样,在高通平台上,这里的实现就是我们本文重点需要分析的CamX-CHI框架。

Camera provider 主程序流程:

Provider几个重要流程:

Camera HAL3 接口:
HAL硬件抽象层(Hardware Abstraction Layer),是谷歌开发的用于屏蔽底层硬件抽象出来的一个软件层, 每一个平台厂商可以将不开源的代码封装在这一层,仅仅提供二进制文件。
该层定义了自己的一套通用标准接口,平台厂商务必按照以下规则定义自己的Module:
每一个硬件模块都通过hw_module_t来描述,具有固定的名字HMI
每一个硬件模块都必须实现hw_module_t里面的open方法,用于打开硬件设备,并返回对应的操作接口集合
硬件的操作接口集合使用hw_device_t 来描述,并可以通过自定义一个更大的包含hw_device_t的结构体来拓展硬件操作集合

其中代表硬件模块的是hw_module_t,对应的设备是通过hw_device_t来描述,这两者的定义如下:
hw_module_t/hw_device_t源码如下:
typedef struct hw_module_t {
uint32_t tag; //HMI
struct hw_module_methods_t* methods;
} hw_module_t;

typedef struct hw_module_methods_t {
int (open)(const struct hw_module_t module, const char* id, struct hw_device_t** device);
} hw_module_methods_t;

typedef struct hw_device_t {
struct hw_module_t* module;
int (close)(struct hw_device_t device);
} hw_device_t;

从上面的定义可以看出,主要是通过hw_module_t 代表了模块,通过其open方法用来打开一个设备,而该设备是用hw_device_t来表示,其中除了用来关闭设备的close方法外,并无其它方法,由此可见谷歌定义的HAL接口,并不能满足绝大部分HAL模块的需要,所以谷歌想出了一个比较好的解决方式,那便是将这两个基本结构嵌入到更大的结构体内部,同时在更大的结构内部定义了各自模块特有的方法,用于实现模块的功能,这样,一来对上保持了HAL的统一规范,二来也扩展了模块的功能。

基于上面的方式,谷歌便针对Camera 提出了HAL3接口,其中主要包括了用于代表一系列操作主体的结构体以及具体操作函数,接下来我们分别进行详细介绍:

核心结构体解析
HAL3中主要定义了camera_module_t/camera3_device_t/camera3_stream_configuration/camera3_stream以及camera3_stream_buffer几个主要结构体。
其中camera_module_t以及camera3_device_t代码定义如下:
typedef struct camera_module {
hw_module_t common;
int (*get_number_of_cameras)(void);
int (*get_camera_info)(int camera_id, struct camera_info info);
nt (set_callbacks)(const camera_module_callbacks_t callbacks);
void (get_vendor_tag_ops)(vendor_tag_ops_t ops);
int (open_legacy)(const struct hw_module_t module, const char id, uint32_t halVersion, struct hw_device_t device);
int (set_torch_mode)(const char camera_id, bool enabled);
int (*init)();
int (*get_physical_camera_info)(int physical_camera_id, int (*is_stream_combination_supported)(int camera_id,const camera_stream_combination_t *streams);
int (*is_stream_combination_supported)(int camera_id, const camera_stream_combination_t *streams);
void (*notify_device_state_change)(uint64_t deviceState);
} camera_module_t;

typedef struct camera3_device {
hw_device_t common;
camera3_device_ops_t *ops; //拓展接口,Camera HAL3定义的标准接口
void *priv;
} camera3_device_t;
由定义不难发现,camera_module_t包含了hw_module_t,主要用于表示Camera模块,其中定义了诸如get_number_of_cameras以及set_callbacks等扩展方法,而camera3_device_t包含了hw_device_t,主要用来表示Camera设备,其中定义了camera3_device_ops操作方法集合,用来实现正常获取图像数据以及控制Camera的功能。

结构体camera3_stream_configuration代码定义如下:
typedef struct camera3_stream_configuration {
uint32_t num_streams;
camera3_stream_t **streams;
uint32_t operation_mode;
const camera_metadata_t *session_parameters;
} camera3_stream_configuration_t;

该结构体主要用来代表配置的数据流列表,内部装有上层需要进行配置的数据流的指针,内部的定义简单介绍下:
num_streams: 代表了来自上层的数据流的数量,其中包括了output以及input stream。
streams: 是streams的指针数组,包括了至少一条output stream以及至多一条input stream。
operation_mode: 当前数据流的操作模式,该模式在camera3_stream_configuration_mode_t中被定义,HAL通过这个参数可以针对streams做不同的设置。
session_parameters: 该参数可以作为缺省参数,直接设置为NULL即可,CAMERA_DEVICE_API_VERSION_3_5以上的版本才支持。

结构体camera3_stream_t的代码定义如下:
typedef struct camera3_stream {
int stream_type;
uint32_t width;
uint32_t height;
int format;
uint32_t usage;
uint32_t max_buffers;
void priv;
android_dataspace_t data_space;
int rotation;
const char physical_camera_id;
}camera3_stream_t;

该结构体主要用来代表具体的buffer对象,其中重要元素如下:
stream: 代表了从属的数据流
buffer:buffer句柄
2. 核心接口函数解析
HAL3的核心接口都是在camera3_device_ops中被定义,代码定义如下:
typedef struct camera3_device_ops {
int (*initialize)(const struct camera3_device *, const camera3_callback_ops_t *callback_ops);
int (*configure_streams)(const struct camera3_device *, camera3_stream_configuration_t *stream_list);
int (*register_stream_buffers)(const struct camera3_device *,const camera3_stream_buffer_set_t buffer_set);
const camera_metadata_t (*construct_default_request_settings)(const struct camera3_device *, int type);
int (*process_capture_request)(const struct camera3_device *, camera3_capture_request_t request);
void (get_metadata_vendor_tag_ops)(const struct camera3_device, vendor_tag_query_ops_t ops);
void (dump)(const struct camera3_device , int fd);
int (flush)(const struct camera3_device );
void (signal_stream_flush)(const struct camera3_device, uint32_t num_streams, const camera3_stream_t const streams);
int (is_reconfiguration_required)(const struct camera3_device, const camera_metadata_t old_session_params, const camera_metadata_t new_session_params);
} camera3_device_ops_t;

从代码中可以看见,该结构体定义了一系列的函数指针,用来指向平台厂商实际的实现方法,接下来就其中几个方法简单介绍下:

a) initialize
该方法必须在camera_module_t中的open方法之后,其它camera3_device_ops中方法之前被调用,主要用来将上层实现的回调方法注册到HAL中,并且根据需要在该方法中加入自定义的一些初始化操作,另外,谷歌针对该方法在性能方面也有严格的限制,该方法需要在5ms内返回,最长不能超过10ms。

b) configure_streams
该方法在完成initialize方法之后,在调用process_capture_request方法之前被调用,主要用于重设当前正在运行的Pipeline以及设置新的输入输出流,其中它会将stream_list中的新的数据流替换之前配置的数据流。在调用该方法之前必须确保没有新的request下发并且当前request的动作已经完成,否则会引起无法预测的错误。一旦HAL调用了该方法,则必须在内部配置好满足当前数据流配置的帧率,确保这个流程的运行的顺畅性。
其中包含了两个参数,分别是camera3_device以及stream_list(camera3_stream_configuration_t ),其中第二个参数是上层传入的数据流配置列表,该列表中必须包含至少一个output stream,同时至多包含一个input stream。
另外,谷歌针对该方法有着严格的性能要求,平台厂商在实现该方法的时候,需要在500ms内返回,最长不能超过1000ms。

c) construct_default_request_settings
该方法主要用于构建一系列默认的Camera Usecase的capture 设置项,通过camera_metadata_t来进行描述,其中返回值是一个camera_metadata_t指针,其指向的内存地址是由HAL来进行维护的,同样地,该方法需要在1ms内返回,最长不能超过5ms。

d) process_capture_request
该方法用于下发单次新的capture request到HAL中, 上层必须保证该方法的调用都是在一个线程中完成,而且该方法是异步的,同时其结果并不是通过返回值给到上层,而是通过HAL调用另一个接口process_capture_result()来将结果返回给上层的,在使用的过程中,通过in-flight机制,保证短时间内下发足够多的request,从而满足帧率要求。

该方法的性能依然受到谷歌的严格要求,规定其需要在一帧图像处理完的时长内返回,最长不超过4帧图像处理完成的时长,比如当前预览帧率是30帧,则该方法的操作耗时最长不能超过120ms,否则便会引起明显的帧抖动,从而影响用户体验。

e) dump
该方法用于打印当前Camera设备的状态,一般是由上层通过dumpsys工具输出debug dump信息或者主动抓取bugreport的时候被调用,该方法必须是非阻塞实现,同时需要保证在1ms内返回,最长不能超过10ms。

f) flush
当上层需要执行新的configure_streams的时候,需要调用该方法去尽可能快地清除掉当前已经在处理中的或者即将处理的任务,为配置数据流提供一个相对稳定的环境,其具体工作如下:
所有的还在流转的request会尽可能快的返回
并未开始进行流转的request会直接返回,并携带错误信息
任何可以打断的硬件操作会立即被停止
任何无法进行打断的硬件操作会在当前状态下进行休眠
flush会在所有的buffer都得以释放,所有request都成功返回后才真正返回,该方法需要在100ms内返回,最长不能超过1000ms。

上面的一系列方法是上层直接对下控制Camera Hal,而一旦Camera Hal产生了数据或者事件的时候,可以通过camera3_callback_ops中定义的回调方法将数据或者事件返回至上层,该结构体定义如下:
typedef struct camera3_callback_ops {
void (*process_capture_result)(const struct camera3_callback_ops *, const camera3_capture_result_t *result);
void (*notify)(const struct camera3_callback_ops *, const camera3_notify_msg_t *msg);
camera3_buffer_request_status_t (*request_stream_buffers)(
const struct camera3_callback_ops *,
uint32_t num_buffer_reqs,
const camera3_buffer_request_t *buffer_reqs,
/out/uint32_t *num_returned_buf_reqs,
/out/camera3_stream_buffer_ret_t *returned_buf_reqs);
void (return_stream_buffers)( const struct camera3_callback_ops , uint32_t num_buffers, const camera3_stream_buffer_t const buffers);
} camera3_callback_ops_t;
其中常用的回调方法主要有两个:用于返回数据的process_capture_result以及用于返回事件的notify,接下来分别介绍下:

a) process_capture_result
该方法用于返回HAL部分产生的metadata和image buffers,它与request是多对一的关系,同一个request,可能会对应到多个result,比如可以通过调用一次该方法用于返回metadata以及低分辨率的图像数据,再调用一次该方法用于返回jpeg格式的拍照数据,而这两次调用时对应于同一个process_capture_request动作。

同一个Request的Metadata以及Image Buffers的先后顺序无关紧要,但是同一个数据流的不同Request之间的Result必须严格按照Request的下发先后顺序进行依次返回的,如若不然,会导致图像数据显示出现顺序错乱的情况。

该方法是非阻塞的,而且并且必须要在5ms内返回。

b) notify
该方法用于异步返回HAL事件到上层,必须非阻塞实现,而且要在5ms内返回。

谷歌为了将系统框架和平台厂商的自定义部分相分离,在Android上推出了Treble项目,该项目直接将平台厂商的实现部分放入vendor分区中进行管理,进而与system分区保持隔离,这样便可以在相互独立的空间中进行各自的迭代升级,而互不干扰,而在相机框架体系中,便将Camera HAL Module从Camera Service中解耦出来,放入独立进程Camera Provider中进行管理,而为了更好的进行跨进程访问,谷歌针对Provider提出了HIDL机制用于Camera Servic对于Camera Provier的访问,而HIDL接口的实现是在Camera Provider中实现,针对Camera HAL Module的控制又是通过谷歌制定的Camera HAL3接口来完成,所以由此看来,Provider的职责也比较简单,通过HIDL机制保持与Camera Service的通信,通过HAL3接口控制着Camera HAL Module。

四、Camera provider(硬件抽象层实现):
一、概览
回顾高通平台Camera HAL历史,之前高通采用的是QCamera & MM-Camera架构,但是为了更精细化控制底层硬件(Sensor/ISP等关键硬件),同时方便手机厂商自定义一些功能,现在提出了CamX-CHI架构,由于在CamX-CHI中完全看不到之前老架构的影子,所以它完全是一个全新的架构,它将一些高度统一的功能性接口抽离出来放到CamX中,将可定制化的部分放在CHI中供不同厂商进行修改,实现各自独有的特色功能,这样设计的好处显而易见,那便是即便开发者对于CamX并不是很了解,但是依然可以很方便的加入自定义的功能,从而降低了开发者在高通平台的开发门槛。
接下来我们以最直观的目录结构入手对该架构做一个简单的认识,以下便是CamX-CHI基本目录结构:
该部分代码主要位于 vendor/qcom/proprietary/ 目录下:
其中 camx 代表了通用功能性接口的代码实现集合(CamX),chi-cdk代表了可定制化需求的代码实现集合(CHI),从图中可以看出Camx部分对上作为HAL3接口的实现,对下通过v4l2框架与Kernel保持通讯,中间通过互相dlopen so库并获取对方操作接口的方式保持着与CHI的交互。
camx/中有如下几个主要目录:
core/ : 用于存放camx的核心实现模块,其中还包含了主要用于实现hal3接口的hal/目录,以及负责与CHI进行交互的chi/目录
csl/: 用于存放主要负责camx与camera driver的通讯模块,为camx提供了统一的Camera driver控制接口
hwl/: 用于存放自身具有独立运算能力的硬件node,该部分node受csl管理
swl/: 用于存放自身并不具有独立运算能力,必须依靠CPU才能实现的node

chi-cdk/中有如下几个主要目录:(在geely项目源码中该目录下部分目录不存在)
chioverride/: 用于存放CHI实现的核心模块,负责与camx进行交互并且实现了CHI的总体框架以及具体的业务处理。
bin/: 用于存放平台相关的配置项
topology/: 用于存放用户自定的Usecase xml配置文件
node/: 用于存放用户自定义功能的node
module/: 用于存放不同sensor的配置文件,该部分在初始化sensor的时候需要用到
tuning/: 用于存放不同场景下的效果参数的配置文件
sensor/: 用于存放不同sensor的私有信息以及寄存器配置参数
actuator/: 用于存放不同对焦模块的配置信息
ois/: 用于存放防抖模块的配置信息
flash/: 存放着闪光灯模块的配置信息
eeprom/: 存放着eeprom外部存储模块的配置信息
fd/: 存放了人脸识别模块的配置信息

二、组件基本概念:
1. Usecase
作为CamX-CHI中最大的抽象概念,其中包含了多条实现特定功能的Pipeline,具体实现是在CHI中通过Usecase类完成的,该类主要负责了其中的业务处理以及资源的管理。

Usecase类,提供了一系列通用接口,作为现有的所有Usecase的基类,其中,AdvancedCameraUsecase又继承于CameraUsecaseBase,相机中绝大部分场景会通过实例化AdvancedCameraUsecase来完成,它包括了几个主要接口:
Create(): 该方法是静态方法,用于创建一个AdvancedCameraUsecase实例,在其构造方法中会去获取XML中的相应的Usecase配置信息。
ExecuteCaptureRequest(): 该方法用于下发一次Request请求。
ProcessResultCb(): 该方法会在创建Session的过程中,作为回调方法注册到其中,一旦Session数据处理完成的时候便会调用该方法将结果发送到AdvancedCameraUsecase中。
ProcessDriverPartialCaptureResult(): 该方法会在创建Session的过程中,作为回调方法注册到其中,一旦Session中产生了partial meta data的时候,便会调用该方法将其发送至AdvancedCameraUsecase中。
ProcessMessageCb(): 该方法会在创建Session的过程中,作为回调方法注册到其中,一旦Session产生任何事件,便会调用该方法通知到AdvancedCameraUsecase中。
ExecuteFlush(): 该方法用于刷新AdvancedCameraUsecase。
Destroy(): 该方法用于安全销毁AdvancedCameraUsecase。

Usecase的可定制化部分被抽象出来放在了common_usecase.xml文件中,这里简单介绍其中的几个主要的标签含义:
Usecase
UsecaseName: 代表了该Usecase的名字,后期根据这个名字找到这个Usecase的定义。
Targets: 用于表示用于输出的数据流的集合,其中包括了数据流的格式,输出Size的范围等。
Pipeline: 用于定义该Usecase可以是使用的所有Pipeline,这里必须至少定义一条Pipeline。

2. Feature
代表了一个特定的功能,该功能需要多条Pipeline组合起来实现,受Usecase统一管理,在CHI中通过Feature类进行实现,在XML中没有对应的定义,具体的Feature选取工作是在Usecase中完成的,通过在创建Feature的时候,传入Usecase的实例的方式,来和Usecase进行相互访问各自的资源。

以下是现有的Feature,其中Feature作为基类存在,定义了一系列通用方法。

几个常用的Feature:
FeatureHDR: 用于实现HDR功能,它负责管理内部的一条或者几条pipeline的资源以及它们的流转,最终输出具有HDR效果的图像。
FeatureMFNR: 用于实现MFNR功能,内部分为几个大的流程,分别包括Prefiltering、Blending、Postfilter以及最终的OfflineNoiseReproces(这一个是可选择使能的),每一个小功能中包含了各自的pipeline。
FeatureASD: 用于AI功能的实现,在预览的时候,接收每一帧数据,并且进行分析当前场景的AI识别输出结果,并其通过诸如到metadata方式给到上层,进行后续的处理。

3. Session
用于管理pipeline的抽象控制单元,一个Session中至少拥有一个pipeine,并且控制着所有的硬件资源,管控着每一个内部pipeline的request的流转以及数据的输入输出,它没有可定制化的部分,所以在CHI中的XML文件中并没有将Session作为一个独立的单元进行定义。

Session的实现主要通过CamX中的Session类,其主要接口如下:
Initialize(): 根据传入的参数SessionCreateData进行Session的初始化工作。
NotifyResult(): 内部的Pipeline通过该接口将结果发送到Session中。
ProcessCaptureRequest(): 该方法用于用户决定发送一个Request到Session中的时候调用。
StreamOn(): 通过传入的Pipeline句柄,开始硬件的数据传输。
StreamOff(): 通过传入的Pipeline句柄,停止硬件的数据传输。

4. Pipeline
作为提供单一特定功能的所有资源的集合,维护着所有硬件资源以及数据的流转,每一个Pipeline包括了其中的Node/Link,在CamX中通过Pipeline类进行实现,负责整条Pipeline的软硬件资源的维护以及业务逻辑的处理,接下来我们简单看下该类的几个主要接口:
Create(): 该方法是一个静态方法,根据传入的PipelineCreateInputData信息来实例化一个Pipeline对象。
StreamOn(): 通知Pipeline开始硬件的数据传输
StreamOff(): 通知Pipeline停止硬件的数据传输
FinalizePipeline(): 用于完成Pipeline的设置工作
OpenRequest(): open一个CSL用于流转的Request
ProcessRequest(): 开始下发Request
NotifyNodeMetadataDone(): 该方法是Pipeline提供给Node,当Node内部生成了metadata,便会调用该方法来通知metadata已经完成,最后当所有Node都通知Pipeline metadata已经完成,Pipeline 便会调用ProcessMetadataRequestIdDone通知Session。
NotifyNodePartialMetadataDone(): 该方法是Pipeline提供给Node,当Node内部生成了partial metadata,便会调用该方法来通知metadata已经完成,最后当所有Node都通知Pipeline metadata已经完成,Pipeline 便会调用ProcessPartialMetadataRequestIdDone通知Session。
SinkPortFenceSignaled(): 用来通知Session 某个sink port的fence处于被触发的状态。
NonSinkPortFenceSignaled(): 用来通知Session 某个non sink port的fence处于被触发的状态。

Pipeline中的Node以及连接方式都在XML中被定义,其主要包含了以下几个标签定义:
PipelineName: 用来定义该条Pipeline的名称
NodeList: 该标签中定义了该条Pipeline的所有的Node
PortLinkages: 该标签定义了Node上不同端口之间的连接关系

5. Node
作为单个具有独立处理功能的抽象模块,可以是硬件单元也可以是软件单元,关于Node的具体实现是CamX中的Node类来完成的,其中CamX-CHI中主要分为两个大类,一个是高通自己实现的Node包括硬件Node,一个是CHI中提供给用户进行实现的Node,其主要方法如下:
Create(): 该方法是静态方法,用于实例化一个Node对象。
ExecuteProcessRequest(): 该方法用于针对hwl node下发request的操作。
ProcessRequestIdDone(): 一旦该Node当前request已经处理完成,便会通过调用该方法通知Pipeline。
ProcessMetadataDone(): 一旦该Node的当前request的metadata已经生成,便会通过调用该方法通知到Pipeline。
ProcessPartialMetadataDone(): 一旦该Node的当前request的partial metadata已经生成,便会通过调用该方法通知到Pipeline。
CreateImageBufferManager(): 创建ImageBufferManager
其可定制化的部分作为标签在XML中进行定义:
NodeName: 用来定义该Node的名称
NodeId: 用来指定该Node的ID,其中IPE NodeId为65538,IFE NodeId为65536,用户自定义的NodeId为255。
NodeInstance: 用于定义该Node的当前实例的名称。
NodeInstanceId: 用于指定该Node实例的Id。

6. Link
用于定义不同Port的连接,一个Port可以根据需要建立多条与其它从属于不同Node的Port的连接,它通过标签来进行定义,其中包括了作为输入端口,作为输出端口。
一个Link中包含了一个SrcPort和一个DstPort,分别代表了输入端口和输出端口,然后BufferProperties用于表示两个端口之间的buffer配置。

7. Port
作为Node的输入输出的端口,在XML文件中,标签用来定义一个输入端口,标签用来定义输出端口,每一个Node都可以根据需要使用一个或者多个输入输出端口,使用OutputPort以及InputPort结构体来进行在代码中定义。
Port
PortId: 该端口的Id: 该端口的名称
NodeName: 该端口从属的Node名称
NodeId: 该端口从属的Node的Id
NodeInstance: 该端口从属的Node的实例名称
NodeInstanceId: 该端口从属的Node的实例的Id

三、组件结构关系
四、关键流程详解
1. Camera Provider 启动初始化
2. 打开相机设备/初始化相机设备
a) open
b) initialize
3. 配置相机设备数据流
① 选择UsecaseId
② 创建Usecase

获取XML文件中Usecase配置信息
创建Feature
保存数据流,重建Usecase的配置信息
调用父类CameraUsecaseBase的initialize方法,进行一些常规初始化工作
该方法中的操作主要有以下三个:
设置Session回调
创建Pipeline
创建Session
4. 处理拍照请求
5. 上传拍照结果
在用户开启了相机应用,相机框架收到某次Request请求之后会开始对其进行处理,一旦有图像数据产生便会通过层层回调最终返回到应用层进行显示,这里我们针对CamX-CHI部分对于拍照结果的上传流程进行一个简单的梳理:

每一个Request对应了三个Result,分别是partial metadata、metadata以及image data,对于每一个Result,上传过程可以大致分为以下两个阶段:

Session内部完成图像数据的处理,将结果发送至Usecase中
Usecase接收到来自Session的数据,并将其上传至Provider
首先来看下Session内部完成图像数据的处理后是如何将结果发送至Usecase的:

接下来我们来看下一旦Usecase接收到Session的数据,是如何发送至Provider的:
我们以常用的AdvancedCameraUsecase为例进行代码的梳理:

五、Camera driver----V4L2框架
六、Camera hardware
一、简介
相机的硬件层,作为整个框架的最底层,通过硬件模块接收来自客观世界的真实光影效果,将其转换为计算机所熟知的数字信号,并按照一定的数据格式向上源源不断提供成稳定并成像效果优秀的图像数据,整个部分复杂且高效,可以说是,一个优秀的硬件基础,就好比为整个相机框架的地基,拥有一个好的地基,便使得建造一座摩天大厦成为可能,接下来我们来详细介绍下,这部分各个组件的基本情况。

二、基本硬件结构
而今的相机硬件系统纷繁复杂,但是如果仔细深入研究的话,你会发现,其实核心组件无外乎镜头、感光器、图像处理器三大件,其中镜头用来聚光,感光器件用于光电转换,而图像处理器用来加工处理图像数据,接下来我们就以这三个组件开始展开对于相机系统的世界的探索之旅。

镜头(Lens)
将时间的转盘向前波动一下,让我们回到各自的小学时代,那时候老师给我们都布置了一个家庭作业,任务是制作一个小孔成像的简单模型,这个简单模型便是我接触的最原始最简单的成像系统,但是那是我一直有一个疑问,成像为什么那么模糊,这个疑问在我接触到真正的相机之后才得以解开,原来一切都是光线惹的祸。
根据小孔成像原理,小孔的一端是光源,另一端是成像平面,光经过小孔,入射到平面上,无数个光线都入射到这个平面上,便形成了光源的像,但是有一个问题,就是光线是按照发散路径向四周蔓延开来,光源某点所发出的某一束光线通过小孔后会到达成像平面的某一点上,但是很显然,该点也会接收来自另一个光源上的点所发出的另一束光线,这样就形成的光的干扰,进而影响了最终的成像效果。所以为了改善这个问题,镜头便被发明出来,而镜头其实我们日常生活中接触的凸透镜,其根本目的就是为了解决光线互相干扰的问题,其原理就是通过凸透镜的折射原理,将来自同一点的光线,重新汇聚至一点,从而大幅度提升了成像效果。而这里的重新汇聚的一点便是光源那点在透镜后的像点,而由于随着光源点的不断变换,其像点会相应的变化,所以我们常常将来自无限远处的光线,通过透镜之后汇聚而成的那个点称为该镜头的焦点,而焦点到透镜中心的距离,便称为焦距,一旦透镜制作完成,焦距便被确定下来。

光圈快门
对于一个制作完成的镜头,无法随意调整镜头的直径,所以便在其中加入了一个叫做光圈的部件,该部件一般采用正多边形或者圆形的孔状光栅,通过调整光栅开合大小进而控制这个镜头的瞬时进光量,然而针对总的进光亮的控制仅仅依靠光圈也是不够的,需要再用到另一个叫做快门的部件,它主要决定着曝光的时长,最初的快门是通过调整镜头前的盖子的开关来进行实现,随着时代的进步,现在快门衍生出了多个实现方式,其中包括机械快门,它是作为一种只使用弹簧或者其他机械结构,不靠电力来驱动与控制速度的快门结构,电子快门,该快门结构通过马达和磁铁在电力驱动的作用下进行控制。电子断流快门,一种完全没有机械结构的快门结构,具有高快门速率和很快的影响捕捉频率,但是缺点是容易产生高光溢出现象。
光圈控制着瞬时进光量,快门控制着曝光时间,通过两者的共同合作,完成了控制光线进入量的目的,进而进一步真实再现了场景的光影效果,避免了过度曝光的情况发生,极大的提升了整个提成像质量。

对焦马达
正如之前所说,入射光线会在通过透镜之后以锥形路径汇聚到一点,该点叫做像点,之后再以锥形发散开去,而所有的相同距离发射的光线,都会汇聚到各自的像点上时,便形成了一个都是像点组成的一个平面,而这个平面一般叫做像平面,又由于这个平面是所有像点所汇聚而成的,所以该平面是成像清晰的,而现如今的对焦的本质便是通过移动透镜,使像平面与感光器件平面重合,从而在感光器件上形成清晰的像。一般来讲,对焦可以通过手动移动透镜完成,但是更一般地,是通过一个叫做对焦马达的器件来完成。除了手动调整镜头进而完成对焦操作外,现在比较主流的方式是通过自动移动透镜进而完成对焦动作,随着技术的不断发展,而今的对焦又发展出了自动对焦策略,其中包括了相位对焦和对比度对焦。其基本原理是前后调整镜头使像平面与感光器感光平面重合,从而形成清晰的成像效果。另外,针对更为复杂的相机系统,为了获得更加优秀的成像质量,一般都会采用多个透镜组合来实现,一来可以消除色差,二来可以通过马达调整透镜间的距离,来动态的修改整个透镜组的焦距,从而满足更加复杂场景下的成像需求。
d) 感光器(Sensor)
正如之前所讲,透镜的作用是为了汇聚光线,从而形成像平面,但是如何将这个所谓的像平面转换成计算机所熟知的图像信息呢?这就需要用到这里的感光器了,感光器并不是现代社会的专有发明,其实早在19世界初期的欧洲便有了这个概念,一位名叫尼埃普斯的法国人通过使用沥青加上薰衣草油,再以铅锡合金板作为片基,拍摄了从他家楼上看到的窗户外的场景,名叫《鸽子窝》的照片,而这里的沥青混以薰衣草油便是一种简单的感光物质,从这开始感光技术开始进入快速发展期,在1888年,美国柯达公司生产出了一种新型感光材料,柔软且可卷绕的胶卷,这是感光材料的一个质的飞跃,之后1969年在贝尔实验室,CCD数字感光器件被发明出来,将整个感光技术推入了数字时代,随后技术的不断革新,便于大规模批量生产的CMOS应运而生,将成像系统往更小更好的方向推进了一大步。随着CMOS的技术不断发展,优势明显的它渐渐取代了CCD,成为相机系统的主流感光器件。

滤光片(IR Filter)
由于感光材料的特性所致,它会感受除了可见光波长范围内的光线,比如部分红外光,由于这部分红外光是不可见的,所以对于我们而言没有实际的用处(当然,这也不绝对,有的情况就是需要采集红外光的信息,比如夜视照相机),并且可能会干扰之后的ISP的处理,所以往往需要使用一个用于过滤红外光,避免红外光线干扰,修正摄入的光线的滤片,一般分为干涉式的IR/AR-CUT(在低通滤波晶片上镀膜,利用干涉相消的原理)和吸收式的玻璃(利用光谱吸收的原理)。

闪光灯(Flash)
针对某些特殊场景,比如暗光环境下拍摄需求,此时由于光线本身较少,无法完成充分的感光操作,但是为了获取正常的拍摄需求,往往需要通过外部补光来作为额外的光照补偿,基于此,闪光灯便应运而生,对于手机而言,其主要分为氙气灯与LED灯两种,由于LED闪光灯具有功耗较低、体积较小的优势,作为手机闪光灯的主流选择。另外,现在很多手机采用了双色闪光灯的策略,双色闪光灯可以根据环境的需要调节两灯发光的强度,可以更为逼近自然光的效果,相比单闪光灯强度有所提升,另外色温也较普通双闪光灯要更为准确,总体来讲效果较好。

图像处理器(ISP)
一旦当感光器件完成光电转换之后,便会将数据给到图像处理器,而ISP第一步需要做的便是去掉暗电流噪声,何为暗电流噪声呢?这要从感光器件说起,针对CCD/CMOS而言,通常并不是全部都用于感光,有一部分是被专门遮挡住,用于采集在并未感光的情况的暗电流情况,通过这种方式消除掉暗电流带来的噪声。

对于镜头的各处的折射率不同的属性,会随着视场角的慢慢增大,能够通过镜头的斜光束慢慢减少,从而产生了图像中心亮度较边缘部分要高,这个现象在光学系统中叫做渐晕,很显然这种差异性会带成像的不自然,所以ISP接下来需要对于这种偏差进行修正,而修正的算法便是镜头阴影矫正,具体原理便是以图像中间亮度均匀的区域为中心,计算出个点由于衰减带来的图像变暗速度,从而计算出RGB三通道的补偿因子,根据这些补偿因子来对图像进行修正。

随后,由于感光器件针对光线都是采用红、绿、蓝三基色进行分别采集而成的,所以数据一般会呈现出类似马赛克的排布效果,此时便需要完成去马赛克处理,基本原理便是通过一定的插值算法,通过附近的颜色分量猜测该像素所缺失的颜色分量,力争还原每一个像素的真实颜色效果,从而形成一个颜色真实的图像数据,而此时的数据格式便是RAW数据格式,即最原始的图像数据。

当感光器进行光电转换的过程中,每一个环节都会产生一定的偏差,而这个偏差到最后便会以噪声的方式表现出来,所以接下来需要对于这个无关信息–噪声进行一定的降噪处理,当前主要采用了非线性去噪算法,比如双边滤波器,在采样时不仅考虑了像素在空间距离上的关系,同时还加入了像素间的相似程度考虑,从而保持了原始图像的大体分块,对于边缘信息保持良好。

进一步降低了噪声之后,ISP需要对于图像白平衡进行处理,由于不同场景下的外界色温的不同,需要按照一定的比例调整RGB分量的值,从而使得在感光器中,白色依然是呈现白色的效果。白平衡可以采用手动白平衡,通过手动调整三个颜色分量的比例关系,达到白平衡的目的,而更一般地采用了自动白平衡的处理,这里ISP就承担着自动白平衡的使命,通过对当前图像进行分析,得到各颜色分量的比例关系,进而调整其成像效果。

调整好图像白平衡后,需要进一步地调整颜色误差,这里的误差主要由于滤光片各颜色块之间存在颜色渗透所导致,一般在Tunning过程中会利用相机模组拍摄的图像与标准图像相比较得到的一个矫正矩阵,ISP利用这个矩阵来对拍摄的图像进行图像颜色矫正,从而达到还原拍摄场景中真实颜色的目的。

以上简单罗列了下,图像处理器的几个基本功能,虽然每个厂商所生产的ISP都不尽相同,但是基本都包括了以上几个步骤,由此可见,图像处理器是用来提升整个相机系统的成像效果的。

三、手机相机简介
对于手机上的相机系统,受到尺寸以及功耗的限制,无法像专业相机那样,为了保证成像效果,可以的很方便地更换更大的镜头,加入更大尺寸的CCD/CMOS感光器件,可以放入更加强大的图像处理模块,所以留给手机的发挥空间并不是很大,但是即便如此,各大手机厂商依旧在有限的空间和续航能力下,将相机系统做到了在某些领域媲美专业相机的地步,接下来我们来简单介绍下这套小体积但具有大能量的相机系统。

如图所示,手机的相机系统可以分为两个部分,一个是相机模组,一个是图像处理器ISP,相机模组是用来进行进行光电转换的,而图像处理器正如之前所介绍那样是用于图像处理的,接下来我们分别来看下,两者在手机端是如何运行的。

相机模组
由于受到体积的限制,手机相机模组往往做得十分精致小巧,里面主要包括了镜头、对焦马达、滤光片以及感光器(Sensor)。
手机中的镜头,一般为了消除色差都会采用多个透镜的组合,手机中的镜头也不例外,其材质多是玻璃和塑料的组合,对于塑料镜头而言,成本较低,适合用于低端产品中的相机系统,而玻璃一般成像质量较高,但是成本也稍高于塑料镜头,所以往往用于一些追求成像质量的手机中,同时其中,镜头主要存在以下几个参数:
视场角FOV,该参数表明了通过镜头可以成像多大范围的场景,一般FOV越大就越能看到大范围的景物,但是有可能会带来严重的畸变,通常使用后期的畸变矫正算法来修正大FOV所带来的畸变。
焦距F ,规定所有平行于透镜主轴的光线汇聚到的那点叫做焦点,而焦点到透镜中心的距离便是这里的焦距,一般焦距越大,镜头的FOV也就越小。而越短的焦距,往往FOV越大。
光圈值f,通过镜头焦距与实际光圈的直径比值来指定,该值越小,说明进光量也就越大,手机镜头一般采用f/2.0的固定光圈。

紧接着是对焦马达,这部分在手机中主要采用的是音圈马达(VCM),而为了方便调整镜头,一般会将整个镜头集成在马达模组中,主板通过I2C总线传输指令,进而驱动马达的移动调整镜头达到对焦或者变焦的目的,这里我们简单介绍下音圈马达。

音圈马达在电子学中被称为音圈电机,之所以被称为音圈,是因为其实现原理与扬声器类似,都是在一个永久磁场内部,通过改变马达内线圈的直流电流大小,来控制弹簧片的拉升位置,进而带动镜头上下运动,达到对焦或者变焦的目的,由于具有着高灵敏度与高精度的特点,使之成为手机的主流对焦组件。

在手机端,对于音圈马达的使用一般分为两种模式,一种是变焦,一种是对焦,两者原理和目的都不一样。
变焦: 通过马达调整镜头组中某一个透镜的移动,进而改变整个镜头的焦距,引起视场角的变化,从而实现对于景物的放大缩小的目的,这种方式便是我们常说的光学变焦,这种变焦手段的优点是在放大景物的过程中,不会损失图像细节,但是缺点也很明显,受到体积的限制,无法进行大范围的光学变焦,所以手机厂商一般采用光学与数字变焦的组合方式,达到高范围的变焦目的。
对焦: 通过音圈马达直接前后移动整个镜头,使物体的像平面与感光器的感光平面重合,进而得到一幅清晰的图像,这种方式正是对焦的过程。其目的是为了获得清晰的图像。

光线在经过了镜头之后,会首先进入到下一个组件–滤光片,该部分会针对光线做进一步处理,主要有两个目的:
过滤红外线: 由于感光器会感受到部分不可见的红外线,进而干扰后面的图像处理效果,所以需要通过滤光片,将这部分红外线过滤掉,只让可见光透过。
修正光线: 光线通过透镜之后,并不都是平行垂直射向感光器的,还有很多并非直射的光线,很显然如果不对其进行拦截,会对感光器产生一定的干扰,所以滤光片利用石英的物理偏光特性,保留了直射的光线,反射掉斜射部份,避免影响旁边的感光点,进一步提升成像效果。

经过滤光片的过滤与修正,此时入射的光线具有一定的稳定性,此时就需要通过这个相机体系的核心感光器来进行光电转换了。

手机端的感光器主要有CCD与CMOS,但是由于成本较高,体积较大,CCD在手机端已经用的不多了,CMOS成为了这个领域的主流感光器,手机端的CMOS依然采用了三层结构,微透镜/滤光片/感光层,具体定义如下:
微透镜层主要用于扩展单个像素的受光面积。
滤光片采用的事Bayer模式,类似与RGB模式,都是采用RGB几个颜色分量来分别度量每一个像素的三通道的灰度值,但是基于人眼对于绿色更为敏感的基本规律,Bayer模式进一步强调了绿色分量,从而将绿色分量分别定义了Gr以及Gb,用于更好地表达图像的色彩和亮度。
感光层,用于将光子转换成电子信号,在经过放大电路以及模电转换电路,将其转换成数字信号。

其感光层的核心便是一个个感光二极管,每一个二极管边上都包含了一个放大器和一个数模转换电路。由于每一个感光元件都有一个放大器,虽然在一定程度上加快的速度的读取,但是却无法保证每一个放大器的放大效果一致,所以这种设计会带来可能的噪声。另外,由于CMOS在每一个二极管旁都加入了额外的硬件电路,势必会造成感光面积的缩小,所以这种设计会影响整体感光效果,这种设计被称为前照式,为了解决该问题,CMOS厂商推出了背照式设计,这种设计将感光像素与金属电极晶体管分别放置于感光片的两面,提高了像素占空比,增加了光线感应效率,增加了像素数量,改善了信噪比,极大的提升了成像效果。

图像处理器
手机端的图像处理器的实现流程基本和非手机端的相机系统中类似,对于高通平台的ISP,其中主要包括了诸如IFE/BPS/IPE/JPEG等硬件模块,他们分别担任了不通过图像处理任务,接下来我们一一简单介绍下:
IFE(Image Front End): Sensor输出的数据首先会到达IFE,该硬件模块会针对preview以及video去做一些颜色校正、下采样、去马赛克统计3A数据的处理。
BPS(Bayer processing segment): 该硬件模块主要用于拍照图像数据的坏点去除、相位对焦、 去马赛克,下采样、HDR处理以及Bayer的混合降噪处理。
IPE(Image processing engine): 该硬件主要由NPS、PPS两部分组成,承担诸如硬件降噪(MFNR、MFSR)、图像的裁剪、降噪、颜色处理、细节增强等图像处理工作。
JPEG: 拍照数据的存储通过该硬件模块进行jpeg编码工作。
相对于专业相机而言,手机相机的受众并不了解太多专业的摄影学知识,但是这类群体具有一个明显不同于专业相机受众的特点,那就是比较关注相机的便携性和可玩性,其中便携性不用多说,整体手机相机的都是以小巧著称,但是可玩性方面,各大手机厂商也是煞费苦心,采用了很多策略来扩展了相机的可玩性,其中多摄便是一个比较典型的例子。

早期的手机相机,一般都是单独的后摄走遍天下,其功能比较单一,之后随着时代的发展以及年轻用户日益增多,对于自拍的需求愈发强烈,其中对于该领域的技术也有所突破。所以手机厂商便顺势推出了双摄模式,在手机前面额外加入一个相机模组来主要用于自拍,其中还在ISP中创新性地加入了美颜算法,进而大幅提升了自拍图像效果。紧接着,手机厂商将多个模组集成到手机上,进而满足了多个场景的拍照需求,接下来简单介绍下,多摄相机系统。

现如今的手机相机,往往采用了多个摄像模组,有专门的用于拍摄微缩景观的微距模组,也有专门拍摄广角场景的广角模组,也有为了满足特定需求开发的双摄系统,由于双摄技术的飞速发展,而今已经产生了很多中成熟的方案。

双摄技术顾名思义,是采用了两个摄像头模组分别成像,并通过特定的算法处理,融合成一张图像,达到特定成像需求的目的。普遍地,现在双摄方案主要用于实现背景虚化、提升暗光/夜景条件下成像质量、光学变焦,接下来依次进行简单的介绍:

a) 背景虚化(RGB + RGB)
为了实现该目的,主要采用了两个RGB的相机模组,同时对景物进行成像,利用三角测量原理,计算出每个点的景深数据,依靠该系列数据,进行前景以及背景的分离,再通过虚化算法针对背景虚化处理,最终营造出背景虚化的成像效果。值得注意的是,这里由于三角测量的原理的限制,需要对两个相机模组进行标定,使得两者成像平面位于同一平面,并且保持像素对齐。

b) 暗光提升(RGB + MONO)
在较暗的环境中,往往拍摄出来的效果不尽如人意,所以手机厂商便采用了一个RGB和一个黑白相机模组(MONO)来提升暗光成像效果,具体原理是,由于黑白相机模组没有Bayer滤光片,所以在暗光条件下,可以获得更多的进光量,进而保存了更多的图像细节,再加之RGB相机模组的颜色份量的补充,这样就可以更好的保证了暗光下的成像质量,同样的由于同样需要对两个相机模组的成像进行融合,所以依然需要进行标定操作,使两个相机模组能够保持像素对齐。

c) 光学变焦(广角 + 长焦)
光学变焦,正如之前介绍的,完全可以在对焦马达中通过调整单个透镜进行焦距变换,从而实现变焦的目的,但是有受到体积的限制,往往无法从单个相机模组中得到更大的变焦范围,所以手机厂商就提出采用两个具有不同焦距(广角和长焦)的相机模组,共同实现光学变焦的目的,其原理是通过广角模组呈现大范围的场景,通过长焦模组看到更远的场景,在拍照是模组切换以及优秀的融合算法实现了相对平滑的变焦操作。

通过上面的介绍,我们可以看到一个相机系统是通过镜头、光圈快门、感光器以及图像处理器组成,而为了提高其成像质量,在发展过程中逐步加入了滤光片、对焦马达以及闪光灯等组件,同时为了将相机系统嵌入手机中,无法避免地对硬件进行了一定的裁剪,比如光圈往往摒弃了可调形式,采用了固定光圈,另外,由于体积以及续航限制,手机上主流感光器主要采用了CMOS,而对焦马达也由于体积限制,对焦范围也有所缩小。但是即便硬件受到不小的限制,通过这这几年图像处理芯片不断发展,以及算法的不断优化,手机相机系统其实正在逐步缩小与专业相机的差距,我相信在不久的将来成像效果手机相机完全可以媲美专业相机。
————————————————
版权声明:本文为CSDN博主「闪烁丶星空」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DEVIL_SCL/article/details/126601710

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值