MTK Camera 从底层到应用层一网打尽

转载请注明出处(https://www.jianshu.com/p/5f538820e370),您的打赏是小编继续下去的动力

  1. Camera总体架构

1.1 Android系统架构

1.2 MTK Android Camera架构分层及代码结构

2.Camera HAL层分析

2.1 Camera HAL层的主要类介绍

2.2 Camera硬件抽象层的三种业务

2.2.1取景器preview(使用YUV原始数据格式,发送到视频输出设备)

2.2.2拍摄照片(可以使用原始数据或者压缩图像数据)

2.2.3 视频录制(将数据传送给视频编码器程序)

3.Camera Framework层分析

3.1 Camera Framwork整体架构和类关系简介

3.2 Camera Framwork层代码调用流程分析

3.2.1应用层onCreate函数入口

3.2.2 Framework层的open方法

3.2.3 JNI层的调用

3.2.4 Camera connect到服务

3.2.5 Camera服务之Binder代理端

3.2.6 Camera服务之CameraService分析

3.2.7 Camera服务之Client分析

3.2.8 Camera服务之Callback分析

4.Camera 应用层对Framework的封装分析

4.1 应用层对Framework层封装的层次和类介绍

4.2 camera的调用流程

5.Camera 应用初始化分析

6.Camera UI介绍

6.1 Camera UI总体预览

6.2 ModePicker类

6.3 IndicatorManager类

6.4 RemainingManager类

6.5 PickerManager类

6.6 ThumbnailManager类

6.7 ShutterManager类

6.8 SettingManager类

6.9 FocusManager类

7.Camera 预览流程

7.1 Camera预览命令流程

7.1.1 App层流程

7.1.2 Framework层流程

7.1.3 Binder调用

7.1.4 HAL层流程

7.2 数据流程

7.3 函数调用流程

8.Camera拍照流程

8.1 CameraService初始化过程

8.2应用程序链接相机服务过程

8.2.1 Camera连接整个过程

8.2.2 Camera takepicture过程

8.2.3 Camera takepicture过程总结

9.Camera Feature 介绍和API使用

9.1 Face detection人脸检测

9.1.1人脸检测主要介绍代码实现要素

9.1.2调用流程,这里以普通模式为例

9.2人脸美化Face beauty

9.2.1功能介绍

9.2.2三个参数的客制化

9.2.3常见问题

9.2.4调用流程

9.3 ASD:自动场景检测

9.3.1限制条件

9.3.2常见问题

9.3.3代码实现要素

9.4 Continue Shot

9.4.1简单介绍与使用方法

9.4.2 Continue Shot Spec and Limitation

9.4.3 Continue Shot效果调试/客制化参数

9.4.4 Continue Shot常见问题

9.5零延时拍照ZSD

9.5.1简介

9.5.2 ZSD Spec and Limitation

9.5.3 ZSD客制化参数

9.5.4 ZSD常见问题

9.6笑脸模式

9.6.1简介

9.6.2限制条件

9.6.3 FD常见问题

9.7物体动态追踪Object Tracking

9.7.1简介

9.7.2说明与限制条件

9.7.3 OT效果调试/客制化参数

9.8全景拍照

9.8.1简介

9.8.2规则和限制条件

9.8.3全景拍照客制化参数

9.8.4全景拍照常见问题

9.8.5全景拍照相关问题debug流程

9.9多视角拍照(MAV)

9.9.1简介

9.9.2限制与规则

9.9.3常见问题

9.10情景照片Live Photo

9.10.1简介

9.10.2适用场景及使用方法

9.10.3限制条件

9.11 HDR

9.11.1简介

9.11.2 HDR 规则和限制条件

9.11.3 HDR效果调试/客制化参数

9.11.4 HDR常见问题

10.Camera中第三方算法添加

10.1 Camera HAL层预览

10.2 Camera图像缓冲Queue

10.3第三方算法预览部分

10.3.1 ExtImgProc介绍

10.3.2 相关类结构图

10.3.3 ExtImpProc处理过程

10.3.4三方算法对性能影响的简单分析

10.4拍照的第三方算法添加

10.4.1移植capture的第三方算法

10.4.2对ZSD 做第三方算法处理:(CapBufShot.cpp)

10.5 新增一个Capture Mode

11.拍照、录像接口调用及开发

11.1 调用系统Camera APP实现功能

11.1.1 实现拍照

11.1.2 实现摄像

11.2调用Camera API实现相应功能

11.2.1添加权限

11.2.2 实现拍照功能

11.2.3实现摄像功能

12.语音拍照详解

12.1设置界面应用是否增加开启、关闭语言识别的开关

12.2增加第三方应用支持设置

12.3增加第三方应用支持的关键词

12.4更改默认设置

12.5更改默认语言

12.6增加新的语言支持

12.7增加语言拍照和语言设置的关联

13.Camera Performance问题分析初步

13.1 Camera startup time

13.1.1 Camera startup time 的不同模式差异分析

13.1.2 Camera startup time 分析LOG

13.1.3 Camera startup time 的参考数据

13.2 Shot to Shot/shutter delay

13.2.1 Shot to Shot/shutter delay分析LOG

13.2.2参考数据

13.2.3各阶段受客制化影响的主要因素

13.3 main/sub sensor switch time

13.3.1 Shot to Shot/shutter delay分析LOG

13.3.2参考数据

13.4目前平台可优化的地方

13.4.1 Stop preview节省帧率

13.4.1拍照回显定格时间较长

14.HQ Camera整体优化方案介绍

14.1改进思路

14.2用户需求(竞品机优势)

14.3 Camera基础效果优化(持续进行中)

14.4软件特效类 132

14.5其他提升体验的细节优化(进展)

14.6前摄选型要求

14.7夜拍效果提升选型要求

14.8提高拍照速度-修改原理及细节

14.9优化暗光下MFLL和MBF功能阀值

14.10 零延时拍照

14.11下一步计划

1. Camera总体架构

1.1 Android系统架构

要分析Android Camera架构,首先从Android本身的架构分析。如下图:

图片.png

从Android框架看,分为四层:应用层、应用框架层、库层、内核层。

应用层主要工作在Android SDK之上,利用Android提供的API进行开发,生成APK包。

应用框架层整合了Android自带各种控件和类,为应用开发提供高效、方便的API接口。这个对上实现统一的接口,对下也提供统一的标准方便各种库的移入。

库层是Android与底层硬件通信接口,它封装底层硬件接口实现该模块的具体逻辑,并以服务的形式通过Binder通讯机制暴露给应用框架。

内核层则是直接与硬件联系的一层,可以理解为设备驱动。

1.2 MTK Android**** Camera架构分层及代码结构

Android的Camera包含取景器(viewfinder)和拍摄照片(takepicture)的功能。目前MTK Android Camera程序的架构分成客户端和服务器两个部分,它们建立在Android的进程间通讯Binder的结构上。Camera模块同样遵循Android的框架,如下图所示。

图片.png

Camera 架构主要分为以下几个层次:

1.****应用层

Camera的应用层在Android上表现为直接调用SDK API开发的一个Camera 应用APK包。代码在\packages\apps\Camera下。主要是Java写的基于android.hardware.Camera类调用的封装,并且实现Camera应用的业务逻辑和UI显示。android.hardware.Camera就是Android提供给上层调用的Camera类。这个类用来连接或断开一个Camera服务,设置拍摄参数,开始、停止预览,拍照等。它也是Android Camera应用框架封装暴露出来的接口。一个Android应用中若要使用这个类,需要在Manifest文件声明Camera的权限,另外还需要添加一些<uses-feature>元素来声明应用中的Camera特性,如自动对焦等。具体做法可如下:

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

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

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

2.应用框架层

Camera框架层将应用与底层的实现隔离开来,实现了一套Android定义的对上对下接口规范,方便应用及底层硬件的开发和移植。这一层对上以Java类的形式包装出android.hardware.Camera,提供给应用层调用;对下在CameraHardwareInterface.h头文件中定义了Camera硬件抽象层的接口,这是一个包含纯虚函数的类,必须被实现类继承才能使用。这个实现类也即是下层中将讲到的用户库层,它继承CameraHardwareInterface接口,实例化对底层硬件驱动的封装,最终生成libcamera.so供框架的libcameraservice.so调用。这样做的好处是让Camera的应用框架代码独立,不受底层硬件驱动改变的影响,方便在不同平台上porting 驱动代码,而保持上层的代码不用变化。

从代码上看,这一层包含Java到JNI到C++的代码。源代码主要在以下路径:

\android\frameworks\base\core\java\android\hardware\Camera.java

这个类作为Android SDK Camera部分提供给上层应用,并通过JNI的方式调用本地C++代码。

\android\frameworks\base\core\jni\android_hardware_Camera.cpp是Camera 的JAVA本地调用部分,是承接JAVA代码到C++代码的桥梁。编译生成libandroid_runtime.so。

\android\frameworks\base\libs\ui 包含文件:

Camera.cpp

CameraParameters.cpp

ICamera.cpp

ICameraClient.cpp

ICameraService.cpp

它们的头文件在\android\frameworks\base\include\ui目录下。这部分的内容编译生成libui.so。在Camera模块的各个库中,libui.so位于核心的位置,作为Camera框架的Client客户端部分,与另外一部分内容服务端libcameraservice.so通过进程间通讯(即Binder机制)的方式进行通讯。\android\frameworks\base\camera\libcameraservice CameraService是Camera服务,Camera框架的中间层,用于链接CameraHardwareInterface 和 Client,它通过调用实际的Camera硬件接口来实现功能。这部分内容被编译成库libcameraservice.so。

libandroid_runtime.so和libui.so两个库是公用的,其中除了Camera还有其他方面的功能。整个Camera在运行的时候,可以大致上分成Client和Server两个部分,它们分别在两个进程中运行,它们之间使用Binder机制实现进程间通讯。这样在client调用接口,功能则在server中实现,但是在client中调用就好像直接调用server中的功能,进程间通讯的部分对上层程序不可见。

当Camera Client端通过Binder机制与Camera Server端通讯,Server端的实现传递到Client端。而Server端的实现又是调用硬件接口来实现。

3.**** HAL层

这个层次其实就是用户空间的驱动代码。前面有介绍过框架层对下在CameraHardwareInterface.h头文件中定义了Camera硬件抽象层的接口,它是包含纯虚函数的类,必须被实现类继承才能使用。HAL层正好继承CameraHardwareInterface接口,依据V4l2规范实例化底层硬件驱动,使用ioctl方式调用驱动,最终生成libcamera.so供框架的libcameraservice.so调用。

这层的代码在\android\hardware\XXX\libcamera目录下(也有可能在vendor目录中对应的libcamera下)。注意这里的XXX是不同厂商为不同产品(板子)而建的目录,以高通msm平台为例,这里XXX用msm7k表示,这样高通msm平台下这个HAL的目录即为\android\hardware\msm7k\libcamera。不难看出,如果要在某硬件平台上运行Android,也就主要在这一层进行修改,因为它是直接和底层硬件驱动相关的。上面也讲过,应用框架层对上对下都定义的标准接口,这样做的目的也就是使上层的代码独立,在porting中不受影响。所以我们现在可以基本确定,如果要改Camera的硬件,框架层以上的部分都可以不动,要改就改HAL到内核层的部分,这也是Android底层开发的主要工作。

4.内核层

这一层主要是基于Linux的设备驱动。对Camera来说,一般是按V4l2规范将Camera原子功能以ioctl的形式暴露出来供HAL层调用的实现。主要功能的实现代码在\android\kernel\drivers\media\video\XXX下。跟HAL层目录一样,XXX是不同厂商不同平台的目录,以高通msm平台为例,这个目录就是\android\kernel\drivers\media\video\msm。所以要在Android平台上添加硬件功能,首先考虑将它的驱动加到Android的Linux内核中。

5.**** Camera库文件

前面已提到Camera模块主要包含libandroid_runtime.so、libcamera_client.s0(libui.so)、libcameraservice.so和一个与Camera硬件相关的硬件库libcamera.so。其中libandroid_runtime.so、libcamera_client.s0(libui.so)是与android系统构架相关的, 不需要对进行其修改,libameraservice.so和libcamera.so则是和硬件设备相关联的,而cameralib.so实际上就是设备的linxu驱动,所以Camera设备的系统继承主要是通过移植CameraLinux驱动和修改libcameraservice.so库来完成。

Libcameraservice.so构建规则中使用宏USE_CAMERA_STUB决定 是否使用真的Camera,如果宏为真,则使用CameraHardwareStub.cpp和FakeCamera.cpp构造一个假的Camera,如果为假则使用libcamera来构造一个实际上的Camera服务。

在CameraHardwareStub.cpp中定义了CameraHardwareStub类,它继承并实现了抽象类CameraHardwareInterface中定义的真正操作Camera设备的所有的纯虚函数。通过openCameraHardware()将返回一个CameraHardwareInterface类的对象,但由于CameraHardwareInterface类是抽象类所以它并不能创建对象,而它的派生类CameraHardwareStub完全实现了其父类的 纯虚函数所以openCameraHardware()返回一个指向派生类对象的基类指针用于底层设备的操作。由于CameraHardwareStub类定义的函数是去操作一个假的Camera,故通过openCameraHardware返回的指针主要用于仿真环境对Camera的模拟操作,要想通过openCameraHardware返回的指针操作真正的硬件设备则需完成以下步骤:

⑴CameraHardwareInterface类中的所有纯虚函数的声明改为虚函数的声明(即去掉虚函数声明后的“=0”);

⑵编写一个源文件去定义CameraHardwareInterface类中声明的所有虚函数;

⑶编写Android.mk文件用于生成一个包含步骤⑵编写的源文件和其他相关文件的libcamera.so文件;

⑷将宏USE_CAMERA_STUB改成false,这样生成libcameraservice.so时就会包含libcamera.so库。(注:如果CameraHardwareInterface类的成员函数并没有直接操作硬件而是调用Camera的linux驱动来间接对硬件操作,那么包含这样的CameraHardwareInterface类的libcamera.so库就相当于一个HAL)。

以上Camera的结构层次可以简单的概括成如下流程图:

图片.png

2.Camera HAL层分析

2.1 Camera HAL层的主要类介绍

首先看一下Camera HAL层的主要类关系图:

图片.png

1. Camera****硬件抽象层

frameworks/base/include/camera/

主要文件为CameraHardwareInterface.h,需要各个系统根据自己的情况实现。

2. Camera****服务部分

frameworks/base/services/camera/libcameraservice

Camera服务是Andriod系统中一个单独的部分,通过调用camera硬件抽象层

来实现。

3. Camera ****的本地框架代码

frameworks/base/libs/camera/

4. Camera****的硬件抽象层

Camera的硬件抽象层是位于V4L2驱动程序和CameraService之间的部分,这是一个C++的接口类,需要具体的实现者继承这个类,并实现其中的各个纯虚函数。纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。硬件抽象层的主要的头文件为:

CameraHardwareInterface.h,定义了C++的接口类需要根据系统的情况继承实现。

camera.h 这是Camera系统本地对上层的接口。

CameraParameters.h定义Camera系统的参数,在本地代码的各个层次中使用。

Camera硬件抽象层的实现通常需要生成动态库libcamera.so。

5. CameraHardwareInterface****中定义了几种回调函数

typedef void (*notify_callback)(int32_t msgType, //通知回调

                            int32_t ext1,

                            int32_t ext2,

                            void* user);

typedef void (*data_callback)(int32_t msgType, //数据回调

                          const sp<IMemory>& dataPtr,

                          void* user);

typedef void (*data_callback_timestamp)(nsecs_t timestamp,//带有时间的数据回调

                                    int32_t msgType,

                                    const sp<IMemory>& dataPtr,

                                    void* user);

消息类型mstType 数据IMemory,回调函数由setCallbacks(),enableMsgType()函数统一处理。

setCallbacks()可以设置三个类型的回调函数指针

/** Set the notification and data callbacks */

virtual void setCallbacks(notify_callback notify_cb,

                          data_callback data_cb,

                          data_callback_timestamp data_cb_timestamp,

                          void* user) = 0;

2.2 ** Camera****硬件抽象层的三种业务**

2.2.1****取景器****preview(****使用****YUV****原始数据格式,发送到视频输出设备****)

CameraHardware.cpp中:

1.在初始化过程中,建立preview的内存队列

void CameraHardware::initHeapLocked() {

// Create raw heap.  

**int** picture_width, picture_height;  

mParameters.getPictureSize(&picture_width, &picture_height);  

mRawHeap = **new** MemoryHeapBase(picture_width * picture_height * 2);  

**int** preview_width, preview_height;  

mParameters.getPreviewSize(&preview_width, &preview_height);  

**int** how_big = preview_width * preview_height*2;  

}

void CameraHardware::initDefaultParameters() {

CameraParameters p;  

p.**set**(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES, "320x240");  

p.setPreviewSize(320, 240);//大小为320*240  

p.setPreviewFrameRate(15);//帧率为15bps  

p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_RGB565);//preview 的格式为RGB565  

p.**set**(CameraParameters::KEY_ROTATION, 0);//90  

p.**set**(CameraParameters::KEY_SUPPORTED_PICTURE_SIZES, "320x240");  

p.setPictureSize(320, 240);  

p.setPictureFormat(CameraParameters::PIXEL_FORMAT_JPEG);// SET OUTPUT PIC TO BMP FORMAT  

**if** (setParameters(p) != NO_ERROR) {  

    LOGE("Failed to set default parameters?!");  

}  

}

2.在startPreview()的实现中,建立preview线程

status_t CameraHardware::startPreview() {

Mutex::Autolock **lock**(mLock);  

**if** (mPreviewThread != 0) {  

      **return** INVALID_OPERATION;  

}  

mPreviewThread = **new** PreviewThread(**this**);  

**return** NO_ERROR;  

}

3.在preview线程的循环中,等待视频数据的到达

int CameraHardware::previewThread() {

**if**(mCamType == CAMTYPE_CMOS)   

        Ov965xCamera->getNextFrameAsRgb565((uint16_t *)frame);  

4.视频帧到达后使用preview回调的机制CAMERA_MSG_PREVIEW_FRAME,将视频

帧向上层传送

// Notify the client of a new frame.

     **if** (mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME)  

           mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie);  

**2.2.2****拍摄照片(可以使用原始数据或者压缩图像数据) **

1.takePicture()函数表示开始拍摄,可以建立单独的线程来处理

status_t CameraHardware::takePicture() {

stopPreview();  

**if** (createThread(beginPictureThread, **this**) == **false**)  

      **return** UNKNOWN_ERROR;  

**return** NO_ERROR;  

}

int CameraHardware::beginPictureThread(void *cookie) {

CameraHardware *c = (CameraHardware *)cookie;  

**return** c->pictureThread();  

}

int CameraHardware::pictureThread() {

**if** (mMsgEnabled & CAMERA_MSG_SHUTTER)  

      mNotifyCb(CAMERA_MSG_SHUTTER, 0, 0, mCallbackCookie);  

**if** (mMsgEnabled & CAMERA_MSG_RAW_IMAGE) {  

    //FIXME: use a canned YUV image!  

    // In the meantime just make another fake camera picture.  

    **int** w, h;  

    mParameters.getPictureSize(&w, &h);  

    sp<MemoryBase> mem = **new** MemoryBase(mRawHeap, 0, w * h * 2);  

    Ov965xCamera cam(w, h);  

    //cam.getNextFrameAsYuv420((uint8_t *)mRawHeap->base());  

    **if**(mCamType == CAMTYPE_CMOS)   {  

      cam.getNextFrameAsRgb565((uint16_t *)mRawHeap->**base**());   

    } **else** **if** (mCamType == CAMTYPE_USB)   {  

     //USBCamera cam1(w, h);  

    LOGE("%s, Taking picure using USB CAM", LOG_TAG);  

    //cam.getNextFrameAsRgb565((uint16_t *)mRawHeap->base()); 

    //cam1.getNextFrameAsYuv420((uint16_t *)mRawHeap->base());   

    }  

    mDataCb(CAMERA_MSG_RAW_IMAGE, mem, mCallbackCookie);  

}  

// ToDo: Release MemoryHeapBase  

// ToDo: Convert BMP to JPEG  

// Todo: Higher Resultion Support  

**if** (mMsgEnabled & CAMERA_MSG_COMPRESSED_IMAGE) {  

LOGE("%s, COMPRESSED IMAGE", LOG_TAG);  

    **int** w, h;  

unsigned **int** DATA_OFFSET = 54;  

    uint16_t WIDTH = w;  

uint16_t HEIGHT = h;  

    mParameters.getPictureSize(&w, &h);  

Ov965xCamera* Ov965xCamera = mOv965xCamera;   

sp<MemoryHeapBase> heap = **new** MemoryHeapBase(DATA_OFFSET+w * h* 2);  

sp<MemoryBase> mem = **new** MemoryBase(heap, 0, DATA_OFFSET+w * h* 2); //16 bits for one pixel  

    uint8_t header[54] = { 0x42, // identity : B  

    0x4d, // identity : M  

    0, 0, 0, 0, // file size  

    0, 0, // reserved1  

    0, 0, // reserved2  

    54, 0, 0, 0, // RGB data offset  

    40, 0, 0, 0, // struct BITMAPINFOHEADER size  

    0, 0, 0, 0, // bmp height  

    0, 0, 0, 0, // bmp width  

    1, 0, // planes  

    16, 0, // bit per pixel  

    0, 0, 0, 0, // compression  

    0, 0, 0, 0, // data size  

    0, 0, 0, 0, // h resolution  

    0, 0, 0, 0, // v resolution  

    0, 0, 0, 0, // used colors  

    0, 0, 0, 0 // important colors  

    };  

    // file size offset 54  

uint16_t file_size = WIDTH * HEIGHT * 2 + DATA_OFFSET;  

header[2] = (uint8_t)(file_size & 0x000000ff);  

header[3] = (file_size >> 8) & 0x000000ff;  

header[4] = (file_size >> 16) & 0x000000ff;  

header[5] = (file_size >> 24) & 0x000000ff;  

// height  

header[18] = HEIGHT & 0x000000ff;  

header[19] = (HEIGHT >> 8) & 0x000000ff;  

header[20] = (HEIGHT >> 16) & 0x000000ff;  

header[21] = (HEIGHT >> 24) & 0x000000ff;  

// width  

header[22] = WIDTH & 0x000000ff;  

header[23] = (WIDTH >> 8) & 0x000000ff;  

header[24] = (WIDTH >> 16) & 0x000000ff;  

header[25] = (WIDTH >> 24) & 0x000000ff;  

LOGE("%s, Header Ready", LOG_TAG);  

unsigned **int** i;  

**for**(i=0;i<DATA_OFFSET;i++){  

    *((uint8_t*)heap->**base**()+i)=header[i];  

}  

Ov965xCamera->getNextFrameAsRgb565((uint16_t*)heap->**base**()+DATA_OFFSET/2);  

uint16_t *heap_base = (uint16_t*)heap->**base**();  

uint16_t pixel_data;  

uint8_t tail_data;  

**for**(i=DATA_OFFSET/2;i<DATA_OFFSET/2+WIDTH*HEIGHT;i++){  

  pixel_data = *(heap_base+i);  

  tail_data = (uint8_t)(pixel_data & 0x001f);  

  pixel_data = (pixel_data & 0xffc0)>>1 | tail_data;  

  *(heap_base+i)=pixel_data;  

}  

mDataCb(CAMERA_MSG_COMPRESSED_IMAGE, mem, mCallbackCookie);

heap=NULL;  

LOGE("%s, IMAGE SAVED!", LOG_TAG);  

}  

**return** NO_ERROR;  

}

2.使用回调机制传送数据

如果得原始格式(通常是YUV的格式,如yuv422sp,这里是RGB565)的数据,使用CAMERA_MSG_RAW_IMAGE将数据传送;如果得到压缩图像(通常JPEG格式,这里是BMP)使用CAMERA_MSG_COMPRESSED_IMAGE将数据传送。

2.2.3****视频录制(将数据传送给视频编码器程序)

1.在startRecording()的实现中,开始录制的准备,录制视频可以使用自己的线程,也可以使用preview线程。

2.当一个视频帧到来的时候,通过录制回调机制(使用CAMERA_MSG_VIDEO_FRAME)将视频帧向上发送。

3.releaseRecordingFrame()被调用后,表示上层通知Camera硬件抽象层,这一帧的内存已经用完,可以进行下一次的处理。

3.Camera Framework层分析

3.1 Camera Framwork整体架构和类关系简介

现在我们尝试从最开始的启动流程来熟悉android camera的整体framework流程。Camera预览的数据流程如下:

图片.png

主要类的继承关系图如下:

图片.png

首先从上图的各个步骤来逐一分析流程,后续会根据具体的一些点进行内容的添加和扩充。

1.****Camera.java

packages/apps/camera/src/com/android/

最上层的应用就是从这个文件开始。

该文件集中了整个android上层应用的所有相关内容,当然更多的则为界面的代码实现。如果出现了camera应用界面的问题(当然除了camera拍摄区域内容外),可以从android的代码入手。

2.****Camera.java

frameworks\base\core\java\android\hardware\

该文件中主要是对native函数接口的调用,当然也包括一些本地的函数实现。也可以认为该文件是实现了从java层调用c++层代码函数接口。也就是我们需要去了解的一点JNI机制。

3.****android_hardware_Camera.cpp

该文件就是JNI的c++层的代码实现。通过camera的类实例来调用camera类的相关接口。

4.****Camera.cpp/Camera.h

对于上层应用来说,camera.cpp是最为直接的函数调用和实现。继承于ICameraClient类,典型的Client端的接口实例。

5.****BnCameraClient/BpCameraClient

IPC通讯所需的函数接口实现,继承于ICameraClient类。

6.****ICameraClient.cpp/ICameraClient.h

Client/Service模式下的Client端实现

7.****ICameraService.cpp/ICameraService.h

Client/Service模式下service端实现

8.**** BnCameraService/BpCameraService

IPC通讯所需的函数接口实现,继承于ICameraService类。

9.****CameraService.cpp/CameraService.h

继承于BnCameraService类。是对BnCameraService函数接口的实现,其本质也是对CameraService的内部类Client函数接口的调用。

10.****Client(CameraService****内部类****)

该类才是真正的底层函数实现,其通过openCameraHardware()得到camera硬件实例对象进行操作。其继承于ICamera类,是对ICamera类函数接口的实现。

3.2 Camera Framwork层代码调用流程分析

接下来,我们通过对流程的步步分析来将camera整体串接起来:

3.2.1应用层****onCreate****函数入口

首先则看看camera.java的 onCreate函数入口,针对android的所有应用,onCreate函数入口作为跟踪和了解应用架构的首选。

public void onCreate(Bundle icicle) {

    super.onCreate(icicle);

    devlatch = new CountDownLatch(1);

CountDownLatch()关于这个类,可以简单的理解为它是用来线程之间的等待处理,当然这里采用的计数为1,则可以简单理解为一个计数开关来控制调用了tlatch.await()函数的进程,方式就是将devlatch的计数减为0(countDown() )。

这里启动了一个线程用来打开camera服务,而打开过程则比较费时(一般在2s左右),故单独启用一个线程避免应用线程阻塞。

Thread startPreviewThread = new Thread(new Runnable() {

        CountDownLatch tlatch = devlatch;

        public void run() {

            try {

                mStartPreviewFail = false;

                ensureCameraDevice();

                // Wait for framework initialization to be complete before

                // starting preview

                try {

                    tlatch.await();

                } catch (InterruptedException ie) {

                    mStartPreviewFail = true;

                }

                startPreview();

            } catch (CameraHardwareException e) {

                // In eng build, we throw the exception so that test tool

                // can detect it and report it

                if ("eng".equals(Build.TYPE)) {

                      throw new RuntimeException(e);

                }

                mStartPreviewFail = true;

            }

        }

    });

    startPreviewThread.start();

在这里,需要跟进ensureCameraDevice();该函数,可以看到其实现为:

private void ensureCameraDevice() throws CameraHardwareException {

    if (mCameraDevice == null) {

        mCameraDevice = CameraHolder.instance().open();

        mInitialParams = mCameraDevice.getParameters();

    }

}

当前mCameraDevice()实例为null,则会调用CameraHolder.instance().open()函数来创建mCameraDevice对象实例。

private android.hardware.Camera mCameraDevice;

跟进CameraHolder.instance().open(),进入到了CameraHolder类中:

public synchronized android.hardware.Camera open()

        throws CameraHardwareException {

          Assert(mUsers == 0);

    if (mCameraDevice == null) {

        try {

            mCameraDevice = android.hardware.Camera.open();

        } catch (RuntimeException e) {

            Log.e(TAG, "fail to connect Camera", e);

            throw new CameraHardwareException(e);

        }

        mParameters = mCameraDevice.getParameters();

    } else {

……

下面大概介绍下我对CameraHolder的理解:

1.CameraHolder对mCameraDevice实例进行短暂的保留(keep()函数中可以设定这个保留时长,一般默认为3000ms),避免用户在短暂退出camera又重新进入时,缩短camera启动时长(正如之前所说,打开CameraDevice时间较长)

2.CameraHolder并有一个关键的计数mUsers用来保证open()和release()的配套调用,避免多次重复释放或者打开(上层应用的保护措施之一)。

3.2.2 Framework层的open方法

第一步的完成,进而跳转到了android.hardware.Camera类中的open()函数接口调用。

public static Camera open() {

return new Camera();

}

静态函数,也就可以通过类名直接调用,open()函数中去创建一个Camera的实例。

Camera() {

    mShutterCallback = null;

    mRawImageCallback = null;

    mJpegCallback = null;

    mPreviewCallback = null;

    mPostviewCallback = null;

    mZoomListener = null;

    Looper looper;

    if ((looper = Looper.myLooper()) != null) {

        mEventHandler = new EventHandler(this, looper);

    } else if ((looper = Looper.getMainLooper()) != null) {

        mEventHandler = new EventHandler(this, looper);

    } else {

        mEventHandler = null;

    }

    native_setup(new WeakReference<Camera>(this));

}

在Camera构造函数中有这个关键的一步,最开始的一些callback可以认为它们最终被底层调用到(至于具体流程后面会讲到)。EventHandler和Looper我们暂时跳过,知道它是消息处理就行了。最后也就是最为关键的函数接口调用:native_setup

private native final void native_setup(Object camera_this);

典型的native函数接口声明,说明并非camera类的本地函数实现,也就意味着会通过JNI(Java Native Interface)调用对用C++文件中的函数接口。

3.2.3 JNI层的调用

跳转到android_hardware_Camera.cpp中寻找native_setup()所对应的JNI函数接口:

   static JNINativeMethod camMethods[] = {

{ "native_setup",

"(Ljava/lang/Object;)V",

(void*)android_hardware_Camera_native_setup },

{ "native_release",

"()V",

(void*)android_hardware_Camera_release },

{ "setPreviewDisplay",

"(Landroid/view/Surface;)V",

(void *)android_hardware_Camera_setPreviewDisplay },

     ……

而camMethods[]在什么时候映射的那?继续看:

int register_android_hardware_Camera(JNIEnv *env) {

…..

// Register native functions

return AndroidRuntime::registerNativeMethods(env,

"android/hardware/Camera",

camMethods,NELEM(camMethods));

}

最终在AndroidRuntime.cpp中被调用:

REG_JNI(register_android_hardware_Camera),

说明如果我们自己要添加JNI接口实现的话,这些地方也需要添加相应的代码(具体在AndroidRuntime.cpp的细节我没深看,也不做介绍)。

简单介绍: JNINativeMethod的第一个成员是一个字符 串,表示了JAVA本地调用方法的名称,这个名称是在JAVA程序中调用的名称;第二个成员也是一个字符串,表示JAVA本地调用方法的参数和返回值;第三个成员是JAVA本地调用方法对应的C语言函数。

跟进观察android_hardware_Camera_native_setup()函数的实现:

// connect to camera service

static void android_hardware_Camera_native_setup(JNIEnv *env

, jobject thiz, jobject weak_this) {

sp<Camera> camera = Camera::connect();

if (camera == NULL) {

    jniThrowException(env, "java/lang/RuntimeException",

                      "Fail to connect to camera service");

    return;

}

….

}

初步可以认为Camera::connect()的函数调用时返回了一个Camera 的实例对象。

3.2.4 Camera connect到服务

通过上述的跟进流程来到了针对上层应用而言最为直接的类:camera.cpp:

对Camera::connect函数的调用如下:

sp<Camera> Camera::connect(){

    LOGV("connect");

    sp<Camera> c = new Camera();

    const sp<ICameraService>& cs = getCameraService();

    if (cs != 0) {

        c->mCamera = cs->connect(c);

    }

    if (c->mCamera != 0) {

        c->mCamera->asBinder()->linkToDeath(c);

        c->mStatus = NO_ERROR;

    } else {

        c.clear();

    }

    return c;

}

首先是创建一个camera对象实例,然后通过调用getCameraService()去取得ICameraService的服务实例:

// establish binder interface to camera service

const sp<ICameraService>& Camera::getCameraService(){

Mutex::Autolock _l(mLock);

if (mCameraService.get() == 0) {

    sp<IServiceManager> sm = defaultServiceManager();

    sp<IBinder> binder;

    do {

        binder = sm->getService(String16("media.camera"));

        if (binder != 0)

           break;

        LOGW("CameraService not published, waiting...");

        usleep(500000); // 0.5 s

    } while(true);

    if (mDeathNotifier == NULL) {

        mDeathNotifier = new DeathNotifier();

    }

    binder->linkToDeath(mDeathNotifier);

    mCameraService = interface_cast<ICameraService>(binder);

}

LOGE_IF(mCameraService==0, "no CameraService!?");

return mCameraService;

}

这边就涉及到了ServiceManager()对服务的管理,在这之前Camera的服务已经注册到了ServiceManager中,我们可以通过服务字串(media.camera)来获得camera service(其本质得到的是CameraService的实例对象,虽然通过类型上溯转换成父类ICameraService,对ICameraService对象的函数调用本质是调用到了CameraService的函数实现)。

在得到camera service后,返回之前的步骤:当得到的cs即cameraservice实例存在时,通过调用cs->connect(c)去得到ICamera实例,并赋值给了camera实例的一个类成员ICamera mCamera:

if (cs != 0) {

c->mCamera = cs->connect(c);

3.2.5 Camera服务之Binder代理端

接下来则涉及到ICamraService的相关调用关系,其实这个地方需要去弄清楚一些函数接口的实现在具体哪些文件中,因为存在较多的虚函数。

继续流程,上一步走到了cs->connect(),也就是ICameraService的connect()函数接口。

class ICameraService : public IInterface{

public:

enum {

    CONNECT = IBinder::FIRST_CALL_TRANSACTION,

};

public:

DECLARE_META_INTERFACE(CameraService);

virtual sp<ICamera>     connect(const sp<ICameraClient>& cameraClient) = 0;

};

可以发现该connect()接口为一个纯虚函数,需要ICameraService的子类对该接口进行实现,从而对connect()的调用则会映射到ICameraService子类的具体实现。

关于ICameraService的实例问题,目前暂时跳过(后面马上就会讲到),简单认为这个时候会调用到其一个子类的实现:

class BpCameraService: public BpInterface<ICameraService>{

public:

BpCameraService(const sp<IBinder>& impl)

    : BpInterface<ICameraService>(impl) {

}

// connect to camera service

virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient){

    Parcel data, reply;

    data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());

    data.writeStrongBinder(cameraClient->asBinder());

    remote()->transact(BnCameraService::CONNECT, data, &reply);

    return interface_cast<ICamera>(reply.readStrongBinder());

}

};

BpCameraService为代理类,其主要用途为Binder通讯机制即进程间的通讯(Client/Service),最终还是会调用BnCameraService的具体实现,即:

status_t BnCameraService::onTransact(

uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {

switch(code) {

    case CONNECT: {

        CHECK_INTERFACE(ICameraService, data, reply);

        sp<ICameraClient> cameraClient

         = interface_cast<ICameraClient>(data.readStrongBinder());

        sp<ICamera> camera = connect(cameraClient);

        reply->writeStrongBinder(camera->asBinder());

        return NO_ERROR;

    } break;

    default:

        return BBinder::onTransact(code, data, reply, flags);

}

}

而BnCameraService(为实现类)类继承于ICameraService,并且也并没有对connect()纯虚函数进行了实现,同样意味着其实该调用的实质是BnCameraService的子类实现。

毕竟虚函数的调用没有实例肯定是没有意义的,说明我们需要找到对connect()纯虚函数的实现子类即继承于BnCameraService。

3.2.6 Camera服务之****CameraService****分析

结合上面所述,可以寻找到了继承于BnCameraService的子类CameraService.cpp:

这时虽然找到了CameraService该类,但是你肯定会问到该类实例的创建在什么地方哪?再后头看CameraService启动注册的地方:

int main(int argc, char** argv) {

sp<ProcessState> proc(ProcessState::self());

sp<IServiceManager> sm = defaultServiceManager();

LOGI("ServiceManager: %p", sm.get());

AudioFlinger::instantiate();

MediaPlayerService::instantiate();

CameraService::instantiate();

AudioPolicyService::instantiate();

ProcessState::self()->startThreadPool();

IPCThreadState::self()->joinThreadPool();

}

这个main函数位于main_mediaserver.cpp中,而mediaserver是在系统开始的时候就启动起来的server端(MediaServer,在系统启动时由init所启动,具可参考init.rc文件),进而将相关的服务也创建了实例。

跟进CameraService::instantiate()函数实现,可以发现:

void CameraService::instantiate() {

defaultServiceManager()->addService(

        String16("media.camera"), new CameraService());

}

创建了一个CameraService实例 ,并给定了CameraService的服务字串为”media.camera”,而之前在通过ServiceManager获取CameraService的时候,所调用的接口为binder = sm->getService(String16("media.camera"));,两者保持了一样的字符串。

if (mCameraService.get() == 0) {

    sp<IServiceManager> sm = defaultServiceManager();

    sp<IBinder> binder;

    do {

        binder = sm->getService(String16("media.camera"));

        if (binder != 0)

              break;

        LOGW("CameraService not published, waiting...");

        usleep(500000); // 0.5 s

    } while(true);

    if (mDeathNotifier == NULL) {

        mDeathNotifier = new DeathNotifier();

    }

    binder->linkToDeath(mDeathNotifier);

    mCameraService = interface_cast<ICameraService>(binder);

}

结合上述分析,此处的binder对象其实为CameraService类实例(多态类型转换)。

interface_cast<ICameraService>(binder)宏映射,需要展开:

template<typename INTERFACE>

inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj) {

  return INTERFACE::asInterface(obj);

}

INTERFACE::asInterface(obj);宏映射,继续展开可得:

sp<I##INTERFACE> I##INTERFACE::asInterface(const sp<IBinder>& obj) \

{ \

    sp<I##INTERFACE> intr;                                        \

    if (obj != NULL) {                                              \

        intr = static_cast<I##INTERFACE*>(                           \

              obj->queryLocalInterface(                               \

                      I##INTERFACE::descriptor).get());                 \

        if (intr == NULL) {                                          \

              intr = new Bp##INTERFACE(obj);                          \

        }                                                       \

    }                                                           \

    return intr;                                                   \

}

(其上的宏展开都是在IMPLEMENT_META_INTERFACE(CameraService, "android.hardware.ICameraService");中实现的)

此处又创建了一个BpCameraService(new Bp##INTERFACE)对象并将binder对象(obj)传入到BpCameraService的构造函数中。

虽然获取的时候通过多态将CameraService实例转换成了BnCameraService 也进一步解释了为什么ICameraService子类BnCameraservice中的connect函数实质会调用到CameraService中函数实现了。

于是就调用到了CameraService的connect函数接口:

sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) {

…..

// create a new Client object

client = new Client(this, cameraClient, callingPid);

mClient = client;

if (client->mHardware == NULL) {

    client = NULL;

    mClient = NULL;

    return client;

}

创建了一个Client实例对象,并将该实例对象赋值给CameraSevice的类成员mClient,方便其实函数接口对Client的调用。

在这之前需要提及它的一个内部类Client,该类才是最为关键的函数实现,CameraService的一些接口都会调用到其Client实例的具体函数。

3.2.7 Camera服务之Client分析

那么现在的关键就是Client类了。进一步跟进:

CameraService::Client::Client(const sp<CameraService>& cameraService,

    const sp<ICameraClient>& cameraClient, pid_t clientPid) {

…..

mCameraService = cameraService;

mCameraClient = cameraClient;

mClientPid = clientPid;

mHardware = openCameraHardware();

}

将cameraService和cameraClient的实例分别赋值给了Client的类成员变量。

另外openCameraHardware()是值得注意的地方,也就是连接上层应用和底层驱动的关键,通过调用openCameraHardware()得到了一个CameraHardwareInterface实例对象,并赋值给自己的类成员:

sp<CameraHardwareInterface> mHardware;

对hardware的操作就是通过该对象完成的,所以说真正意义上的功能实现其实就是在这里,即client类的函数接口调用。

对于hardware的东东咱们暂时不去关注吧。

那么我们再次仔细研究下Client类的继承关系(这些继承关系很容易混乱,涉及到较多的多态类型转换),这个其实往往都很关键:

图片.png

Client继承于BnCamera,而BnCamera则继承于ICamera,也就是说Client继承了ICamera,实现了ICamera中的函数。

进而发现,原来绕一个大圈,把最开始的图简化下:

图片.png

流程图可以概括如下:

图片.png

3.2.8 Camera服务之Callback分析

先从单一函数去跟进,看具体一些callback的实现流程:

// callback from camera service

void Camera::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2){

sp<CameraListener> listener;

{

    Mutex::Autolock _l(mLock);

    listener = mListener;

}

if (listener != NULL) {

    listener->notify(msgType, ext1, ext2);

}

}

这是Camera类中一个callback函数实现,但其本质在哪?先看camera类的继承关系:

图片.png

通过以上的继承关系,继续跟进其父类ICameraClient:

class ICameraClient: public IInterface{

public:

DECLARE_META_INTERFACE(CameraClient);

virtual void  notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) = 0;

virtual void  dataCallback(int32_t msgType, const sp<IMemory>& data) = 0;

virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& data) = 0;

};

其中notifyCallback()又是纯虚函数,则同样说明实现在其子类BpCameraClient中:

// generic callback from camera service to app

void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {

    LOGV("notifyCallback");

    Parcel data, reply;

    data.writeInterfaceToken(ICameraClient::getInterfaceDescriptor());

    data.writeInt32(msgType);

    data.writeInt32(ext1);

    data.writeInt32(ext2);

  remote()->transact(NOTIFY_CALLBACK,data, &reply, IBinder::FLAG_ONEWAY);

}

然后通过Binder通讯调用到BnCameraClient中实现:

status_t BnCameraClient::onTransact(

uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {

switch(code) {

    case NOTIFY_CALLBACK: {

        LOGV("NOTIFY_CALLBACK");

        CHECK_INTERFACE(ICameraClient, data, reply);

        int32_t msgType = data.readInt32();

        int32_t ext1 = data.readInt32();

        int32_t ext2 = data.readInt32();

        notifyCallback(msgType, ext1, ext2);

        return NO_ERROR;

    } break;

               ….

}

进而调用到了Camera.cpp中的函数实现了,但或许你有疑问,这些callback是涉及到一些驱动的callback,哪怎么跟驱动联系起来那?

结合之前对hardware接口调用的类Client,进一步可以发现callback的处理同样是在Client类实例化的时候:

CameraService::Client::Client(const sp<CameraService>& cameraService,

    const sp<ICameraClient>& cameraClient, pid_t clientPid) {

     …..

     mHardware->setCallbacks(notifyCallback,

                           dataCallback,

                           dataCallbackTimestamp,

                           mCameraService.get());

…..

}

调用了mHardware将callback传入,但此处的notifyCallback并不是camera.cpp中的函数,而是client类的notifyCallback函数。

再继续看client类中的notifyCallback函数实现:

void CameraService::Client::notifyCallback(int32_t msgType

, int32_t ext1,int32_t ext2, void* user){

       …..

    default:

        sp<ICameraClient> c = client->mCameraClient;

        if (c != NULL) {

              c->notifyCallback(msgType, ext1, ext2);

        }

        break;

     …..

}

通过得到ICameraClient实例进而调用到了具体的对象Camera的notifyCallback()函数。这个地方估计会遇见跟ICameraService函数调用一样的问题,ICameraClient函数调用所需要的函数实例在哪?

记得上述ICameraService讲到的connect()函数嘛?其中有一个参数不能被忽略掉的,就是ICameraClient,但它在真正传入的时候却是一个ICameraClient子类camera的实例对象。

CameraService:

sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) {

               …..

     // create a new Client object

         client = new Client(this, cameraClient, callingPid);

               …..

}

Client:

CameraService::Client::Client(const sp<CameraService>& cameraService,

    const sp<ICameraClient>& cameraClient, pid_t clientPid){

….

mCameraService = cameraService;

mCameraClient = cameraClient;

….

}

这样就清楚了,其实Client在调用设置callback的调用最终还是调用到了camera.cpp中的callback函数,进而将具体内容通过callback反馈给上层应用做出相应的处理。

4.Camera 应用层对Framework的封装分析

4.1 应用层对Framework层封装的层次和类介绍

Android 4.0版本以前的camera在是camera 主activity中直接调用camera hal层的的接口(如android.hardware.camera.open(), android.hardware.camera.setPreviewDisplay(), android.hardware.camera..startPreview()等)与camera device通信。Android4.4之后的版本camera app对camera device的访问又封装了一次,对camera device的访问必须经过camera manager接口,camera manager接口定义了camera基本操作,这样便于控制和管理camera device。下图为相关类关系图。

图片.png

1.****CameraManager****接口

提供的访问camera设备基本的操作,实现类必须调用CameraManager.cameraOpen获取CameraManager.CameraProxy接口对象来控制camera,实现CameraManager接口的类必须拥有一个独立于主线程的线程来实现对camera的操作,CameraManager接口也包装回调函数。

2.****CameraProxy****接口

封装在CameraManager接口之内,接收对camera操作请求,发送消息到camer handle。所有对camera的操作都经由改接口,进行异步处理。

3.****AndroidCameraManagerImpl****类

该类实现了CameraManager接口,与camera framework层接口直接通信,起到camera app 与camera framework对话中介的作用。

4.****AndroidCameraProxyImpl****类

AndroidCameraManagerImpl内部类,实现CameraManager.CameraProxy接口,CameraProxy已经介绍过了就是控制对camera的访问,AndroidCameraProxyImpl类实现了具体的操作。

5.****CameraManagerFactory****类

这个类的实现很简单,看到Factory,会想到软件设计中的工厂模式,这里就是封装了创建CameraManager对象的细节。

6.**** CameraHolder****类

看名字就可以看出这个类的基本功能了,就用来保存camera实力对象的,用在不同的module之间快速切换。

4.2 camera的调用流程

时序图如下

图片.png

1.****CameraHolder .****instance()

public static synchronized CameraHolder instance() {

    if (sHolder == null) {

        sHolder = new CameraHolder();

    }

    return sHolder;

}

这个函数定义在Camera/src/com/android/cameraCameraHolder.java中

这个函数很简单就是创建一个CameraHolder实例,并保存在变量sHolder中,如果是第一次调用sHolder肯定是null,接着new 一个CameraHolder实例对象,这样的设计在android中经常见到,一种单例设计模式。现在看看CameraHolder都做了些什么:

private CameraHolder() {

    HandlerThread ht = new HandlerThread("CameraHolder");

    ht.start();

    mHandler = new MyHandler(ht.getLooper());

    if (mMockCameraInfo != null) {

        mNumberOfCameras = mMockCameraInfo.length;

        mInfo = mMockCameraInfo;

    } else {

        mNumberOfCameras = android.hardware.Camera.getNumberOfCameras();

        mInfo = new CameraInfo[mNumberOfCameras];

        for (int i = 0; i < mNumberOfCameras; i++) {

            mInfo[i] = new CameraInfo();

            android.hardware.Camera.getCameraInfo(i, mInfo[i]);

        }

    }

    // get the first (smallest) back and first front camera id

    for (int i = 0; i < mNumberOfCameras; i++) {

        if (mBackCameraId == -1

&& mInfo[i].facing == CameraInfo.CAMERA_FACING_BACK) {

            mBackCameraId = i;

        } else if (mFrontCameraId == -1

&& mInfo[i].facing == CameraInfo.CAMERA_FACING_FRONT) {

            mFrontCameraId = i;

        }

    }

}

CameraHolder对象,首先创建了一个HandlerThread线程,独立于main thread,启动该线程。接着创建自己MyHandler,用于在HandlerThread中处理自己的事物。最后初始化mInfo变量,保存camerainfo。

2.****CameraHolder .open****()

public synchronized CameraProxy open(

        Handler handler, int cameraId,

        CameraManager.CameraOpenErrorCallback cb) {

  …

    Assert(!mCameraOpened);

    if (mCameraDevice != null && mCameraId != cameraId) {

        mCameraDevice.release();

        mCameraDevice = null;

        mCameraId = -1;

    }

    if (mCameraDevice == null) {

        Log.v(TAG, "open camera " + cameraId);

        if (mMockCameraInfo == null) {

            mCameraDevice

= CameraManagerFactory

                    .getAndroidCameraManager()

.cameraOpen(handler, cameraId, cb);

        } else {

            if (mMockCamera != null) {

                mCameraDevice = mMockCamera[cameraId];

            } else {

                mCameraDevice = null;

            }

        }

        if (mCameraDevice == null) {

            Log.e(TAG, "fail to connect Camera:" + mCameraId + ", aborting.");

            return null;

        }

        mCameraId = cameraId;

        mParameters = mCameraDevice.getCamera().getParameters();

    } else {

        if (!mCameraDevice.reconnect(handler, cb)) {

            Log.e(TAG, "fail to reconnect Camera:" + mCameraId + ", aborting.");

            return null;

        }

        mCameraDevice.setParameters(mParameters);

    }

    mCameraOpened = true;

    mHandler.removeMessages(RELEASE_CAMERA);

    mKeepBeforeTime = 0;

    return mCameraDevice;

}

该函数首先判断当前camera是否已经打开,如果已经打开。需要先调用mCameraDevice.release()释放掉,在重新调用CameraManagerFactory获取mCameraDevice实例对象。

3.**** CameraManagerFactory****.**** getAndroidCameraManager ()

Public static synchronized CameraManager getAndroidCameraManager() {

    if (sAndroidCameraManager == null) {

        sAndroidCameraManager = new AndroidCameraManagerImpl();

    }

    return sAndroidCameraManager;

}

该函数定义在Camera/src/com/android/camera/ CameraManagerFactory.java文件中,代码很简单new AndroidCameraManagerImpl对象返回给调用者。

AndroidCameraManagerImpl() {

    HandlerThread ht = new HandlerThread("Camera Handler Thread");

    ht.start();

    mCameraHandler = new CameraHandler(ht.getLooper());

}

从这个类的初始化中可以看到,该对象内部创建了自己的HandlerThread,并启动,这个很重要,后期的client对camera的操作都在这个线程中完成的。后面遇到具体操作我们在分析。

4.**** AndroidCameraManagerImpl****.**** cameraOpen ()

public CameraManager.CameraProxy cameraOpen(

    Handler handler, int cameraId, CameraOpenErrorCallback callback) {

    mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0,

            CameraOpenErrorCallbackForward.getNewInstance(

                    handler, callback)).sendToTarget();

    mCameraHandler.waitDone();

    if (mCamera != null) {

        return new AndroidCameraProxyImpl();

    } else {

        return null;

    }

}

这个函数定义在Camera/src/com/android/camera/ AndroidCameraManagerImpl.java文件中

函数首先通过mCameraHandler获取OPEN_CAMERA消息并发送给mCameraHandler处理,mCameraHandler处理是在AndroidCameraManagerImpl初始化的时候创建的HandlerThread线程中处理的。发送完消息之后调用mCameraHandler.waitDone()阻塞当前主线程,等待framework层的camera开启。

5.**** android.hardware.Camera.open

在上一步发送了OPEN_CAMERA,指令给mCameraHandler。mCameraHandler收到这个指令之后调用framework层camera接口android.hardware.Camera.open开启camera device。当这一步完成之后,主线程会被唤醒,继续执行下一步。

6**** AndroidCameraProxyImpl

当主线程再次被唤醒的时候,判断camera device是否成功开启,如果成功开启,将创建AndroidCameraProxyImpl实例,后续对camera所有的操作的都要经过AndroidCameraProxyImpl统一发送到mCameraHandler进行处理。

5.Camera 应用初始化分析

Camera app提供的主要功能有:预览,拍照,摄影,全景拍摄。

而这些功能统一组织在CameraActivity这个组件中,并细分到各个模块逐步实现。其中涉及的类如下图

图片.png

PhotoModule:负责与拍照相关的部分;

VideoModule:负责与视频录制相关的部分;

WideAnglePanoramaModule:全景照片的拍摄;

接下来我们分析PhotoModule启动过程,其他两个模块类似。

首先给出时序图,结合时序图,分析每一步的实现。

图片.png

1.CameraActivity.onCreate()

当在launcher界面点击camera图标的时候,该函数首先被调用。

public void onCreate(Bundle state) {

setContentView(R.layout.camera_filmstrip);

LayoutInflater inflater = getLayoutInflater();

    View rootLayout = inflater.inflate(R.layout.camera, null, false);

    mCameraModuleRootView

= rootLayout.findViewById(R.id.camera_app_root);

    mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);

    mBottomProgress

= (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);

    mCameraPreviewData = new CameraPreviewData(rootLayout,

            FilmStripView.ImageData.SIZE_FULL,

            FilmStripView.ImageData.SIZE_FULL);

    // Put a CameraPreviewData at the first position.

    mWrappedDataAdapter = new FixedFirstDataAdapter(

            new CameraDataAdapter(new ColorDrawable(

                    getResources().getColor(R.color.photo_placeholder))),

            mCameraPreviewData);

    setModuleFromIndex(moduleIndex);

    mCurrentModule.init(this, mCameraModuleRootView);

},

该函数主要设置activity视图,并根据传过来的intent来判读改启动photomodule还是videomodule,当从launcher启动的时候moduleIndex肯定是0,所以会调用mCurrentModule = new PhotoModule();新建一个PhotoModule对象,该对象就是负责拍照相关的操作。

2.****PhotoModule()

对象创建之后,并没有做实质性的动作,我们接着往下看

3.****mCurrentModule.init(this,mCameraModuleRootView)

mCurrentModule就是PhotoModule类的一个实例,

public void init(CameraActivity activity, View parent) {

    mActivity = activity;

    mRootView = parent;

    mUI = new PhotoUI(activity, this, parent);

    mPreferences = new ComboPreferences(mActivity);

    CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());

    mCameraId = getPreferredCameraId(mPreferences);

    mContentResolver = mActivity.getContentResolver();

    // Surface texture is from camera screen nail and startPreview needs it.

    // This must be done before startPreview.

    mIsImageCaptureIntent = isImageCaptureIntent();

    mPreferences.setLocalId(mActivity, mCameraId);

    CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());

    // we need to reset exposure for the preview

    resetExposureCompensation();

    initializeControlByIntent();

    mQuickCapture

= mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);

    mLocationManager = new LocationManager(mActivity, mUI);

    mSensorManager

= (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));

    brightnessProgressBar = (ProgressBar)mRootView.findViewById(R.id.progress);

    if (brightnessProgressBar instanceof SeekBar) {

        SeekBar seeker = (SeekBar) brightnessProgressBar;

        seeker.setOnSeekBarChangeListener(mSeekListener);

    }

    brightnessProgressBar.setMax(MAXIMUM_BRIGHTNESS);

    brightnessProgressBar.setProgress(mbrightness);

    skinToneSeekBar = (SeekBar) mRootView.findViewById(R.id.skintoneseek);

    skinToneSeekBar.setOnSeekBarChangeListener(mskinToneSeekListener);

    skinToneSeekBar.setVisibility(View.INVISIBLE);

    Title = (TextView)mRootView.findViewById(R.id.skintonetitle);

    RightValue = (TextView)mRootView.findViewById(R.id.skintoneright);

    LeftValue = (TextView)mRootView.findViewById(R.id.skintoneleft);

    Storage.setSaveSDCard(

mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0").equals("1"));

}

刚才介绍过Photomodule是负责camera照相功能的,主要协调界面的显示,参数的配置,方便用户根据自己的爱好来设置相关参数。参数View parent,是拍照界面的根节点, 即在cameraactivity中inflate 的R.layout.camera的camera_app_root视图。Init函数中做了很多重要的工作。创建UI视图,初始化基本参数,

4.****new PhotoUI(activity, this, parent)

视图的真正创建是在PhotoUI对象中,我们接着往下看

public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {

    mActivity = activity;

    mController = controller;

    mRootView = parent;

    mActivity.getLayoutInflater().inflate(R.layout.photo_module,

            (ViewGroup) mRootView, true);

    mRenderOverlay

= (RenderOverlay) mRootView.findViewById(R.id.render_overlay);

    mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);

    mPreviewCover = mRootView.findViewById(R.id.preview_cover);

    // display the view

    mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);

    mTextureView.setSurfaceTextureListener(this);

    mTextureView.addOnLayoutChangeListener(mLayoutListener);

    initIndicators();

    mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);

    mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);

    mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX);

    mSwitcher.setSwitchListener(mActivity);

    mMenuButton = mRootView.findViewById(R.id.menu);

    ViewStub faceViewStub = (ViewStub) mRootView

            .findViewById(R.id.face_view_stub);

    if (faceViewStub != null) {

        faceViewStub.inflate();

        mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);

        setSurfaceTextureSizeChangedListener(mFaceView);

    }

    mCameraControls

= (CameraControls) mRootView.findViewById(R.id.camera_controls);

    mAnimationManager = new AnimationManager();

    mOrientationResize = false;

    mPrevOrientationResize = false;

}

函数首先inflate R.layout.photo_module视图,挂在mRootView节点下,mRootView即是R.layout.camera中的

camera_app_root视图,他是photomodule的根视图。photo_module这个视图包括了其他的一些重要视图,其中比较重要的几个mTextureView,mShutterButton,mSwitcher,faceViewStub,mCameraControls,mMenuButton等

mTextureView:是TextureView控件,和surfaceview功能差不多,只是实现机制不同,都是用来显示流媒体的。Camera preview数据就在此显示

mCameraControls:是个容器,包裹mShutterButton,mSwitcher,PieMenuButton,等控件

mShutterButton:拍照按钮,mSwitcher:模式切换,faceViewStub脸部扑捉。mMenuButton:设置参数相关的

5.cameraactivity.onresume()

界面和参数的初始化工作完成之后,camera activity进去resume状态,

public void onResume() {

 …

    mOrientationListener.enable();

    mCurrentModule.onResumeBeforeSuper();

    super.onResume();

    mCurrentModule.onResumeAfterSuper();

    setSwipingEnabled(true);

 ….

}

这里掉用了一个重要的函数mCurrentModule.onResumeAfterSuper();到目前为止我们还没有看到camera设备被打开的过程,什么原因,以前在oncreate的时候就打开了camera设备,难道是我们在前面分析的时候漏掉了,客官别急,切往下看。

6.****mCurrentModule.onResumeAfterSuper()

调用这个函数之后,又进入到photomodule对象中了,我们来看一下具体实现

public void onResumeAfterSuper() {

String action = mActivity.getIntent().getAction();

if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)

     || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)){

    Log.v(TAG, "On resume, from lock screen.");

    mHandler.postDelayed(new Runnable() {

        public void run() {

            onResumeTasks();

        }

    }, ON_RESUME_TASKS_DELAY_MSEC);

} else {

    Log.v(TAG, "On resume.");

    onResumeTasks();

}

mHandler.post(new Runnable(){

    @Override

    public void run(){

        mActivity.updateStorageSpaceAndHint();

    }

});

}

函数先根据action类型做不同的处理,最终都会调用onResumeTasks函数。

7.****onResumeTasks****()

private void onResumeTasks() {

    if (mOpenCameraFail || mCameraDisabled)

return;

    mJpegPictureCallbackTime = 0;

    mZoomValue = 0;

    resetExposureCompensation();

    if (!prepareCamera()) {

        // Camera failure.

        return;

    }

函数接着调用prepareCamera进一步启动camera设备

8.****prepareCamera()

private boolean prepareCamera() {

    mCameraDevice = CameraUtil.openCamera(

            mActivity, mCameraId, mHandler,

            mActivity.getCameraOpenErrorCallback());

    if (mCameraDevice == null) {

        Log.e(TAG, "Failed to open camera:" + mCameraId);

        return false;

    }

    mParameters = mCameraDevice.getParameters();

    initializeCapabilities();

    if (mFocusManager == null) initializeFocusManager();

    setCameraParameters(UPDATE_PARAM_ALL);

    mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);

    mCameraPreviewParamsReady = true;

    startPreview();

    mOnResumeTime = SystemClock.uptimeMillis();

    checkDisplayRotation();

    return true;

}

这个是很重要的函数,函数先调用CameraUtil.openCamera

来获取camera设备,CameraUtil.openCamera我们在上一篇文章中介绍过了,这里不做介绍了。获取到camera设备后,接着设置camera初始化参数,最后调用startPreview();启动预览。在启动预览的时候mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);向mHandler发送了一个CAMERA_OPEN_DONE消息。Camera setting的部分测初始化是在这个阶段完成的。

9.**** onCameraOpened

private void onCameraOpened() {

    View root = mUI.getRootView();

    int width = root.getWidth();

    int height = root.getHeight();

    mFocusManager.setPreviewSize(width, height);

    openCameraCommon();

    resizeForPreviewAspectRatio();

}

该函数还是比较简单的,设置mFocusManager区域大小,调用openCameraCommon进一步操作camera

10.****openCameraCommon()

private void openCameraCommon() {

    loadCameraPreferences();

    mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);

    if (mIsImageCaptureIntent) {

        mUI.overrideSettings(CameraSettings.KEY_CAMERA_HDR_PLUS,

                mActivity.getString(R.string.setting_off_value));

    }

    updateCameraSettings();

    showTapToFocusToastIfNeeded();

}

函数首先调用loadCameraPreferences函数,来加载默认初始数据R.xml.camera_preferences,将这些初始数据加载到内存中,最终保存在mPreferenceGroup变量中,被后面的UI空间使用。

11.****mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this)

加载完初始数据之后,下一步便是创建必要的UI控件

public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,

        Camera.Parameters params, OnPreferenceChangedListener listener) {

    if (mPieRenderer == null) {

        mPieRenderer = new PieRenderer(mActivity);

        mPieRenderer.setPieListener(this);

        mRenderOverlay.addRenderer(mPieRenderer);

    }

    if (mMenu == null) {

        mMenu = new PhotoMenu(mActivity, this, mPieRenderer);

        mMenu.setListener(listener);

    }

    mMenu.initialize(prefGroup);

    mMenuInitialized = true;

    if (mZoomRenderer == null) {

        mZoomRenderer = new ZoomRenderer(mActivity);

        mRenderOverlay.addRenderer(mZoomRenderer);

    }

    if (mGestures == null) {

        // this will handle gesture disambiguation and dispatching

        mGestures

= new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);

        mRenderOverlay.setGestures(mGestures);

    }

    mGestures.setZoomEnabled(params.isZoomSupported());

    mGestures.setRenderOverlay(mRenderOverlay);

    mRenderOverlay.requestLayout();

    initializeZoom(params);

    updateOnScreenIndicators(params, prefGroup, prefs);

}

PhotoMenu这对象控制在屏幕上出现的各个小按钮.视图的大体层次见下图

图片.png

6.Camera UI介绍

6.1 Camera UI总体预览

UI界面布局及主要控件说明:

图片.png

6.2 ModePicker类

显示支持的feature

1.Feature table中配置:

(1).参考FAQ:FAQ05993如何配置MT658X Camera的feature table

(2).配置KEY_CAPTURE_MODE

图片.png

只有featuretable跟Camera AP都有配置的feature,才会在UI上显示。可以通过LOG确认feature table中配置的feature,抓取进入Camera的main log,搜索【showParameters】,在打印出来的字串中找到cap-mode-values.

2.Camera AP配置

ModeCheck中,针对前后摄像头配置支持的feature.

图片.png

只有featuretable跟Camera AP都有配置的feature,才会在UI上显示。

6.3 IndicatorManager类

该类主要负责显示awb,scene mode,exposure,timelapse,selftimer,Voice的indicator等。当awb,scene mode,exposure,timelapse,selftimer,不是default值时,会显示indicator;声控拍照功能打开时,会显示indicator。

6.4 RemainingManager类

该类负责存储卡相关信息的提示,包含剩余拍照张数剩余录像时长,SD卡状态(已满,没有SD卡等)。

1.剩余拍照张数的计算以及显示

在storage.java中,有定义一个PICTURE_SIZE_TABLE,该table中存储不同拍照size以及不同quality,一张照片可能会占用的空间。根据SD卡的剩余空间,LOW_STORAGE_THRESHOLD,以及该估算值计算出剩余拍照张数.

2.剩余录像时长的计算

根据所录制Video的VideoBitrate以及audioBitrate,SD卡剩余空间,LOW_STORAGE_THRESHOLD计算而来.计算方法可以参考RemainingManager的computeStorage方法。

6.5 PickerManager类

该类主要包含开启/关闭HDR,开启/关闭smile shot,SwitchCamera,Flash Mode的选择。具体响应流程如下:

图片.png

6.6 ThumbnailManager类

该类主要用于缩略图的显示更新,包含从launcher进Camera,在图库中对图片进行操作之后返回Camera,拍照录像之后更新缩略图。具体流程:

1.进Camera显示thumbnail,如果data/data/com.android.gallery3d/files/last_thumb存在,会从该文件中decode thumbnail。如果该文件不存在,会从mediaprovider通过调用getLastThumbFromContentResolver获取。如下图:

图片.png

进入图库编辑照片(比如删除)之后,回到camera界面,thumbnail的显示流程,通过发送ACTION_UPDATE_PICTURE的广播,在thumbnailmanager中监听此广播,更新thumbnail。如下图:

图片.png

拍照可录像之后thumbnail的保存更新,如下图:

图片.png

SaveRequest包含所有要保存到数据库或SD卡的数据单元;

FileSaver管理保存业务,命名文件,保存文件信息到数据库或SD卡,创建Thumbnail,通知监听;

ThumbnailManager用于在文件保存后更新Thumbnail;

Camera在prepareSaveRequest()设置全局参数到SaveRequest;

Actor通过调用Camera. prepareSaveRequest()得到SaveRequest,保存数值到SaveRequest,通过调用SaveRequest.addRequest添加request到保存队列。

6.7 ShutterManager类

该类包含拍照、录像、确定、取消等按钮的管理。

public static final int SHUTTER_TYPE_PHOTO_VIDEO = 0;

点击camera图标进入camera,不是通过彩信或其它应用调用拍照或录像;

public static final int SHUTTER_TYPE_PHOTO = 1;

通过Image Capture Intent调用Camera拍照;

public static final int SHUTTER_TYPE_VIDEO = 2;

通过Video Capture Intent调用Camera录像;

public static final int SHUTTER_TYPE_OK_CANCEL = 3;

通过Image Capture Intent/Video Capture Intent调用Camera 拍照或录像之后,全景拍照过程中;

public static final int SHUTTER_TYPE_CANCEL = 4;

MAV拍照过程中;

public static final int SHUTTER_TYPE_CANCEL_VIDEO = 5;

选择Video Effect之后;

6.8 SettingManager类

负责设置菜单的显示、隐藏点击某一项的响应。

图片.png

点击Item的响应流程:

图片.png

6.9 FocusManager类

该类负责touch focus的处理,根据touch的点,计算touch area以及metering area,做AE以及AF;根据对焦状态,显示对焦框:idle,focusing,success,fail,focusing_snap_on_finfish。

可以客制化为点击屏幕拍照,点击ShutterButton键对焦完成之后拍照等。

Touch focus流程:

1.响应onSingleTapUp;

2.计算FocusArea以及MeteringArea;

    calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,

            mFocusArea.get(0).rect);

    calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,

            mMeteringArea.get(0).rect);

3.stopFaceDetection();

4.mCamera.getCameraDevice().autoFocus(mAutoFocusCallback);

5. mAutoFocusCallback中返回对焦结果,更新focusUI。

7.Camera 预览流程

7.1 Camera预览命令流程

图片.png

7.1.1 App层流程

打开Camera第一件事情就是预览取景preview的动作,我们先从Camera app分析起 。

packages/apps/Camera/src/com/android/camera/PhotoModule.java

private void startPreview() {

mCameraDevice.setPreviewDisplayAsync(mCameraSurfaceHolder);

mCameraDevice.startPreviewAsync();

}

packages/apps/Camera/src/com/android/camera/CameraManager.java

public void setPreviewDisplayAsync(final SurfaceHolder surfaceHolder) {

mCameraHandler.obtainMessage(SET_PREVIEW_DISPLAY_ASYNC

, surfaceHolder).sendToTarget();

}

public void startPreviewAsync() {

mCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC);

}

public void handleMessage(final Message msg) {

switch (msg.what) {

case SET_PREVIEW_DISPLAY_ASYNC:  

  try {  

     mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);  

  } catch(IOException e) {  

      throw new RuntimeException(e);  

  }  

  return;  // no need to call mSig.open()  

case START_PREVIEW_ASYNC:  

  mCamera.startPreview();  

  return;  // no need to call mSig.open()  

}

}

7.1.2 Framework层流程

frameworks/base/core/java/android/hardware/Camera.java

public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {

setPreviewDisplay(holder.getSurface());  

}

public native final void startPreview();

frameworks/base/core/jni/android_hardware_Camera.cpp

static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env

, jobject thiz, jobject jSurface){

sp<Camera> camera = get_native_camera(env, thiz, NULL);

if (camera->setPreviewDisplay(surface) != NO_ERROR) {

  jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed");  

}

}

static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz){

sp<Camera> camera = get_native_camera(env, thiz, NULL);

if (camera->startPreview() != NO_ERROR) {

jniThrowRuntimeException(env, "startPreview failed");  

return;  

}

}

frameworks/av/camera/Camera.cpp

status_t Camera::setPreviewDisplay(const sp<Surface>& surface){

sp <ICamera> c = mCamera;

return c->setPreviewDisplay(surface);

}

status_t Camera::startPreview(){

sp <ICamera> c = mCamera;

return c->startPreview();

}

7.1.3 Binder调用

frameworks/av/services/camera/libcameraservice/CameraClient.cpp

status_t CameraClient::setPreviewDisplay(const sp<Surface>& surface) {

return setPreviewWindow(binder, window);  

}

status_t CameraClient::startPreview() {

return startCameraMode(CAMERA_PREVIEW_MODE);  

}

status_t CameraClient::startCameraMode(camera_mode mode) {

switch(mode) {

case CAMERA_PREVIEW_MODE:  

  return startPreviewMode();  

}

}

status_t CameraClient::startPreviewMode() {

mHardware->setPreviewWindow(mPreviewWindow);

result = mHardware->startPreview();

}

frameworks/av/services/camera/libcameraservice/CameraHardwareInterface.h

status_t setPreviewWindow(const sp<ANativeWindow>& buf){

mPreviewWindow = buf;

return mDevice->ops->set_preview_window(mDevice,

                buf.get() ? &mHalPreviewWindow.nw : 0);  

}

status_t startPreview(){

return mDevice->ops->start_preview(mDevice);

}

7.1.4 HAL层流程

hardware/amlogic/camera/CameraHal_Module.cpp

int camera_set_preview_window(struct camera_device * device,

    struct preview_stream_ops *window){  

rv = gCameraHals[aml_dev->cameraid]->setPreviewWindow(window);  

}

int camera_start_preview(struct camera_device * device){

rv = gCameraHals[aml_dev->cameraid]->startPreview();  

}

hardware/amlogic/camera/CameraHal.cpp

status_t CameraHal::setPreviewWindow(struct preview_stream_ops *window){

mDisplayAdapter = new ANativeWindowDisplayAdapter();  

mDisplayAdapter->setFrameProvider(mCameraAdapter); //将frameCallbackRelay设置为BaseCameraAdapter的回调函数

mDisplayAdapter->setErrorHandler(mAppCallbackNotifier.get()); //设置BaseCameraAdapter错误处理函数

ret = mDisplayAdapter->setPreviewWindow(window);//设置post的Surface

}

status_t CameraHal::startPreview(){

ret = allocPreviewBufs(mPreviewWidth, mPreviewHeight

, mParameters.getPreviewFormat(), required_buffer_count, max_queueble_buffers);

}

7.2 数据流程

图片.png

hardware/amlogic/camera/V4LCameraAdapter/V4LCameraAdapter.cpp

int V4LCameraAdapter::previewThread(){

yuyv422_to_nv21(src,dest,width,height);

frame.mFrameMask |= CameraFrame::PREVIEW_FRAME_SYNC;

frame.mPixelFmt = mPixelFormat;

ret = sendFrameToSubscribers(&frame);

}

hardware/amlogic/camera/BaseCameraAdapter.cpp

status_t BaseCameraAdapter::sendFrameToSubscribers(CameraFrame *frame){

ret = __sendFrameToSubscribers(frame, &mFrameSubscribers

, CameraFrame::PREVIEW_FRAME_SYNC);

}

status_t BaseCameraAdapter::__sendFrameToSubscribers(CameraFrame* frame,

                                                 KeyedVector<int, frame_callback> *subscribers,  

                                                 CameraFrame::FrameType frameType){  

callback = (frame_callback) subscribers->valueAt(k);

callback(frame);

}

hardware/amlogic/camera/ANativeWindowDisplayAdapter.cpp

void ANativeWindowDisplayAdapter::frameCallbackRelay(CameraFrame* caFrame){

ANativeWindowDisplayAdapter *da

= (ANativeWindowDisplayAdapter*) caFrame->mCookie;

da->frameCallback(caFrame);

}

void ANativeWindowDisplayAdapter::frameCallback(CameraFrame* caFrame){

PostFrame(df);

}

status_t ANativeWindowDisplayAdapter

::PostFrame(ANativeWindowDisplayAdapter::DisplayFrame &dispFrame){

mANativeWindow->set_crop(mANativeWindow, xOff/bytesPerPixel, yOff,

                                 (xOff/bytesPerPixel)+mPreviewWidth, yOff+mPreviewHeight);  

ret = mANativeWindow->enqueue_buffer(mANativeWindow, mBufferHandleMap[i]);

}

frameworks/av/services/camera/libcameraservice/CameraHardwareInterface.h

static int __set_crop(struct preview_stream_ops *w,

                   int left, int top, int right, int bottom){  

ANativeWindow *a = anw(w);

return native_window_set_crop(a, &crop);

}

static int __enqueue_buffer(struct preview_stream_ops* w,

                   buffer_handle_t* buffer){  

ANativeWindow *a = anw(w);

return a->queueBuffer(a,

               container_of(buffer, ANativeWindowBuffer, handle), -1);  

}

7.3 函数调用流程

图片.png

Camera在预览时候都要实现SurfaceHolder.Callback接口,并实现其surfaceCreated、 surfaceChanged、surfaceDestroyed三个函数,同时声明一个用于预览的窗口SurfaceView ,以下是APP的源代码

SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);

SurfaceHolder holder = preview.getHolder();

holder.addCallback(this);

设置camera预览的surface缓存区 ,在surfaceChange()方法里面设置Camera的预览区,以供底层获取的preview数据不断投递到这个surface缓存区内。

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

mSurfaceHolder = holder;

if (mCameraDevice == null) return;

if (mPausing || isFinishing()) return;

if (mCameraState == PREVIEW_STOPPED) {

        startPreview();

        startFaceDetection();

} else {

if (Util.getDisplayRotation(this) != mDisplayRotation) {

         setDisplayOrientation();

}

        if (holder.isCreating()) {

setPreviewDisplay(holder);

}

}

设置好以上参数后,就可以调用startPreview()进行取景预览。startPreview()也是一层层往下调用,最后到Camera的服务端CameraService,我们看下它的过程

Camera.java(应用)

------------->Camera.java(框 架)

------------->android_hardware_camera.cpp(JNI)

------------->Camera.cpp(客 户端)

------------->CameraService.cpp(服务 端)

------------->CameraHarwareInterface(HAL接口)

在CameraService端将处理preview的请求并进入HAL层

status_t CameraService::Client::startPreview() {

enableMsgType(CAMERA_MSG_PREVIEW_METADATA);

return startCameraMode(CAMERA_PREVIEW_MODE);

}

先是传递preview的消息到HAL层,然后执行preview

status_t CameraService::Client::startCameraMode(camera_mode mode) {

switch(mode) {

case CAMERA_PREVIEW_MODE:

if (mSurface == 0 && mPreviewWindow == 0) {

LOG1("mSurface is not set yet.");

     // still able to start preview in this case.

}

return startPreviewMode();

}

}

status_t CameraService::Client::startPreviewMode() {

LOG1("startPreviewMode");

status_t result = NO_ERROR;

// if preview has been enabled, nothing needs to be done

if (mHardware->previewEnabled()) {

return NO_ERROR;

}

if (mPreviewWindow != 0) {

native_window_set_scaling_mode(mPreviewWindow.get(),

NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);

native_window_set_buffers_transform(mPreviewWindow.get(),

mOrientation);

}

mHardware->setPreviewWindow(mPreviewWindow);

result = mHardware->startPreview();

return result;

}

然后就近去HAL层调用,并通过回调函数源源不断的将数据投递到 surfaceview的缓存去,因为preview的数据是比较大的,所以数据不会携带着传上上层,而是直接在两个缓存区之间copy,一个是底层采集 数据的缓存区,另一个是用于显示的surfaceview缓存区。

我们看看preview的回调函数是怎么处理的。首先在Camera客户端与服务端连接成功的时候就会设置一个回调函数dataCallBack

CameraService::Client::Client(const sp& cameraService,

const sp& cameraClient,

const sp& hardware,

int cameraId, int cameraFacing, int clientPid) {

......

mHardware->setCallbacks(notifyCallback,

dataCallback,

dataCallbackTimestamp,

(void *)cameraId);

}

在上篇有介绍到,client与server连接成功后就会new 一个client返回,在client的构造函数中,就对camera设置了notifyCallback、dataCallback、 dataCallbackTimestamp三个回调函数,用于返回底层数据用于处理,看下它的处理方法:

void CameraService::Client::dataCallback(int32_t msgType,

const sp& dataPtr, camera_frame_metadata_t metadata, void user) {

switch (msgType & ~CAMERA_MSG_PREVIEW_METADATA) {

case CAMERA_MSG_PREVIEW_FRAME:

client->handlePreviewData(msgType, dataPtr, metadata);

break;

.......

}

继续看handlePreviewData():

void CameraService::Client::handlePreviewData(int32_t msgType,

const sp& mem,

camera_frame_metadata_t *metadata) {

sp c = mCameraClient;

.......

if (c != 0) {

// Is the received frame copied out or not?

if (flags & CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK) {

LOG2("frame is copied");

copyFrameAndPostCopiedFrame(msgType, c, heap, offset, size, metadata);

} else {

LOG2("frame is forwarded");

mLock.unlock();

c->dataCallback(msgType, mem, metadata);

}

} else {

mLock.unlock();

}

}

copyFrameAndPostCopiedFrame就是这个函数执行两个buff区preview数据的投递

void CameraService::Client::copyFrameAndPostCopiedFrame(

    int32_t msgType, const sp& client,

    const sp& heap, size_t offset, size_t size,

    camera_frame_metadata_t *metadata) {

......

previewBuffer = mPreviewBuffer;

memcpy(previewBuffer->base(), (uint8_t *)heap->base() + offset, size);

sp frame = new MemoryBase(previewBuffer, 0, size);

if (frame == 0) {

    LOGE("failed to allocate space for frame callback");

    mLock.unlock();

    return;

   }

mLock.unlock();

client->dataCallback(msgType, frame, metadata);

}

将数据处理成frame,继续调用客户端client的回调函数:

client->dataCallback(msgType, frame, metadata);

// callback from camera service when frame or image is ready

void Camera::dataCallback(int32_t msgType, const sp& dataPtr,

camera_frame_metadata_t *metadata)

{

sp listener;

{

    Mutex::Autolock _l(mLock);

    listener = mListener;

}

if (listener != NULL) {

    listener->postData(msgType, dataPtr, metadata);

}

}

初始化的时候,在jni里面有设置listener

static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,

jobject weak_this, jint cameraId)

{

sp context = new JNICameraContext(env, weak_this, clazz, camera);

context->incStrong(thiz);

camera->setListener(context);

}

继续 listener->postData(msgType, dataPtr, metadata);

void JNICameraContext::postData(int32_t msgType, const sp& dataPtr,

                            camera_frame_metadata_t *metadata)

{

......

 switch (dataMsgType) {

    case CAMERA_MSG_VIDEO_FRAME:

        // should never happen

        break;

      default:

        LOGV("dataCallback(%d, %p)", dataMsgType, dataPtr.get());

        copyAndPost(env, dataPtr, dataMsgType);

        break;

}

}

继续copyAndPost(env, dataPtr, dataMsgType);

void JNICameraContext::copyAndPost(JNIEnv* env, const sp& dataPtr, int msgType)

{

jbyteArray obj = NULL;

// allocate Java byte array and copy data

if (dataPtr != NULL) {

.......

        } else {

            LOGV("Allocating callback buffer");

            obj = env->NewByteArray(size);

    .......

            env->SetByteArrayRegion(obj, 0, size, data);

        }

    } else {

LOGE("image heap is NULL");

    }

}

// post image data to Java

env->CallStaticVoidMethod(mCameraJClass, fields.post_event,

        mCameraJObjectWeak, msgType, 0, 0, obj);

if (obj) {

env->DeleteLocalRef(obj);

}

}

解释一下标红的部分,先建立一个byte数组obj,将data缓存数据存储进obj数组,CallStaticVoidMethod是C调用java函数,最后执行实在Camera.java(框架)的postEventFromNative()

private static void postEventFromNative(Object camera_ref,

                                        int what, int arg1, int arg2, Object obj)

{

    Camera c = (Camera)((WeakReference)camera_ref).get();

    if (c == null)

return;

    if (c.mEventHandler != null) {

        Message m = c.mEventHandler.obtainMessage(what, arg1, arg2, obj);

         c.mEventHandler.sendMessage(m);

    }

}

还是handler处理地

public void handleMessage(Message msg) {

        switch(msg.what) {

         case CAMERA_MSG_SHUTTER:

            if (mShutterCallback != null) {

                mShutterCallback.onShutter();

            }

            return;

        case CAMERA_MSG_RAW_IMAGE:

            if (mRawImageCallback != null) {

                mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return;

        case CAMERA_MSG_COMPRESSED_IMAGE:

            if (mJpegCallback != null) {

                mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return;

         case CAMERA_MSG_PREVIEW_FRAME:

            if (mPreviewCallback != null) {

                PreviewCallback cb = mPreviewCallback;

                if (mOneShot) {

                    mPreviewCallback = null;

                } else if (!mWithBuffer) {

                      setHasPreviewCallback(true, false);

                }

                cb.onPreviewFrame((byte[])msg.obj, mCamera);

            }

            return;

         }

    }

}

上面可以看出,这里处理了所有的回调,快门回调 mShutterCallback.onShutter(),RawImageCallback.onPictureTaken()拍照数据回调,自动对 焦回调等。。默认是没有previewcallback这个回调的,除非你的app设置了setPreviewCallback,可以看出preview 的数据还是可以向上层回调,只是系统默认不回调,另数据采集区与显示区两个缓存区buffer preview数据的投递,以完成preview实时显示是在HAL层完成的。

8.Camera拍照流程

8.1 CameraService初始化过程

frameworks\base\media\mediaserver\Main_MediaServer.cpp

CameraService在MediaServer中初始化,代码是MediaServer的main函数,在该函数中初始化照相机服务。

CameraService中的instantiate方法用来创建CameraService实例,并进行相应的初始化,这个函数定义在它的父类BinderService中:frameworks/base/include/binder/BinderService.

相机服务的初始化过程首先是创建CameraService实例,然后将其注册到ServiceManager中,关于它的启动是发生在init.rc中,通过media_server来启动CameraService,具体代码如下:

system/core/rootdir/init.rc

service servicemanager /system/bin/servicemanager

class core

user system

group system

critical

onrestart restart zygote

onrestart restart media

onrestart restart surfaceflinger

onrestart restart drm

service media /system/bin/mediaserver

class main

user media

group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc

ioprio rt 4

在cameraService注册以及启动过程中cameraService自身会执行一些初始化的工作,主要涉及到如下工作

frameworks/base/services/camera/libcameraservice/CameraService.cpp

CameraService::CameraService()

:mSoundRef(0), mModule(0)

{

LOGI("CameraService started (pid=%d)", getpid());

gCameraService = this;

}

void CameraService::onFirstRef()

{

BnCameraService::onFirstRef();

if (hw_get_module(CAMERA_HARDWARE_MODULE_ID,

            (const hw_module_t **)&mModule) < 0) {

    LOGE("Could not load camera HAL module");

    mNumberOfCameras = 0;

}

else {

    mNumberOfCameras = mModule->get_number_of_cameras();

    if (mNumberOfCameras > MAX_CAMERAS) {

        LOGE("Number of cameras(%d) > MAX_CAMERAS(%d).",

                mNumberOfCameras, MAX_CAMERAS);

        mNumberOfCameras = MAX_CAMERAS;

    }

    for (int i = 0; i < mNumberOfCameras; i++) {

        setCameraFree(i);

    }

}

}

在上述初始化代码中,先通过调用HAL硬件抽象层库load camera HAL module,获取支持的摄像头个数并保存到mNumberOfCameras。

8.2应用程序链接相机服务过程

8.2.1 Camera连接整个过程

在camera应用程序启动的时候首先会和CameraService建立连接,camera应用程序代码就不分析了,下面是一个简单的流程图。

图片.png

从上面的流程图我们可以看出在请求服务的过程中多出用到了应用框架层的camera类,该类定义在 frameworks/base/core/java/android/hardware/Camera.java文件当中,这一个类正是Camera子系统中APP层和JNI层交换的接口,它对上为应用程序提供了各种操作Camera的方法,向下访问JNI实现它自身的接口Camera类定义如下:

public class Camera {

public static Camera open(int cameraId) {

      return new Camera(cameraId);

}

.................

Camera(int cameraId) {

Looper looper;

if ((looper = Looper.myLooper()) != null) {

    mEventHandler = new EventHandler(this, looper);

} else if ((looper = Looper.getMainLooper()) != null) {

    mEventHandler = new EventHandler(this, looper);

} else {

    mEventHandler = null;

}

native_setup(new WeakReference<Camera>(this), cameraId);

}

}

8.2.2 Camera takepicture过程

下面就开始从app的takepicture逐步分析camera takepicture整个过程。在app中,takepicture是在capture方法中被调用的

packages/apps/OMAPCamera/src/com/ti/omap4/android/camera/Camera.java

@Override

public boolean capture() {

    synchronized (mCameraStateLock) {

        // If we are already in the middle of taking a snapshot then ignore.

        if (mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {

              return false;

        }

        mCaptureStartTime = System.currentTimeMillis();

        mPostViewPictureCallbackTime = 0;

        mJpegImageData = null;

        // Set rotation and gps data.

        Util.setRotationParameter(mParameters, mCameraId, mOrientation);

        Location loc = mLocationManager.getCurrentLocation();

        Util.setGpsParameters(mParameters, loc);

        if (canSetParameters()) {

              mCameraDevice.setParameters(mParameters);

        }

        try {

              mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,

                    mPostViewPictureCallback, new JpegPictureCallback(loc));

        } catch (RuntimeException e ) {

            e.printStackTrace();

            return false;

        }

        mFaceDetectionStarted = false;

        setCameraState(SNAPSHOT_IN_PROGRESS);

        return true;

    }

}

这里调用的takePicture是在framework层中定义:

frameworks/base/core/java/android/hardware/Camera.java

public final void takePicture(ShutterCallback shutter, PictureCallback raw,

        PictureCallback postview, PictureCallback jpeg) {

    mShutterCallback = shutter;

    mRawImageCallback = raw;

    mPostviewCallback = postview;

    mJpegCallback = jpeg;

    // If callback is not set, do not send me callbacks.

    int msgType = 0;

    if (mShutterCallback != null) {

          msgType |= CAMERA_MSG_SHUTTER;

    }

    if (mRawImageCallback != null) {

          msgType |= CAMERA_MSG_RAW_IMAGE;

    }

    if (mPostviewCallback != null) {

          msgType |= CAMERA_MSG_POSTVIEW_FRAME;

    }

    if (mJpegCallback != null) {

          msgType |= CAMERA_MSG_COMPRESSED_IMAGE;

    }

    native_takePicture(msgType);

}

在这里设置callback函数,并调用通过JNI调用takepicture方法:

frameworks/base/core/jni/android_hardware_Camera.cpp

static void android_hardware_Camera_takePicture(JNIEnv *env, jobject thiz, int msgType)

{

LOGV("takePicture");

JNICameraContext* context;

sp<Camera> camera = get_native_camera(env, thiz, &context);

if (camera == 0) return;

if (msgType & CAMERA_MSG_RAW_IMAGE) {

    LOGV("Enable raw image callback buffer");

    if (!context->isRawImageCallbackBufferAvailable()) {

        LOGV("Enable raw image notification, since no callback buffer exists");

        msgType &= ~CAMERA_MSG_RAW_IMAGE;

        msgType |= CAMERA_MSG_RAW_IMAGE_NOTIFY;

    }

}

if (camera->takePicture(msgType) != NO_ERROR) {

    jniThrowRuntimeException(env, "takePicture failed");

    return;

}

}

这里调用camera 的takepicture,即camera client 的takepicture方法:

frameworks/base/libs/camera/Camera.cpp

status_t Camera::takePicture(int msgType, const String8& params)

{

LOGV("takePicture: 0x%x", msgType);

sp <ICamera> c = mCamera;

if (c == 0) return NO_INIT;

return c->takePicture(msgType, params);

}

这里client 端的takepicture又调用到camera server端的takepicture:

frameworks/base/services/camera/libcameraservice/CameraService.cpp

// take a picture - image is returned in callback

ifdef OMAP_ENHANCEMENT_CPCAM

status_t CameraService::Client::takePicture(int msgType, const String8& params) {

else

status_t CameraService::Client::takePicture(int msgType) {

endif

LOG1("takePicture (pid %d): 0x%x", getCallingPid(), msgType);

Mutex::Autolock lock(mLock);

status_t result = checkPidAndHardware();

if (result != NO_ERROR) return result;

if ((msgType & CAMERA_MSG_RAW_IMAGE) &&

    (msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {

    LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"

            " cannot be both enabled");

    return BAD_VALUE;

}

// We only accept picture related message types

// and ignore other types of messages for takePicture().

int picMsgType = msgType

                    & (CAMERA_MSG_SHUTTER |

                       CAMERA_MSG_POSTVIEW_FRAME |

                       CAMERA_MSG_RAW_IMAGE |

ifdef OMAP_ENHANCEMENT

                       CAMERA_MSG_RAW_BURST |

endif

                       CAMERA_MSG_RAW_IMAGE_NOTIFY |

                       CAMERA_MSG_COMPRESSED_IMAGE);

ifdef OMAP_ENHANCEMENT

picMsgType |= CAMERA_MSG_COMPRESSED_BURST_IMAGE;

endif

enableMsgType(picMsgType);

ifdef OMAP_ENHANCEMENT

// make sure the other capture messages are disabled

picMsgType = ~picMsgType &

             (CAMERA_MSG_SHUTTER |

              CAMERA_MSG_POSTVIEW_FRAME |

              CAMERA_MSG_RAW_IMAGE |

              CAMERA_MSG_RAW_BURST |

              CAMERA_MSG_RAW_IMAGE_NOTIFY |

              CAMERA_MSG_COMPRESSED_IMAGE |

              CAMERA_MSG_COMPRESSED_BURST_IMAGE);

disableMsgType(picMsgType);

endif

ifdef OMAP_ENHANCEMENT_CPCAM

return mHardware->takePicture(params);

else

return mHardware->takePicture();

endif

}

一些初始化之后,最终server端的takepicture方法会调用HAL层(硬件接口层)的takepicture方法:

frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h

ifdef OMAP_ENHANCEMENT_CPCAM

status_t takePicture(const ShotParameters ¶ms)

{

    LOGV("%s(%s)", __FUNCTION__, mName.string());

    if (mDevice->ops->take_picture)

        return mDevice->ops->take_picture(mDevice,

                                          params.flatten().string());

    return INVALID_OPERATION;

}

else

status_t takePicture()

{

    LOGV("%s(%s)", __FUNCTION__, mName.string());

    if (mDevice->ops->take_picture)

        return mDevice->ops->take_picture(mDevice);

    return INVALID_OPERATION;

}

endif

下面的重点是分析数据回调过程,这个过程是camera的最重点,在我看来也是最难点理解的地方,要多花点时间,努把力了,现在就开始

首先还是必须先追溯到Camera客户端与服务端连接的时候,由我的上一遍初始化的文章知道,Camera客户端与服务端连接的时候,首先调用的是client端的connect方法,client的connect方法首先getservice,然后调用server端的connect方法,为了方便理解我再次把这部分代码贴出:server的connect()函数定义在以下路径:

frameworks/base/services/camera/libcameraservice/CameraService.cpp

sp<ICamera> CameraService::connect(

    const sp<ICameraClient>& cameraClient, int cameraId) {

int callingPid = getCallingPid();

sp<CameraHardwareInterface> hardware = NULL;

LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId);

if (!mModule) {

    LOGE("Camera HAL module not loaded");

    return NULL;

}

sp<Client> client;

if (cameraId < 0 || cameraId >= mNumberOfCameras) {

    LOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).",

        callingPid, cameraId);

    return NULL;

}

char value[PROPERTY_VALUE_MAX];

property_get("sys.secpolicy.camera.disabled", value, "0");

if (strcmp(value, "1") == 0) {

    // Camera is disabled by DevicePolicyManager.

    LOGI("Camera is disabled. connect X (pid %d) rejected", callingPid);

    return NULL;

}

Mutex::Autolock lock(mServiceLock);

if (mClient[cameraId] != 0) {

    client = mClient[cameraId].promote();

    if (client != 0) {

        if (cameraClient->asBinder() == client->getCameraClient()->asBinder()) {

            LOG1("CameraService::connect X (pid %d) (the same client)",

                callingPid);

            return client;

        } else {

            LOGW("CameraService::connect X (pid %d) rejected (existing client).",

                callingPid);

            return NULL;

        }

    }

    mClient[cameraId].clear();

}

if (mBusy[cameraId]) {

    LOGW("CameraService::connect X (pid %d) rejected"

         " (camera %d is still busy).", callingPid, cameraId);

    return NULL;

}

struct camera_info info;

if (mModule->get_camera_info(cameraId, &info) != OK) {

    LOGE("Invalid camera id %d", cameraId);

    return NULL;

}

char camera_device_name[10];

snprintf(camera_device_name, sizeof(camera_device_name), "%d", cameraId);

hardware = new CameraHardwareInterface(camera_device_name);

if (hardware->initialize(&mModule->common) != OK) {

    hardware.clear();

    return NULL;

}

client = new Client(this, cameraClient, hardware, cameraId, info.facing, callingPid);

mClient[cameraId] = client;

LOG1("CameraService::connect X");

return client;

}

最重要的地方在上面标注的绿色部分,这里在connect成功之后会new一个client,这个Client是CamereService类的内部类,

这个时候便会调用client这个内部类的client构造函数,而我们的回调函数也正是在这个时候被设置,看看代码:

CameraService::Client::Client(const sp<CameraService>& cameraService,

    const sp<ICameraClient>& cameraClient,

    const sp<CameraHardwareInterface>& hardware,

    int cameraId, int cameraFacing, int clientPid) {

int callingPid = getCallingPid();

LOG1("Client::Client E (pid %d)", callingPid);

mCameraService = cameraService;

mCameraClient = cameraClient;

mHardware = hardware;

mCameraId = cameraId;

mCameraFacing = cameraFacing;

mClientPid = clientPid;

mMsgEnabled = 0;

mSurface = 0;

mPreviewWindow = 0;

ifdef OMAP_ENHANCEMENT_CPCAM

mTapin = 0;

mTapinClient = 0;

mTapout = 0;

mTapoutClient = 0;

endif

mHardware->setCallbacks(notifyCallback,

                        dataCallback,

                        dataCallbackTimestamp,

                        (void *)cameraId);

// Enable zoom, error, focus, and metadata messages by default

enableMsgType(CAMERA_MSG_ERROR 

| CAMERA_MSG_ZOOM

| CAMERA_MSG_FOCUS

| CAMERA_MSG_PREVIEW_METADATA);

// Callback is disabled by default

mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP;

mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);

mPlayShutterSound = true;

cameraService->setCameraBusy(cameraId);

cameraService->loadSound();

LOG1("Client::Client X (pid %d)", callingPid);

}

上面就对camera设置了notifyCallback、dataCallback、dataCallbackTimestamp三个回调函数,用于返回底层数据用于处理,看下它的处理方法:

这里先针对其中的dataCallback回调方法做介绍,其他的回调方法以此类推,所以我们就先看一下dataCallback方法中都做了些什么事情:

这里的回调函数是camera server层的回调函数:

frameworks/base/services/camera/libcameraservice/CameraService.cpp

void CameraService::Client::dataCallback(int32_t msgType,

    const sp<IMemory>& dataPtr, camera_frame_metadata_t *metadata, void* user) {

LOG2("dataCallback(%d)", msgType);

sp<Client> client = getClientFromCookie(user);

if (client == 0) return;

if (!client->lockIfMessageWanted(msgType)) return;

if (dataPtr == 0 && metadata == NULL) {

    LOGE("Null data returned in data callback");

    client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0);

    return;

}

switch (msgType & ~CAMERA_MSG_PREVIEW_METADATA) {

    case CAMERA_MSG_PREVIEW_FRAME:

        client->handlePreviewData(msgType, dataPtr, metadata);

        break;

    case CAMERA_MSG_POSTVIEW_FRAME:

        client->handlePostview(dataPtr);

        break;

    case CAMERA_MSG_RAW_IMAGE:

        client->handleRawPicture(dataPtr);

        break;

    case CAMERA_MSG_COMPRESSED_IMAGE:

        client->handleCompressedPicture(dataPtr);

        break;

ifdef OMAP_ENHANCEMENT

    case CAMERA_MSG_COMPRESSED_BURST_IMAGE:

        client->handleCompressedBurstPicture(dataPtr);

        break;

endif

    default:

        client->handleGenericData(msgType, dataPtr, metadata);

        break;

}

}

这里进行分类处理,因为preview过程需要大量数据传输,而且容易大家理解,这里就针对preview数据回调过程进行分析:

// preview callback - frame buffer update

void CameraService::Client::handlePreviewData(int32_t msgType,

                                          const sp<IMemory>& mem,

                                          camera_frame_metadata_t *metadata) {

ssize_t offset;

size_t size;

sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);

// local copy of the callback flags

int flags = mPreviewCallbackFlag;

// is callback enabled?

if (!(flags & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK)) {

    // If the enable bit is off, the copy-out and one-shot bits are ignored

    LOG2("frame callback is disabled");

    mLock.unlock();

    return;

}

// hold a strong pointer to the client

sp<ICameraClient> c = mCameraClient;

// clear callback flags if no client or one-shot mode

if (c == 0 || (mPreviewCallbackFlag 

& CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) {

    LOG2("Disable preview callback");

    mPreviewCallbackFlag &= ~(CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK |

                              CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK |

                              CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK);

    disableMsgType(CAMERA_MSG_PREVIEW_FRAME);

}

if (c != 0) {

    // Is the received frame copied out or not?

    if (flags & CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK) {

        LOG2("frame is copied");

        copyFrameAndPostCopiedFrame(msgType, c, heap, offset, size, metadata);

    } else {

        LOG2("frame is forwarded");

        mLock.unlock();

        c->dataCallback(msgType, mem, metadata);

    }

} else {

      mLock.unlock();

}

}

这里有两个方向copyFrameAndPostCopiedFrame这个函数执行两个buff区preview数据的投递,通过它的具体实现过程,可以知道最终他也要调用dataCallback方法继续调用客户端client的回调函数

所以这里直接分析copyFrameAndPostCopiedFrame:

void CameraService::Client::copyFrameAndPostCopiedFrame(

    int32_t msgType, const sp<ICameraClient>& client,

    const sp<IMemoryHeap>& heap, size_t offset, size_t size,

    camera_frame_metadata_t *metadata) {

LOG2("copyFrameAndPostCopiedFrame");

sp<MemoryHeapBase> previewBuffer;

if (mPreviewBuffer == 0) {

    mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);

} else if (size > mPreviewBuffer->virtualSize()) {

    mPreviewBuffer.clear();

    mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);

}

if (mPreviewBuffer == 0) {

    LOGE("failed to allocate space for preview buffer");

    mLock.unlock();

    return;

}

previewBuffer = mPreviewBuffer;

memcpy(previewBuffer->base(), (uint8_t *)heap->base() + offset, size);

sp<MemoryBase> frame = new MemoryBase(previewBuffer, 0, size);

if (frame == 0) {

    LOGE("failed to allocate space for frame callback");

    mLock.unlock();

    return;

}

mLock.unlock();

client->dataCallback(msgType, frame, metadata);

}

从这里开始,回调函数进入到camera client的回调函数:

frameworks/base/libs/camera/Camera.cpp

// callback from camera service when frame or image is ready

void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr,

                      camera_frame_metadata_t *metadata)

{

sp<CameraListener> listener;

{

    Mutex::Autolock _l(mLock);

    listener = mListener;

}

if (listener != NULL) {

    listener->postData(msgType, dataPtr, metadata);

}

}

这里的listener到底是什么,还记得初始化的时候,在jni里面有设置listenerm吗?我们还是从新再看一下吧:frameworks/base/core/jni/android_hardware_Camera.cpp

// connect to camera service

static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,

jobject weak_this, jint cameraId)

{

sp<Camera> camera = Camera::connect(cameraId);

if (camera == NULL) {

    jniThrowRuntimeException(env, "Fail to connect to camera service");

    return;

}

// make sure camera hardware is alive

if (camera->getStatus() != NO_ERROR) {

    jniThrowRuntimeException(env, "Camera initialization failed");

    return;

}

jclass clazz = env->GetObjectClass(thiz);

if (clazz == NULL) {

    jniThrowRuntimeException(env, "Can't find android/hardware/Camera");

    return;

}

// We use a weak reference so the Camera object can be garbage collected.

// The reference is only used as a proxy for callbacks.

sp<JNICameraContext> context 

= new JNICameraContext(env, weak_this, clazz, camera);

context->incStrong(thiz);

camera->setListener(context);

// save context in opaque field

env->SetIntField(thiz, fields.context, (int)context.get());

}

由上面可以看出JNICameraContext是个监听类,同时set这个监听类,这个类的定义在:

frameworks/base/core/jni/android_hardware_Camera.cpp

// provides persistent context for calls from native code to Java

class JNICameraContext: public CameraListener

{

public:

JNICameraContext(JNIEnv* env, jobject weak_this,

jclass clazz, const sp<Camera>& camera);

~JNICameraContext() { release(); }

virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);

virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr,

                      camera_frame_metadata_t *metadata);

virtual void postDataTimestamp(nsecs_t timestamp

, int32_t msgType, const sp<IMemory>& dataPtr);

void postMetadata(JNIEnv *env

, int32_t msgType, camera_frame_metadata_t *metadata);

void addCallbackBuffer(JNIEnv *env, jbyteArray cbb, int msgType);

void setCallbackMode(JNIEnv *env, bool installed, bool manualMode);

sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }

bool isRawImageCallbackBufferAvailable() const;

void release();

private:

void copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType);

void clearCallbackBuffers_l(JNIEnv *env, Vector<jbyteArray> *buffers);

void clearCallbackBuffers_l(JNIEnv *env);

jbyteArray getCallbackBuffer(JNIEnv *env

, Vector<jbyteArray> *buffers, size_t bufferSize);

jobject mCameraJObjectWeak; // weak reference to java object

jclass mCameraJClass; // strong reference to java class

sp<Camera> mCamera; // strong reference to native object

jclass mFaceClass; // strong reference to Face class

jclass mRectClass; // strong reference to Rect class

Mutex mLock;

Vector<jbyteArray> mRawImageCallbackBuffers;

Vector<jbyteArray> mCallbackBuffers; 

bool mManualBufferMode; 

bool mManualCameraCallbackSet; 

};

标注部分是我们在上面用到的postData,我们看一看postData的实现过程:

void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr,

                            camera_frame_metadata_t *metadata)

{

// VM pointer will be NULL if object is released

Mutex::Autolock _l(mLock);

JNIEnv *env = AndroidRuntime::getJNIEnv();

if (mCameraJObjectWeak == NULL) {

    LOGW("callback on dead camera object");

    return;

}

int32_t dataMsgType = msgType & ~CAMERA_MSG_PREVIEW_METADATA;

// return data based on callback type

switch (dataMsgType) {

    case CAMERA_MSG_VIDEO_FRAME:

        // should never happen

        break;

    // For backward-compatibility purpose, if there is no callback

    // buffer for raw image, the callback returns null.

    case CAMERA_MSG_RAW_IMAGE:

        LOGV("rawCallback");

        if (mRawImageCallbackBuffers.isEmpty()) {

            env->CallStaticVoidMethod(mCameraJClass, fields.post_event,

                    mCameraJObjectWeak, dataMsgType, 0, 0, NULL);

        } else {

            copyAndPost(env, dataPtr, dataMsgType);

        }

        break;

    // There is no data.

    case 0:

        break;

    default:

        LOGV("dataCallback(%d, %p)", dataMsgType, dataPtr.get());

        copyAndPost(env, dataPtr, dataMsgType);

        break;

}

// post frame metadata to Java

if (metadata && (msgType & CAMERA_MSG_PREVIEW_METADATA)) {

    postMetadata(env, CAMERA_MSG_PREVIEW_METADATA, metadata);

}

}

我们接着看看这个copyAndPost方法:

void JNICameraContext::copyAndPost(JNIEnv* env

, const sp<IMemory>& dataPtr, int msgType)

{

jbyteArray obj = NULL;

// allocate Java byte array and copy data

if (dataPtr != NULL) {

    ssize_t offset;

    size_t size;

    sp<IMemoryHeap> heap = dataPtr->getMemory(&offset, &size);

    LOGV("copyAndPost: off=%ld, size=%d", offset, size);

    uint8_t *heapBase = (uint8_t*)heap->base();

    if (heapBase != NULL) {

        const jbyte* data = reinterpret_cast<const jbyte*>(heapBase + offset);

        if (msgType == CAMERA_MSG_RAW_IMAGE) {

              obj = getCallbackBuffer(env, &mRawImageCallbackBuffers, size);

        } else if (msgType == CAMERA_MSG_PREVIEW_FRAME 

&& mManualBufferMode) {

            obj = getCallbackBuffer(env, &mCallbackBuffers, size);

            if (mCallbackBuffers.isEmpty()) {

                LOGV("Out of buffers, clearing callback!");

                mCamera

->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_NOOP);

                mManualCameraCallbackSet = false;

                if (obj == NULL) {

                    return;

                }

            }

        } else {

            LOGV("Allocating callback buffer");

            obj = env->NewByteArray(size);

        }

        if (obj == NULL) {

            LOGE("Couldn't allocate byte array for JPEG data");

            env->ExceptionClear();

        } else {

              env->SetByteArrayRegion(obj, 0, size, data);

        }

    } else {

        LOGE("image heap is NULL");

    }

}

// post image data to Java

env->CallStaticVoidMethod(mCameraJClass, fields.post_event,

        mCameraJObjectWeak, msgType, 0, 0, obj);

if (obj) {

    env->DeleteLocalRef(obj);

}

}

以上先建立一个byte数组obj,将data缓存数据存储进obj数组,CallStaticVoidMethod是C调用java函数,最后执行实在Camera.java(框架)的postEventFromNative()

从这里开始,回调函数进入到camera framework层

frameworks/base/core/java/android/hardware/Camera.java

private static void postEventFromNative(Object camera_ref,

                                        int what, int arg1, int arg2, Object obj)

{

    Camera c = (Camera)((WeakReference)camera_ref).get();

    if (c == null)

          return;

    if (c.mEventHandler != null) {

        Message m = c.mEventHandler.obtainMessage(what, arg1, arg2, obj);

        c.mEventHandler.sendMessage(m);

    }

}

sendMessage之后由handle进行处理,定义同样在framework层

private class EventHandler extends Handler

{

    private Camera mCamera;

    public EventHandler(Camera c, Looper looper) {

        super(looper);

        mCamera = c;

    }

    @Override

    public void handleMessage(Message msg) {

        switch(msg.what) {

        case CAMERA_MSG_SHUTTER:

            if (mShutterCallback != null) {

                mShutterCallback.onShutter();

            }

            return;

        case CAMERA_MSG_RAW_IMAGE:

            if (mRawImageCallback != null) {

                mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return;

        case CAMERA_MSG_COMPRESSED_IMAGE:

            if (mJpegCallback != null) {

                mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return

        case CAMERA_MSG_PREVIEW_FRAME:

            if (mPreviewCallback != null) {

                PreviewCallback cb = mPreviewCallback;

                if (mOneShot) {

                    mPreviewCallback = null;

                } else if (!mWithBuffer) {

                    setHasPreviewCallback(true, false);

                }

                cb.onPreviewFrame((byte[])msg.obj, mCamera);

            }

            return;

        case CAMERA_MSG_POSTVIEW_FRAME:

            if (mPostviewCallback != null) {

                mPostviewCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return;

        case CAMERA_MSG_FOCUS:

            if (mAutoFocusCallback != null) {

                mAutoFocusCallback.onAutoFocus(msg.arg1 == 0 

? false : true, mCamera);

            }

            return;

        case CAMERA_MSG_ZOOM:

            if (mZoomListener != null) {

                mZoomListener.onZoomChange(msg.arg1, msg.arg2 != 0, mCamera);

            }

            return;

        case CAMERA_MSG_PREVIEW_METADATA:

            if (mFaceListener != null) {

                mFaceListener.onFaceDetection((Face[])msg.obj, mCamera);

            }

            return;

        case CAMERA_MSG_ERROR :

            Log.e(TAG, "Error " + msg.arg1);

            if (mErrorCallback != null) {

                mErrorCallback.onError(msg.arg1, mCamera);

            }

            return;

        default:

            Log.e(TAG, "Unknown message type " + msg.what);

            return;

        }

    }

}

上面可以看出,这里处理了所有的回调,快门回调mShutterCallback.onShutter(),RawImageCallback.onPictureTaken()拍照数据回调,自动对焦回调等。

默认是没有previewcallback这个回调的,除非你的app设置了setPreviewCallback,可以看出preview的数据还是可以向上层回调,只是系统默认不回调,这里再说深一些:

需要做以下事情,检查PreviewCallback 这个在framework中定义的接口有没有设置了setPreviewCallback,设置则调用,这里接口中的onPreviewFrame方法需要开发者自己实现,这里默认是没有实现的,需要特殊使用的要自己添加,这里是自己的理解,看一下PreviewCallback 接口的定义:frameworks/base/core/java/android/hardware/Camera.java

public interface PreviewCallback

{

    void onPreviewFrame(byte[] data, Camera camera);

};

另数据采集区与显示区两个缓存区buffer preview数据的投递,以完成preview实时显示是在HAL层完成的。

takePicture()处理过程跟preview差不多,只是增加了回调函数返回时候存储图像的动作,这里分析一下takepicture的处理过程:

case CAMERA_MSG_COMPRESSED_IMAGE:

            if (mJpegCallback != null) {

                mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);

            }

            return;

mJpegCallback的定义

private PictureCallback mJpegCallback;

走到这里我们又不得不回头看看最起初在调用takepicture的时候是怎么调用的

try {

            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,

                    mPostViewPictureCallback, new JpegPictureCallback(loc));

} catch (RuntimeException e ) {

            e.printStackTrace();

            return false;

}

这里大家看到了标准部分就是要使用的mJpegCallback,但是这个callback是JpegPictureCallback类,我们定义的mJpegCallback确是PictureCallback 类,不是同一个类

所以这个还是必须得说清楚一点,看看JpegPictureCallback类的定义吧

private final class JpegPictureCallback implements PictureCallback {

    Location mLocation;

    public JpegPictureCallback(Location loc) {

        mLocation = loc;

    }

    public void onPictureTaken(

            final byte [] jpegData, final android.hardware.Camera camera) {

        if (mPausing) {

            if (mBurstImages > 0) {

                resetBurst();

                mBurstImages = 0;

                mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA,

                                                 CAMERA_RELEASE_DELAY);

            }

            return;

        }

        FocusManager.TempBracketingStates

tempState = mFocusManager.getTempBracketingState();

        mJpegPictureCallbackTime = System.currentTimeMillis();

        if (mPostViewPictureCallbackTime != 0) {

            mShutterToPictureDisplayedTime =

                    mPostViewPictureCallbackTime - mShutterCallbackTime;

            mPictureDisplayedToJpegCallbackTime =

                    mJpegPictureCallbackTime - mPostViewPictureCallbackTime;

        } else {

            mShutterToPictureDisplayedTime =

                    mRawPictureCallbackTime - mShutterCallbackTime;

            mPictureDisplayedToJpegCallbackTime =

                    mJpegPictureCallbackTime - mRawPictureCallbackTime;

        }

        Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "

                + mPictureDisplayedToJpegCallbackTime + "ms");

        if (!mIsImageCaptureIntent) {

            enableCameraControls(true);

            if (( tempState != FocusManager.TempBracketingStates.RUNNING ) &&

                  !mCaptureMode.equals(mExposureBracketing) &&

                  !mCaptureMode.equals(mZoomBracketing) &&

                  !mBurstRunning == true) {

                long delay = 500 - mPictureDisplayedToJpegCallbackTime;

                if (delay < 0) {

                    startPreview(true);

                    startFaceDetection();

                } else {

                    mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, delay);

                }

            }

        }

        if (!mIsImageCaptureIntent) {

            Size s = mParameters.getPictureSize();

            mImageSaver.addImage(jpegData, mLocation, s.width, s.height);

        } else {

            mJpegImageData = jpegData;

            if (!mQuickCapture) {

                showPostCaptureAlert();

            } else {

                doAttach();

            }

        }

        checkStorage();

        if (!mHandler.hasMessages(RESTART_PREVIEW)) {

            long now = System.currentTimeMillis();

            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;

            Log.v(TAG, "mJpegCallbackFinishTime = "

                    + mJpegCallbackFinishTime + "ms");

            mJpegPictureCallbackTime = 0;

        }

        if (mCaptureMode.equals(mExposureBracketing) ) {

            mBurstImages --;

            if (mBurstImages == 0 ) {

                mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 0);

            }

        }

      //reset burst in case of exposure bracketing

        if (mCaptureMode.equals(mExposureBracketing) && mBurstImages == 0) {

            mBurstImages = EXPOSURE_BRACKETING_COUNT;

            mParameters.set(PARM_BURST, mBurstImages);

            mCameraDevice.setParameters(mParameters);

        }

        if (mCaptureMode.equals(mZoomBracketing) ) {

            mBurstImages --;

            if (mBurstImages == 0 ) {

                mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 0);

            }

        }

      //reset burst in case of zoom bracketing

        if (mCaptureMode.equals(mZoomBracketing) && mBurstImages == 0) {

            mBurstImages = ZOOM_BRACKETING_COUNT;

            mParameters.set(PARM_BURST, mBurstImages);

            mCameraDevice.setParameters(mParameters);

        }

        if ( tempState == FocusManager.TempBracketingStates.RUNNING ) {

            mBurstImages --;

            if (mBurstImages == 0 ) {

                mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 0);

                mTempBracketingEnabled = true;

                stopTemporalBracketing();

            }

        }

        if (mBurstRunning) {

            mBurstImages --;

            if (mBurstImages == 0) {

                resetBurst();

                mBurstRunning = false;

                mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 0);

            }

        }

    }

}

原来他们是父子类之间的关系,那么自然父类可以可以转换为子类的形式,但是子类就不能向父类转换了。而且这里子类重新实现了父类的方法onPictureTaken。这里这个函数不就是handle里面调用的函数了。takepicture最终将图片保存下来了。

8.2.3 Camera takepicture过程总结

函数调用流程图如下:

图片.png

拍照命令时序图:

图片.png

拍照数据回调时序图:

图片.png
  1. 点击拍照按钮图标会执行ShutterButton类中的drawableStateChanged方法,接着调用callshutterButtonFocus(boolean flag)方法,接着会调用OnShutterButtonListener接口中的onShutterButtonFocus(boolean flag)方法。

  2. 在PhotoModule类中执行onShutterButtonFocus(boolean pressed)方法,在该方法中判断按下的状态,如果按下就调用FocusOverlayManager类中的onShutterDown()方法,接着调用autoFocus()方法,在该方法中又调用Listener接口中的autoFocus()方法,回调PhotoModule类中的autoFocus()方法。

3.在autoFocus方法中调用setCameraState(FOCUSING),接下来会调用内部类AutoFocusCallback的onAutoFocus(boolean focused, android.hardware.Camera camera)方法,初始化相机后,接着调用FocusOverlayManager类中的onAutoFocus(boolean focused, boolean shutterButtonPressed)方法,在该方法中处理mState为STATE_FOCUSING的逻辑。

4.执行ShutterButton类中的performClick()方法,主要用来响应按下ShutterButton事件,接着回调PhotoModule类中的onShutterButtonClick()方法,接着调用onSnap方法,接下来调用FocusOverlayManager类中的doSnap()方法,接着调用capture方法,接着又回调PhotoModule类中的capture方法(关键)。

  1. 拍照按钮的状态发生变化,pressed值由true变为false,继续调用ShutterButton类中的callShutterButtonFocus方法,调用OnShutterButtonListener接口中的onShutterButtonFocus()方法,在PhotoModule类的onShutterButtonFocus(boolean pressed)方法中直接返回,回调实现了ShutterCallback接口的类ShutterCallback里面的onShutter方法。

UI层类之间调用流程

图片.png

6.底层(HAL)调用UI(APP)的接口实现抽象类AbstractShotMode中的public void onPictureTaken(byte[] data, Camera camera)方法,回调PhotoModule类中的public void onLastPictureTaken(boolean isNeedStartPreview)方法。

  1. 在onLastPictureTaken方法中又调用restartPreview()方法,接着调用setupPreview()方法,在该方法中调用startPreview方法,流程如下startPreview(int updateSet)->setCameraParameters(int updateSet) ->updateCameraParametersInitialize()-> setPreviewSize(),在上述流程中的setCameraParameters方法中继续调用updateCameraParametersPreference()方法(关键),底层回调FeaturePictureCallback接口中的onQuickThumbnail(byte[] data)方法,在该方法中调用PhotoModule类中的onQuickThumbnail(byte[] data, boolean isGenUrlEarlier)方法,返回到startPreview方法里。

  2. 在抽象类AbstractShotMode中onPictureTaken方法(关键)中执行FeaturePictureCallback接口中的onFakePictureTaken()方法,接着会执行CommonFeaturePictureCallback回调接口中的onFakePictureTaken()方法,再调用PhotoModule类中的onFakePictureTaken方法,继续调用接口中的storeImage(byte[] jpegData, int postWidth, int postHeight,boolean isHdr, boolean updateThumbnail)。

底层调用UI层处理流程

图片.png

9.Camera Feature 介绍和API使用

9.1 Face detection人脸检测

简介:根据preview信息,通过算法检测人的脸部,并用白框选中检测到的人脸。

9.1.1人脸检测主要介绍代码实现要素

1创建face detection监听

不同平台有所不同流程一致这里以6580为例

(6580主要是通过消息来实现face detection 的流程的实现代码在CameraManager.java)

设置监听mCamera.setFaceDetectionListener(...)

2启动face detection

代码在CameraManager.java子类CameraProxy.java里面startFaceDetection()函数

3关闭face detection

stopFaceDetection()

9.1.2调用流程,这里以普通模式为例

其它模式雷同,还是以6580为参考

1设置face detection监听photoActor.java

private FaceDetectionListener mFaceDetectionListener = new FaceDetectionListener() {

    @Override

    public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {

....

mCameraActivity.getFrameView().setFaces(faces);//注意这里白框出现了哈哈

mModuleManager.onFaceDetected(faces);//这里是进一步的人脸状态动态处理看2

}

2 Preview 数据face detection 完后(faces>0) 转到photoMode找到photoMode.java里面有对应的face detection完成后的处理

protected boolean executeAction(ActionType type, Object... arg) {

...

case ACTION_FACE_DETECTED:

if (arg != null) {

Log.d(TAG, "faceLength = " + ((Face[]) arg).length);

onFaceDetected(((Face[]) arg).length);//根据你的脸的大小对应白框的变化更新

....

}

3结束face detection

调用:onFaceDetected(0);

例如:关闭camera的时候需要调用这句话释放face detection,实例代码在photoActor.java的

onCameraClose(){

onFaceDetected(0);

}

9.2人脸美化****Face beauty

9.2.1功能介绍

对人脸进行美白、磨皮、瘦脸和大眼等美化功能;

Smooth:皮肤平滑度,越高皮肤除;

Skin Color:皮肤.色,low为做红.处理;

Sharp:瘦脸&大眼程度,值越高表示程度越大。

9.2.2三个参数的客制化

客制化参数packages/apps/Camera/src/com/android/camera/res/values/arrays.xml

<string-array name = “pref_camera_facebeauty_turning_entryvalues”

translatable = “false”>

<item>-4</item>

<item>0</item>

<item>4</item>

</string-array>

-4到4共9个等级

9.2.3常见问题

1美肤拍照无美肤效果

确认拍照时有无正确识别到人脸(人脸框是否显示),如未显示请先check FD是否有正常启用,若有请check cap-mode是否设置为FB,如果都正常请提交e-service并附上log处理。

2不保存美肤拍照原图

– packages/apps/Camera/src/com/android/cameraFeatureSwitcher.java文件中将isFaceBeautyOriginalPictureSaved的返回值改为False

3 FB拍照异常退出发生NE

– 看拍照比例是否符合4:3、5:3、16:9这三种,如果不符 合请修改为这几种比例或申请patch:ALPS00801970

– 请check拍照size是否超过了5000*4000,即宽不能超过5000,高不能超过4000,如果超过请申请patch:ALPS00872370

– 其他情况请提交e-service处理

9.2.4调用流程

Facebeauty是MTK开发的一种mode,一般模式类功能都到mediatek的mode文件夹里面去找,里面绝对有你想要的~。接下来就来看看这个FB模式吧。众观MTK写的附加功能,估计有许多人写,好乱。但mode类写得比较好,采用的是常用的MVC设计模式这里单独把face beauty 模式拆解一下,到时后面的其它模式就简单概括了哈,可以参考这里来看流程。也可以按照这个来自己添加一个模式。

单独的功能模块实现:MVC模式设计

M(模型):找到FaceBeautyMode.java

V(视图):1:FaceBeautyRotateLayout.java

2:FaceBeautyView.java

C(控制):1:FaceBeautyInfo.java

2:FaceBeautyPictureSizeRule.java

3:FaceBeautyParamentersHelper.java

4:FaceBeautyPreviewSize.java

5:VfbQualityRule.java

6:VideoFaceBeautyRule.java

当用户切换到face beauty时会发送一个参数到ModeFactory.java(这是个模式生产工厂)新建一个FaceBeautyMode实例,模式的视图View则在FaceBeautyMode.java通过调用getCameraView(SpecViewType.MODE_FACE_BEAUTY)向UI大管家CameraAppUiImpl.java发送一个参数来获取。这里吐槽一下视图的产生也是和mode的产生一下都是基于工厂设计模式的,mode的view生产的地方在最终是在ViewFactory.java这个工厂里面。下面是伪流程图。

图片.png

9.3 Auto ASD:自动场景检测

简介:根据preview数据信息判断当前场景:一般是7种场景:正常场景,夜间场景,风景,人物等。

9.3.1限制条件

1人脸检测结果

2原始图像信息

3 3A信息

所以自动场景检测并非100%准确。

9.3.2常见问题

只能识别正常场景,没有实现3A信息接口,直接找驱动。

9.3.3代码实现要素

1 调用setCameraMode将camera mode 设置为CAMERA_MODE_MTK_PRV

2 打开人脸识别功能

3 将capture mode设置为asd

parameter.setCaptureMode(Parameter.CAPTURE_MODE_ASD);

4 创建ASD callback

这里参考MT6580代码在ASD.java里面

Private final AsdListener mASDCaptureCallback = new AsdListener(){

...

}

5 注册ASD callback

代码在ASD.java里面mCameraDevice.setASDCallback(mASDCaptureCallback );

6 使用完后或关闭ASD时调用如下代码回收

设置ASD CallBacl为null;

mCameraDevice.setASDCallback(null);

9.4 **Continue Shot **

9.4.1简单介绍****与使用方法

Continue Shot:相对单拍可以连续拍多张照片;

长按capture按键,直到设定的最大连拍张数或者松开capture按键为止。

9.4.2 Continue Shot Spec and Limitation

1.当AP发现保存jpeg file到SD变慢时,会自动降低连拍速度,甚至停止连拍

– 当SD卡写速度太慢、底层callback给AP的jpeg file速度相对较快时,AP层可用的缓存jpeg file的空间逐渐减小,当减小到一定程度时,会降低连拍速度;若仍不能超出写SD速度,则会停止连拍。

2.连拍拍照音响次数与实际拍照张数并不匹配,是互相独立的

– 连拍的拍照音在AP层播

– 连拍的实际动作在hal层完成

9.4.3 Continue Shot****效果调试****/****客制化参数

1.控制连拍速度

– There is a API in Camera AP

setContinuousShotSpeed (int speed), speed=3 means capture

speed = 3fps

– You could call it after takePicture() as needed

2.更换拍照音

– /system/media/audio/ui/ camera_shutter.ogg

9.4.4 Continue Shot****常见问题

1拍照绿屏,花屏(直接找驱动分析)

– 提供复现问题的mobile log以及dump图像数据给Mediatek分析

• Cmd:adb shell setprop debug.camera.dump 1

• Dump图像路径:/sdcard/.raw /sdcard/.yuv:

2连拍达不到设定的固定张数

– 请check SD卡的读写速度是否可以稳定在较高速率上,其次请

check手机RAM是否太小,而capture size是否又太大;

– 除此,请提供复现问题的mobile log到Mediatek分析

3松开capture键后,连拍还在继续

– 请确认手机touch Pannel是否有异常

– 若无异常,请提供复现问题的mobile log给Mediatek分析

4连拍速度太慢

– 首先,确认sensor capture frame rate是否过低

– 其次,请check SD卡读写速度是否可稳定在较高速率上

– 再者,请check手机RAM是否太小,而设置的capture size又太大

– 除此,请提供复现问题的mobile log给Mediatek分析

9.5零延时拍照ZSD

9.5.1简介

ZSD模式下,sensor的工作方式不区分preview与capture,始终以特定的size输出,一般为sensor full size,也可根据需要调整小。

当触发ZSD拍照时,sensor无须切换mode,直接将拍照键抬起瞬间的sensor输出压缩成JPEG file

9.5.2 ZSD Spec and Limitation

ZSD模式下,预览可能不流畅

– ZSD模式下,sensor的工作方式不区分preview与capture,始终以特定的size输出,一般为sensor full size

– 由于sensor能力的限制,一些sensor在full size输出时帧率较低,预览会有卡顿不顺的感觉

9.5.3 ZSD****客制化参数

1.ZSD实现原理

– sensor在ZSD模式时输出size始终不变,hal层在preview状态下时时保存最新的N帧(可客制),当user触发拍照事件,可在保存的N帧中选择与按拍照键时间最接近的用于拍照

2.客制化参数

– N = Camera_custom_zsd.cpp中函数get_zsd_cap_stored_frame_cnt()的返回值,默认为3

– 若认为ZSD效果不明显,shutter delay较大,可增大get_zsd_cap_stored_frame_cnt函数的返回值

– 增大此值的影响,需要耗更多的memory

–需多耗2倍capture size的memory or 1.25倍sensor output size的memory,case by case

9.5.4 ZSD****常见问题

1.预览画面不流畅,帧率低

– 请先确认sensor输出帧率,若较低,一般为sensor原因

– 否则请抓取复现问题的mobile log,并提交e-service处理

2.零延时效果不明显

– 参考二说明

3.拍照绿屏,花屏(直接找驱动)

– 提供dump数据与mobile log,提供e-service处理

– cmd:adb shell setprop debug.camera.zsddump 1

– dump数据存储路径:/sdcard/zsd/

4.预览绿屏,花屏(直接找驱动)

– 提供dump数据与mobile log,提供e-service处理

– cmd:adb shell setprop camera.dumpbuffer.enable 1

– dump数据存储路径:/sdcard

9.6笑脸模式

9.6.1简介

在camera预览时 preview 数据到底层分析通过算法检测笑脸。当检测到笑脸时便会拍一张照片。

9.6.2限制条件

1.当人脸五官模糊到一定程度或明显偏暗时,人脸侦测和追踪quality会变差

2.图像中人脸小于图像高度1/10时,无法侦测到人脸;人脸高度在图像高度1/5 -1/10时,识别率也较低。

3.戴帽子、戴眼镜等会影响识别率

4.FD过程中,可能会有人脸框漂移或跟踪不是很准确

– 人脸识别功能包含两个阶段

1.FD(Face Detection): 根据人脸特征检测出图像中的人脸

2.FT(Face Tracking): 对检测到的人脸进行追踪

– 由于FT参考人脸肤色信息,因此有此限制(有方式改善)

FD效果调试与客制化,找驱动调试

关键代码:

void get_fd_CustomizeData(FD_Customize_PARA *FDDataOut){

FDThreshold = 32 (default)

FDRefresh = 70(default)

....

}

这两个参数调试如下:

1.****FDRefresh = 70(default)

– Usage:

• 由于FT算法只对已检测到的人脸进行追踪,而预览画面中可能出现

新的人脸,所以需要定期检查画面中是否有新的人脸进入。

FDRefresh即用来设定每隔多少frame重新检测人脸。

• 重新获取的人脸位置跟上一张frame FT的结果可能有些许不同,

user会有FD框更新的感觉,频率太高会感觉人脸框更新太过频繁。

– Suggest Range:

• 30~120

– Tuning purposes:

• 如果希望新的人脸加入时即能马上被检测到,此值可以设小。

– Description:

• Increasing value à 两次FD算法间隔帧数增加

• Decreasing value à两次FD算法间隔帧数减少

2.****FDThreshold = 32 (default)

– Usage:

• FD阀值,判断某区域是否为人脸的标准。值低即标准低,识别率提

高,误判率也同步提高;值高即标准高,识别率降低,误判率也同

步降低。

– Range:

• 29~35

– Tuning purposes:

• 可以根据实际的误判状况来调整FDThreshold,如果某些物体不希

望被误判为人脸,可以慢慢调高阀值,再看状况是否改善

– Description

• Increasing valueà DR(Detection Rate)下降,FPR(False Pass

Rate)下降

• Decreasing value à DR上升,FPR上升

9.6.3 FD****常见问题

▪ 四个方向均支持人脸识别

– MT6589 JB2 Patch ID: ALPS00943937

– MT6582 JB5 Patch ID: ALPS00957624

▪ FD误判

– 可以通过调整FDThreshold改善

▪ FD识别框飘动

– 可以通过调整FDRefresh改善

9.7物体动态追踪****Object Tracking

9.7.1简介

在preview界面,点中物体长按,当出现绿框锁定物体时,捕获成功。当物体移动时,绿框会锁定跟随物体移动。

9.7.2说明与限制条件

1.太大颜色变化会track fail

2.颜色太相近会追错对象

3.Touch的位置会影响追踪的结果

4.追踪物体或者手机移动太快,导致追踪物体在前后两张frame中的成像区域没有重叠,会导致tracking fail

9.7.3 OT****效果调试****/****客制化参数

找驱动调试。

代码路径:alps\mediatek\custom\common\hal\camera\camera\camera_custom_ot.cpp

Void get_ot_CustomizeData(OT_Customize_PARA *OTDataOut){

LtOcOb_ColorSimilarity_TH = 0.37;

Numiter_shape_F =1 (default);

OBLoseTrackingFrm = 90 (default);

OCLoseTrackingFrm = 90 (default);

......

}

上面几个参数的调试如下:

1.LtOcOb_ColorSimilarity_TH = 0.37(default)

– Usage:

• Objecttracking算法阀值,阀值设低,可以提高tracking的成功率,

较不易lose tracking,但是也容易追错object

– Range:

• 0.2~0.59

– Tuning purposes

• 如果tracking的时候很容易追错物体(例如: 从鲜红色的物体追错成

粉红色或暗红色物体),可以提高阀值

• 如果不希望一些AE变化引起lose tracking(例如: 暗红色的物体因

为曝光时间增加变为亮红色,而lose tracking),可以调低阀值

– Description

• Increasing valueà不容易追错物体,容易lose tracking

• Decreasing value à容易追错物体,不容易lose tracking

2.Numiter_shape_F =1 (default)

– Usage:

• 每几个frame会对被追踪物体做一次reshape,reshape根据被追踪

物体成像大小的变化来更新tracking框的大小

• 若每个frame都做reshape,UI上的框会实时更新,但花费较大的计

算量

– Range:

• 1 ~ 10

– Tuning purposes

• 如果不需要reshape很频繁、更重视降低cpu loading时,可以将值

设大

– Description

• Increasing valueà Tracking框的大小更新的较慢,但计算量较小

• Decreasing value à Tracking框的大小更新的较快,但计算量较大

3.OBLoseTrackingFrm = 90 (default)

▪ OCLoseTrackingFrm = 90 (default)

– Usage:

• OB (object boundary):表示由于物体移出画面外而造成的lose

tracking。

OBLoseTrackingFrm表示如果连续多少张frame都没有再成功

tracking,则需要user重新指定欲追踪的物体。

• OC(object occlusion):表示由于result model和target model相差太

多,导致追踪失败,通常是因为物体被挡住,或是移动得太快速,

造成算法追不到。

OCLoseTrackingFrm表示如果连续多少张frame都没有再成功

tracking,则需要user重新指定欲追踪的物体。

– Range:

• 10~ 120

9.8全景拍照

9.8.1简介

全景拍照运行在Camera预览模式,抓取连续Preview buffer,通过算法将连续图像拼接合成一张图像。全景拍照适合对远景/亮度均匀场景拍摄。(mode调用流程参考face beauty)

9.8.2规则和限制条件

1.对近景/室内场景进行全景拍照,会在连接处出现拼接物体错位问题

算法限制

– 原因:近景物体在不同视角下成像形状会有差异,导致拼接时会有少许错位。

2.对亮度差异较大的场景做全景拍照,效果会不理想

3.纯色场景由于算法限制,全景拍照效果不理想

9.8.3全景拍照客制化参数

(驱动)

▪ 可以客制化拍照张数,默认拍摄9张再拼接:

– PanoramaActor.java: NUM_AUTORAMA_CAPTURE9(Default Value)

– autorama_hal_base.h: MINT32 gImgEV[9] (DefaultValue)

9.8.4全景拍照常见问题

▪ 全景拍照拼接出现波浪现象

– 存在于MT6589/72/82等platform的版本

– 申请Patch:ALPS00931987

9.8.5全景拍照相关问题****debug****流程

▪ 全景拍照照片效果问题

– 首先,检查是否是拍摄近景的限制问题,此类问题目前无法解法

– 其次,检查是否是拼接出现波浪形,如果是,请申请相关patch

▪ 全景拍照使用过程中出现其他异常(例如:NE/定屏)等问题。

– 抓取复现问题的mobile log,并提交e-service处理

9.9多视角拍照(****MAV****)

9.9.1简介

多视角拍照(Multi Angle View)

– 在Camera预览模式抓取连续多张Preview frame,将多个角度的图像合成一张MPO图片。

横屏拿手机,围绕被摄对象平稳移动手机拍摄

– 若想拍下来的视角更广,需要移动速度更快

9.9.2限制与规则

目前MAV可拍摄的最大角度为250度,拍照角度与拍摄者围绕被摄对象的移动速度有关。点击拍照键触发拍摄第一张照片,同时算法将此张设置为参考帧。当算法检测到场景发生一定变化时会触发拍第二张,算法会根据前两照片的相关信息推算是拍摄者的移动速度,并根据此移动速度抓取后面需要拍摄的帧,最终合成MPO文件。

9.9.3常见问题

MAV icon没有显示

– 需要在feature table的capture mode中添加MAV

– 如何修改Feature table可以参考MTK on-line相关FAQ

9.10情景照片****Live Photo

9.10.1简介

当选择Live Photo拍照模式时,Camera开始进行后台录像。当拍照时,会保存最近的1-4s视频

▪ 保存的视频播放效果

– 先显示视频最后一帧1.5s

– 用最后一帧做一个500ms的动画,最后停留在左下角

– 播放录制的3s视频,300ms后左下角小图会浅出简介

重要功能:

– Live photo会在background预录(1~4s)Video,动画帧是background video的最后一帧

– Live Photo在Video Player中会循环播放

– Live Photo的Resolution会贴近Screen Size

– Live Photo的thumbnail为用于做动画的最后一帧

– Live Photo模式下能滑动到Gallery,进入Gallery后会stop recording,

重回Camera后会重新start recording

– Live Photo录制下来的Video以“LIV”开头。

9.10.2适用场景及使用方法

▪ 适用场景:适用任何场景

▪ 使用方法

– 进入camera,选择live photo拍照模式

– 按拍照键,会出现“saving live photo….”字符串

– 保存完成后自动返回到preview

9.10.3限制条件

|

Capture

|

item

|

Description

|
|

setting相容

| | |
| |

Flash

|

Live Photo是在background录像,无法在录像过程中切换flash,因此flash被置

为off

|
| |

Camera Tab

|

Live Photo正在录像,所以无法设置与Camera相关的Setting

(如:ZSD、FD、ASD、Self timer、CS、Picture Size、Preview Size、ISO、

FB)

|
| |

Voice Capture

|

Live Photo支援Voice Capture

|
|

function

| | |
| |

Preview时可滑动到Gallery

|

滑动到Gallery后停止后台录像,从Gallery滑回到Camera重新启动后台录像

|
| |

后台录像开始和结束时不能有提

示音

|

为了达到后台录像效果,不能有提示音播放出来

|
| |

Capture时有提示音

|

点击Capture Button,会有拍照的提示音播放出来

|
| |

循环播放

|

拍下来的Live Photo在MTK Video Player中能循环播放

|
| |

四个方向录像

|

Live Photo产生的Video方向由Capture时手持方向决定

|
| |

thumbnail与动画帧一致

|

Camera右下角的thumbnail跟live photo动画帧是一致的

|
| |

后台录制的Video最后一帧为I

Frame

|

Live Photo需要拿background video的最后一帧做动画,因为需要让最后一帧为

I Frame

|
| |

Preview Size由向screen size

靠近

|

live photo preview过程中不能修改quality,选取屏的大小为参考标准

|
|

feature相容

| | |
| |

Continuous Shot

|

不支援CS

|
|

performance

| | |
| |

resolution

|

录制下来的Video最大resolution为720p

|
| |

FPS

|

录制下来的Video最低fps为24

|
| | | |
| | | |

下面限制将导致7s2aving时长变长

1.动画帧是background video的最后一帧,且嵌在live photo的每一帧中

2.GPU处理每一帧的能力、Video Player decode每一帧的能力Live Photo与其他Capture Mode切换时,Preview有卡顿现象

原因:Live Photo需要在后台录像,从Video mode (live phone相当于video mode)切换到Camera Mode需要做stop preview与 start preview

9.11 HDR

9.11.1简介

从一组曝光图像中选择一张曝光良好的图像。

9.11.2 HDR 规则和限制条件

目前HDR的拍照速度在4-5秒

– HDR要拍-2EV,0EV,+2EV的三张照片,算法处理时间较长,因此耗时较久

– 若将CUST_HDR_CAPTURE_ALGORITHM配置成1 or 2,在拍2张的case时,比拍摄3张节省1S

– 后续chip的HDR时间,有较大改善

9.11.3 HDR****效果调试****/****客制化参数

▪ CUST_HDR_CAPTURE_ALGORITHM=1 (Default)

– Usage

• 决定HDR拍摄2张or 3张不同亮度的图片

– Range

• 0~2

– Tuning purpose

• CUST_HDR_CAPTURE_ALGORITHM=0,Always take 3 pictures

• CUST_HDR_CAPTURE_ALGORITHM=1,算法自动判断需要拍摄

两张还是三张

• CUST_HDR_CAPTURE_ALGORITHM=2,Always take 2 pictures

9.11.4 HDR****常见问题

▪ 使用8M sensor,插值到12M或者更高分辨率拍HDR时出现内存不足

– 解决方法:以sensor实际输出size做HDR算法,之后scale up到拍照size

• 需要打上patch:ALPS00909176、ALPS942214.

• 然后将

alps\mediatek\custom\common\hal\inc\camera_custom_hdr.h,将

define CUST_HDR_CAPTURE_POLICY 0改为: #define CUST_HDR_CAPTURE_POLICY 1

▪ 因为手震,拍HDR出现模糊/鬼影

– 使用burstshot抓取HDR需要的3帧数据

– Patch id:ALPS00876463 for MT6589/MT658

HDR图片在PC上看不到缩略图

– 需打patch解决

• ALPS00671141 for MT6589

• ALPS00986932 for MT6572

▪ 后台播放music,前台进行HDR拍照,music与拍照音都出现卡顿现象、有时候自动退出camera

– 需打Patch解决

• ALPS00987671 for MT6572

• ALPS00431395 for MT6589

10.Camera中第三方算法添加

10.1 Camera HAL层预览

Camera接口图:

图片.png

Camera组件关系表

图片.png

Camera Hal层组件:Image Buffer Queue, Client,Adapter,Camera Device, Camera Device manager, Params Manager

10.2 Camera图像缓冲Queue

图片.png

TODO Queue:用于保存未处理和准备处理的图片缓存

DONE Queue: 用于保存已处理或取消的图片缓存

图片.png

流程如下:

1 客户处理端提供一个 TODO buffer 给处理器(Processor);

2处理供应端从供应队列(provider)里取出一个buffer;

3 处理获取到的buffer(通过imageio);

4 处理供应端把处理好的buffer输入到供应队列;

5 客户处理端从处理器中得到这个DONE buffer

6 客户端再做一些其他的处理。

10.3第三方****算法预览部分

10.3.1 ExtImgProc介绍

客户经常为一些camera feature增加第三方图片处理。

ExImgProc为增加拓展图片处理和图片集中控制流提供了一个简单的方法。

其文件目录如下:

1.common folder

图片.png

2.Platform folder

图片.png

根据项目不同,目录可能略有变化。

10.3.2 相关****类****结****构图

图片.png

10.3.3 ExtImpProc处理过程

这么看来,他们是ExtImgProc的两块不同的缓存处理方法:COMMON , platform buffers

他们的实现流程如下:

1.COMMON部分处理内容为: Display, Record, and PreviewCBclients(Common Buffer)

图片.png

(1)取出消息

(2)把消息发送给ExtImgProc处理

(3)等待ExtImgProc返回数据

(4)把处理结果返回给 APP

2.添加三方算法处理的流程(Common buffer)

(1)设置需要处理的Image buffer type

图片.png

(2)添加三方算法

图片.png

添加三方算法Samplecode:将预览图转换为黑白图像显示:

图片.png

(3)调用第三方处理函数(Common buffer)

图片.png

3.平台 BUFFER 处理 (Platform 部分)

正常流程处理****情况****:

图片.png

(1)sensor 把数据发送给ISP Pass 1

(2)ISP pass1 把数据发送给MEM

(3)把数据发送给ExtImgProcHw处理

(4)等待消息处理返回

(5)把消息发送给ISP pass2 做输入处理。

Two Run Pass2****处理****流程****:

当程序处理图片的过程比较复杂时触发,我们通过这个方法来减少所用的时间。

图片.png

调用3 方处理流程(Platform Buffer)

(1)设置需要处理的Platform Buffer type

图片.png

(2)添加三方算法

图片.png

(3)调用第三方处理函数(doImgProc)

图片.png

调整格式和Buffer大小:

图片.png

10.3.4三方算法对性能影响的简单分析

数据流程说明图:

图片.png

(1).Sensor 预览帧率上3M@30fps;

(2).对Common buffer,如果ExtImgProc 过程多于33ms将丢帧;

(3).对Platform buffer,如果ExtImgProcHW过程多于20ms将丢帧;

(4).如果Display和Record buffers需要同样的image 处理过程,ExtImgProcHW进程只有一个buffer会减少系统loading. ExtImgProc过程(Display和Record buffer相互独立)会有很多的性能问题。参考下表:

图片.png

10.4拍照****的第三方算法添加

10.4.1移植capture****的第三方算法

移植步骤:

1 在路径

vendor/mediatek/proprietary/platform/mtXXXX/hardware/mtkcam/D1/v1/adapter/Scenario/Shot(可能项目不同会有所区别)下新建一个shotclass(可命名为VendorShot)

2 VendorShot类需要实现3方专用的拍照流程,主要步骤如下

(1)调用底层相关接口获取JPEG ENC之前的YUV buffer

(2)进行三方算法处理

(3)获取postview数据(经过3方算法处理)并显示

(4)调用jpeg enc方法生成图片

3 具体修改:参考如下:

(1)VendorShot::doCapture()

图片.png

(2)VendorShot::createYUVFrame()

这个方法会调用SingleShot or burstShot来拍照。在只获取一张图像的时候使用single ,在获取多张或者需要获取不同EV的图像时,需要使用burstShot

SingleShot获取一张图像

图片.png

BurstShot来获取多张图像

图片.png

通过BurstShot来获取不同的EV的图像


图片.png

10.4.2对****ZSD 做****第三方算法处理:****(****CapBufShot.cpp****)

1.单张YUV

图片.png

主要流程为:

(1)申请YUV BUFFER

(2)向底层singleshot注册申请好的YUV Buffer

(3)从BUF QUE中获取到RAW DATA的BUF

(4)调用singleshot相关接口将RAW DATA转化为YUV

(5)进行3方算法处理

(6)调用jpeg encode方法生成jpeg和thumbnail

多张连续YUV处理只是申请多块YUV buffer 调用多次singleshot其余都一样

图片.png

2.获取不同EV的YUV方法

获取多张不同的EV的yuv data需要先将ZSD preview缓存的buffer修改为不同的EV的raw buffer,其他步骤与case2相同。将缓存的raw buffer 修改为不同的EV的主要步骤如下:

-修改3A相关文件,提供设置不同EV的接口;

-修改DefaultCtrlNode新增precap_for_zsd()函数,在函数中调用设置不同EV的3A接口;

-修改MtkDefaultCamAdapter,针对ZSD拍照,在capture之前调用DefaultCtrlNode的precap_for_zsd()函数。

3.获取不同焦距的YUV方法

获取多张不同的EV的yuv data需要先将ZSD preview缓存的buffer修改为不同的焦距的raw buffer,其他步骤与case2相同。

10.5 新增一个Capture Mode

在如下文件添加相应参数:

图片.png

11.拍照、录像接口调用及开发

Android中Camera的使用,一是拍照,二是摄像,由于Android提供了强大的组件功能,为此对于在Android手机系统上进行 Camera的开发,可以使用两类方法:一是借助Intent和MediaStroe调用系统Camera App程序来实现拍照和摄像功能,二是根据Camera API自写Camera程序。

11.1 调用系统Camera APP实现功能

第三方APP用到Camera的需求就是获取照片或者视频,比如社交SNS应用、随手记、短信等,借助Android系统强大的组件特性,使得应用开发者只需通过Intent就可以方便的打开系统自带的Camera APP,并通过MediaStroe方便地获取照片和视频的文件路径。

11.1.1 **实现拍照 **

在第三方应用的菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,实现如下:

imgPath = "/sdcard/test/img.jpg"; //必须确保文件夹路径存在,否则拍照后无法完成回调

File vFile = new File(imgPath);

if(!vFile.exists()) {

File vDirPath = vFile.getParentFile(); //new File(vFile.getParent());

vDirPath.mkdirs();

}

Uri uri = Uri.fromFile(vFile);

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);//

startActivityForResult(intent, SystemCapture);

上面使用的是startActivityForResult,所以最好需要重载void onActivityResult(int requestCode, int resultCode, Intent data)方法,不过因为已经传入文件路径的的原因,data返回参数是null值,只要resultCode为RESULT_OK,则上述代码中 /sdcard/test/img.jpg的图片文件就是最新的照片文件。所以只需使用如下简单代码,将其显示到ImageView中

if (resultCode == RESULT_OK) {

iViewPic.setImageURI(Uri.fromFile(new File(imgPath)));

}

假设不传参数MediaStore.EXTRA_OUTPUT的情况下,onActivityResult函数在resultCode为RESULT_OK的情况下,data返回的参数是经过实际拍摄照片经过缩放的图像数据,可以通过类似如下方法来打印缩放图像的尺寸

if (resultCode == RESULT_OK) {

Bitmap bmp = (Bitmap)data.getExtras().get("data");

}

假如仅仅是调用系统照相机拍照,不关心拍照结果,则可以使用如下代码

Intent intent = new Intent(); //调用照相机

intent.setAction("android.media.action.STILL_IMAGE_CAMERA");

startActivity(intent);

对于设置MediaStore.EXTRA_OUTPUT的方法,除了设定的路径下有照片外,在手机存储卡上也会保存一份照片,默认目录为sdcard/dcim/camera下面。

11.1.2 **实现摄像 **

第三方应用需要摄像功能时,通过设置MediaStore.EXTRA_OUTPUT以传入类似拍照时的文件路径,得到视频文件是一个0k的空文件,说明不能通过同样的方法调用视频拍摄功能。可以通过如下代码实现

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//参数设置可以省略

startActivityForResult(intent, SystemVideoRecord);

在onActivityResult函数中进行如下代码调用

Uri videoUri = data.getData();

//String[] projection = { MediaStore.Video.Media.DATA, MediaStore.Video.Media.SIZE };

Cursor cursor = managedQuery(videoUri, null, null, null, null);

cursor.moveToFirst();//这个必须加,否则下面读取会报错

int num = cursor.getCount();

String recordedVideoFilePath

= cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA));

int recordedVideoFileSize

= cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));

iResultText.setText(recordedVideoFilePath);

返回参数data,也会因为是否设置MediaStore.EXTRA_OUTPUT参数而改变,如果没有通过EXTRA_OUTPUT设置路径,data.getData返回的Uri为content://media/external/video/media/个数字,代表具体的记录号,通过managedQuery可以获取到路径,如果设置了EXTRA_OUTPUT (比如/sdcard/test.3gp),则 data.getData返回的Uri则为[file:///sdcard/test.3gp,但是该文件大小为0](file:///sdcard/test.3gp,但是该文件大小为0)。

11.2调用Camera API实现相应功能

通过调用系统Camera App实现拍照和摄像功能,虽然能够满足需求,但是毕竟自由度降低了,而且拍照的界面就是系统的样子,要实现个性的拍照可复杂的功能就需要根据SDK提供的Camera API来编写应用程序。

11.2.1添加权限

通过调用系统Camera App,不需要任何权限,因为权限已经在Camera APP中声名,但是如果使用Camera API,就必须在manifest清单文件中声明使用权限,通常由以下三项

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

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

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

一般拍照和摄像的时候需要写到sd卡上,所以还有一向权限声明如下

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

进行摄像时,需要音频录制和视频录制功能,所以还需要下面两项权限声明

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

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

另外使用Camera API拍照或摄像,都需要用到预览,预览就要用到SurfaceView,为此Activity的布局中必须有SurfaceView。

11.2.2****实现拍照功能

(1)在Activity的OnCreate函数中设置好SurfaceView,包括设置SurfaceHolder.Callback对象和SurfaceHolder对象的类型,具体如下

SurfaceView mpreview = (SurfaceView) this.findViewById(R.id.camera_preview);

SurfaceHolder mSurfaceHolder = mpreview.getHolder();

mSurfaceHolder.addCallback(this);

mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

(2)在SurfaceHolder.Callback的surfaceCreated方法中,使用Camera的Open方法打开摄像头硬件,这个API在 SDK 2.3之前,是没有参数的,2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,再通过 getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,假如摄像头开启成功则返回一个Camera对象,否则就抛出异常。

(3)开启成功的情况下,在SurfaceHolder.Callback的surfaceChanged函数中调用 getParameters方法得到已打开的摄像头的配置参数Parameters对象,如果有需要就修改对象的参数,然后调用 setParameters函数设置进去(SDK2.2以后,还可以通过Camera::setDisplayOrientation设置方向);

(4)同样在surfaceChanged方法中,通过Camera::setPreviewDisplay为摄像头设置SurfaceHolder对象,设置成功后调用Camera::startPreview方法开启预览功能,上面3,4两步的代码如下所示

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

//已经获得Surface的width和height,设置Camera的参数

Camera.Parameters parameters = camera.getParameters();

parameters.setPreviewSize(w, h);

List<Size> vSizeList = parameters.getSupportedPictureSizes();

for(int num = 0; num < vSizeList.size(); num++) {

Size vSize = vSizeList.get(num);

}

if(this.getResources().getConfiguration().orientation

!= Configuration.ORIENTATION_LANDSCAPE) {

//如果是竖屏

parameters.set("orientation", "portrait");

//在2.2以上可以使用

//camera.setDisplayOrientation(90);

} else {

parameters.set("orientation", "landscape");

//在2.2以上可以使用

//camera.setDisplayOrientation(0);

}

camera.setParameters(parameters);

try {

//设置显示

camera.setPreviewDisplay(holder);

} catch (IOException exception) {

camera.release();

camera = null;

}

//开始预览

camera.startPreview();

}

(5)如果要支持自动对焦功能,则在需要的时候,或者在上述surfaceChanged调用完startPreview方法后,可以调用 Camera::autoFocus方法来设置自动对焦回调函数,该步是可选操作,有些设备可能不支持,可以通过 Camera::getFocusMode函数查询。代码如下:

// 自动对焦

camera.autoFocus(new AutoFocusCallback() {

@Override

public void onAutoFocus(boolean success, Camera camera) {

if (success) {

// success为true表示对焦成功,改变对焦状态图像

ivFocus.setImageResource(R.drawable.focus2);

}

}

});

(6)在需要拍照的时候,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)方法来完成拍照,这个方法中有四个回调接口,ShutterCallback是快门按下的回调,在里面可以设置播放“咔嚓”声之类的操作,后面有三个PictureCallback接口,分别对应三份图像数据,分别是原始图像、缩放和压缩图像和JPG 图像,图像数据可以在PictureCallback接口的void onPictureTaken(byte[] data, Camera camera)中获得,三份数据相应的三个回调正好按照参数顺序调用,通常只关心JPG图像数据,此时前面两个PictureCallback接口参 数可以直接传null。

(7)每次调用takePicture获取图像后,摄像头会停止预览,如果需要继续拍照,则在上面的PictureCallback的onPictureTaken方法末尾,再次调用Camera::startPreview方法;

(8)在不需要拍照的时候,主动调用Camera::stopPreview方法停止预览功能,并且调用Camera::release方法释放 Camera,以便其他应用程序调用。SDK中建议放在Activity的Pause方法中,也可以放在surfaceDestroyed方法中,代码如下

// 停止拍照时调用该方法

public void surfaceDestroyed(SurfaceHolder holder) {

// 释放手机摄像头

camera.release();

}

以上是实现拍照程序的的流程,一般还可以还可以获取预览帧的图像数据,可以分别通过Camera::setPreviewCallback和 Camera::setOneShotPreviewCallback来设置每帧或下一帧图像数据的回调。

11.2.3实现摄像功能

摄像也是需要预览的,而且流程上与拍照流程的(1)~(4)步和最后一步(即步骤(8))是一样的,唯一不同的是(6)和(7)两个步骤,至于(5)自动对焦本身就是可选的,在摄像流程也没必要。

(6)开启视频录制,需要创建一个MediaRecorder对象,并调用Camera::unLock操作解锁摄像头,因为默认Camera都是锁定的,只有解锁后MediaRecorder等多媒体进程调用,并设置一些参数,然后调用MediaRecorder:: start开启录制,参考如下代码:

MediaRecorder mMediaRecorder = new MediaRecorder();

camera.unlock();

mMediaRecorder.setCamera(camera);

mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);

mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

mMediaRecorder.setProfile(mProfile);

mMediaRecorder.setMaxDuration(100000);//ms为单位

long dateTaken = System.currentTimeMillis();

Date date = new Date(dateTaken);

SimpleDateFormat dateFormat

= new SimpleDateFormat(getString(R.string.video_file_name_format));

String title = dateFormat.format(date);

String filename = title + ".3gp"; // Used when emailing.

String cameraDirPath = ImageManager.CAMERA_IMAGE_BUCKET_NAME;

String filePath = cameraDirPath + "/" + filename;

File cameraDir = new File(cameraDirPath);

cameraDir.mkdirs();

mMediaRecorder.setOutputFile(filePath);

try {

mMediaRecorder.prepare();

mMediaRecorder.start(); // Recording is now started

} catch (RuntimeException e) {

return;

}

(7)上面设置了最大间隔为100s,当100是视频录制结束,录制就会被停止,如果没有设时长和文件大小限制,那么通常需要调用MediaRecorder:: stop函数主动停止视频的录制,并将Camera对象通过lock函数继续加锁,代码如下

mMediaRecorder.stop();

mMediaRecorder.reset();

mMediaRecorder.release();

mMediaRecorder = null;

if(camera != null)

camera.lock();

之后的操作根据交互要么重新录制要么就释放Camera对象回到拍照流程的(8)步骤了。

12.语音拍照详解

目前,语音识别功能可以支持的应用有(phone,camera,Alarm 和voiceUnlock)在设置菜单里,这些项默认都是关闭的。目前支持的语言有(Chiese-Mandarin,Chiese-Taiwan, and English 并且默认为中文)。

图片.png

语言拍照控制所涉及到的VoiceUi包括:

1设置各个应用是否开启语言识别的开关。

2增加第三方应用支持设置。

3增加关键词支持。

4更改默认设置。

5更改默认开关。

6增加多语言支持。

7增加语言拍照和语言设置的关联。

12.1设置界面****应用****是否增加****开启****、****关闭语言识别的开关

去除和添加的方法

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voiceprocessinfo.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/1.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/2.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/3.xml

图片.png

keyword/1.xml

图片.png

keyword/2.xml

图片.png

keyword/3.xml

图片.png

12.****2****增加****第三方****应用****支持****设置

涉及到如下文件

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voiceprocessinfo.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/1.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/2.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/3.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/src/com/mediatek/voicecommand/ui/settings/VoiceUiResUtil.java

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res

1.****voiceprocessinfo.xml

图片.png

2.****keyword/1.xml

图片.png

3.****keyword/2.xml

图片.png

4.****keyword/3.xml

图片.png

5.****VoiceUiResUtil.java

图片.png

各个voicecommond界面的字段(修改增加)方法如下:

(1).

图片.png
图片.png

(2).icon

图片.png
图片.png

(3).title app name

图片.png
图片.png

(4).title

图片.png
图片.png

12.3增加第三方****应用****支持****的关键词

涉及文件:

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voiceprocessinfo.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/1.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/2.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/keyword/3.xml

/trunk/vendor/mediatek/proprietary/frameworks/base/voicecommand/cfg/command/camera

/trunk/vendor/mediatek/proprietary/frameworks/base/voicecommand/cfg/voicecommand.mk

详细介绍一下:

1.****voiceprocessinfo.xml

图片.png

2.****keyword/1.xml

图片.png

keyword/2.xml同keyword/1.xml

keyword/3.xml同keyword/1.xml

3.****/command/camera

图片.png

4.****voicecommand.mk

图片.png

12.4更改****默认设置

涉及文件

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voiceprocessinfo.xml

图片.png

12.****5****更改****默认****语言

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voicelanguage.xml

图片.png

12.6增加新****的语言支持

涉及文件

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voicelanguage.xml

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/assets/

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/cfg/command/

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/cfg/voicecommand.mk

1.****voicelanguage.xml

图片.png

2.****assets/

图片.png

3.****/cfg/command/

图片.png

4.****voicecommand.mk

图片.png

12.7增加语言****拍照****和****语言设置的关联

涉及文件:

/trunk/vendor/mediatek/proprietary/packages/apps/VoiceCommand/res/xml/voicecustomization.xml

图片.png

Note :如果这个设置成TRUE 了后面的defaultlanguage 不需要关注了

13.Camera Performance问题分析初步

13.1 Camera startup time

定义:从”Touch Camera Icon抬起手”到”屏幕上第一帧画面显示出来”的时间。

13.1.1Camera startup time 的不同模式差异分析

1.不同LCD size、全屏和非全屏的区别

从Camera Native和HAL层的角度看,他们会有不同的Preview Size,因此有不同的Preview Buffer大小,其它部分都一样。

2.横竖屏、解锁进入Camera的区别

主要是APP和Framework的行为差异(如AMS,WMS):原理上是启动Activity,onCreate()被执行的时间差异,Camera Native层以下的部分是相同的。

3.ZSD和Normal模式的主要区别

(1).start preview 的start阶段需要allocate 几块capture buffer (数量可以客制化,默认为3).

(2).config sensor时,driver在normal时call preview setting,zsd时call capture setting。

(3).zsd pewview的帧率可能会低一些。

4.zsd cc与ncc的主要区别

在start preview 的start阶段:zsd cc allocate的capture buffer 为3块pass2 yuv(capture size)和1块thumbnail yuv buffer(preview size)。而zsd ncc为allocate 3块pass1 raw(sensor full size)。

13.1.2Camera startup time 分析LOG

选取以下时间点的LOG(ZSD、Normal通用):

1.点击icon后手抬起来:AP_PROF:AppLaunch_dispatchPtr:Up

2.Camera AP开始create:parseIntent() mPickType

3.开始connect CameraService:CameraService::connect E

4.connect CameraService 结束:CameraService::connect X

5.CameraService start preview开始:startPreview(pid)

6. CameraService start preview结束:[onJandleStartPreview]-

(ZSD时LOG为(StateIdle)[onStartPreview]-status)

7.first frame post from CameraService:[handleReturnBuffers] Show frame

8.first frame available for AP :onFirstFrameArrived()

每两个时间点之间的时间命名如下,其中All是1->8总的时间。红色主要和AP有关,蓝色和底层CameraService有关,绿色和AP,低层和CameraService都有关系,但主要是AP。

图片.png

13.1.3Camera startup time 的参考数据

1.Normal Mode,竖屏多次反复进入Camera。

图片.png

第一栏的橙色数据为开机后第一次进入竖屏进入Camera时的数据。

2.ZSD NCC mode,竖屏多次反复进入Camera
图片.png

13.1.4Camera startup time各阶段爱客制化影响的主要因素

1.Connect CS

(1).LoadSound时间,若有改动拍照声音或录像声音文件,需要check此处:

查看main log:CameraService:[CameraService::loadSound]和最后一个

[CameraService::newMediaPlayer]-之间的时间间隔.

(2).Sensor上电时间 不同的Sensor,power on时间会有差异,查看kernel log:

[[kd_sensorlist]] [kdModulePowerOn]Profile = 16490.(单位为ns)

(3)Sensor driver open时间,不同的sensor,open的时间会有差异,

查看kernel log:[[kd_sensorlist]][SensorOpen]Profile = 16834. (单位为ns)

2.CS Start Preview

(1)Preview delay frame数量:

delay frames for 3A statistic(raw:3,yuv:由sensor driver设定)。

图片.png

delay frames for sensor stable.

由sensor driver 设定,一般在xxx_Sensor.c中

图片.png

查看main log:MtkCam/PrvCQT(21541)[delay] delay(init) 3A(3)+sensor(1).

(2).Preview fps,可查看log:[updateOne]P2之间的时间间隔。当preview达到30fps时,log:两个相邻[updateOne]P2的时间间隔为33ms.

(3).Driver preview(zsd时:capture)settings,不同的Sensor所需要设定寄存器数量不同,并且里面可能会增加一些delay.

可以查看log:[start]与[init]meSensorDev之间的间隔时间。

3.First Frame

Preview fps,可以查看log:[updateOne]P2之间的时间间隔。当preview达到30fps时,log:[upateOne]P2的时间间隔为33ms.

13.2 Shot to Shot/shutter delay

定义:

1.Shot2Shot:从”touch shutter button icon抬起手”到”回preview后屏幕上第一帧画面显示出来(shutter button再次被enable)”的时间。

2.Shutter delay:” touch shutter button icon抬起手时看到的那一帧”与”实际拍出来的图片”的时间差.

13.2.1 Shot to Shot/shutter delay分析LOG

1.Normal mode选取以下时间点的log

(1).点击icon后抬起手来:AP_PROF:AppLaunch_dispatchPtr:Up

(2).CameraService take picture start:takePicture(pid)

(3).Precapture start:[onHandlePreCapture]+ (YUV sensor会直接跳过)

(4).Stop preview start:[onHandleStopPreview] +

(5).Hal capture start:(StateIdle)[onCapture] +

(6).Create raw image end:createSensorRawImg]:(1-th)

(7).Create yuv image end:createYuvRawImg]: (2-th)

(8).Create jpeg image end:[handleJpegData] +

(9).Hal capture end:[ImpShot]-

(10).CameraService start preview开始:startPreview(pid)

(11).CameraService start preview结束:[onHandleStartPreview]-

(12).第一个preview frame被post:[handleReturnBuffers] Show frame

每两个时间点之间的时间命名如下,其中S2S是1->12总的时间,Shutter Delay是1->6的时间。红色主要和AP有关,蓝色和低层CameraService有关。

图片.png

2.ZSD NCC mode 选取以下时间点的log

(1).点击icon后抬起手来:AP_PROF:AppLaunch_dispatchPtr:Up

(2).CameraService take picture start:takePicture(pid)

(3).Precapture start:[onHandlePreCapture]+ (若不打闪,什么都不做)

(4).Stop preview start:[onHandleStopPreview] +

(5).Hal capture start:(StateIdle)[onCapture] +

(与Normal不同的地方,没有pass1)

(6).Create yuv image end:createYuvRawImg]: (2-th)

(7).Create jpeg image end:[handleJpegData] +

(8).Hal capture end:[ImpShot]-

(9).CameraService start preview开始:startPreview(pid)

(10).CameraService start preview结束:StateIdle)[onStartPreview]-status

(11).第一个preview frame被post:[handleReturnBuffers] Show frame

每两个时间点之间的时间命名如下,其中S2S是1->11总的时间,红色主要和AP有关,蓝色和低层CameraService有关。

图片.png

ZSD NCC是抓取存储下来的pass1 raw buffer经过pass2的处理去压jpeg,可以通过调整存储buffer的数量来改变shutter delay time.

3.ZSD CC mode选取以下时间点的LOG

(1).点击icon后抬起手来:AP_PROF:AppLaunch_dispatchPtr:Up

(2).CameraService take picture start:takePicture(pid)

(3).Precapture start:[onHandlePreCapture]+ (若不打闪,什么都不做)

(4).Hal capture start:(StatePreCapture)[onCapture] +

(与NCC不同的地方,直接压jpeg)

(5).Create jpeg image end:[handleJpegData] +

(6).Hal capture end:[ImpShot]-

(7).Camera:handleMessage:256

每两个时间点之间的时间命名如下,其中S2S是1->7总的时间,红色主要和AP有关,蓝色和低层CameraService有关。

图片.png

ZSD NCC是抓取存储下来的pass2 yuv buffer去压jpeg,可以通过调整存储buffer的数量来改变shutter delay time.

ZSD CC preview不停,Shot2Shot time 主要和拍照时的动画有关。

13.2.2参考数据

1.Normal Shot,竖屏多次拍照(Shot to Shot/shutter delay time)

图片.png

最后一栏的橙色数据为测试环境变暗时的拍照数据。(帧率变低,shot to shot 时间变长)

2.ZSD NCC Shot,竖屏多次拍照(Shot to Shot time)

图片.png

13.2.3各阶段受客制化影响的主要因素

1.Pre Capture(非ZSD mode,且为raw sensor)

Preview fps,可查看log:[updateOne] P2之间的时间间隔,preview达到30fps时,log:

[updateOne] P2的时间间隔为33ms.

2.Pass1(非ZSD mode)

(1).Driver capture settings,不同的sensor所需要设定的寄存数量不同,并且里面可能会有加一些delay,(在82/92上面main log已经没有打印出该信息,需要自行在sensor driver文件中计算capture setting的时间)

(2).Capture delay frame数量(driver设定)

图片.png

可查看main log:campipe/io:[skipFrame]+(u4SkipCount) = (1).

(3).Capture fps,可查看log:campipe/io(140):[start]-和campipe/io(140):[stop]+之间的时间间隔,这是收一个frame的时间。

3.CS Start Preview(Normal,ZSD NCC mode)

(1).Preview fps:

(2).Preview delay frame数量:

(3).Driver Preview setting.

4.First Frame(Normal,ZSD NCC mode)

Preview fps

13.3 main/sub sensor switch time

定义:从”touch camera switch icon 抬起手”到”切换后屏幕上第一帧画面显示出来”的时间。

13.3.1 Shot to Shot/shutter delay分析LOG

选取以下时间点的log:

(1).点击icon后抬起手来:AP_PROF:AppLaunch_dispatchPtr:Up

(2).CameraService开始stop preview:stopPreview(pid)

(3). CameraService stop preview结束:[onHandleStopPreview]-

(4).开始disconnect CameraService: disconnect E

(5). disconnect CameraService结束: disconnect X

(6).开始connect CameraService: CameraService :: connect E

(7). connect CameraService结束: CameraService :: connect X

(8). CameraService 开始start preview开始:startPreview(pid)

(9). CameraService start preview结束:[onStartPreview]-

(10).get到第一个preview frame:[handleReturnBuffers] Show frame

每两个时间点之间的时间命名如下,其中Main to Sub/Sub to Main是1->10总的时间。红色主要和AP有关,蓝色和低层CameraService有关,绿色和AP,低层和CameraService都有关系,但主要是AP。

图片.png

客制化影响sensor switch time的主要因素

同camera startup 相同。

13.3.2参考数据

相关信息:

图片.png
图片.png

上面数据为main to sub的切换。后两栏橙色部分时间有变长,主要是因为测试环境变暗,帧率下降。

13.4目前平台可优化的地方

13.4.1 Stop preview节省帧率

流程上在stop preview的地方有一处可以删掉,可节省一帧的时间(对shot to shot 和sensor switch的performance优化有效)

在VSSScenario.cpp

(mediatek/proprietary/platform/mt6582/hardware/mtkcam/core/hwscenario)

MBOOL VSSScenario::stop()

{

CAM_TRACE_NAME("VSSScen::stop");

FUNCTION_LOG_START;

//

PortID rPortID;

mapPortCfg(eID_Pass1Out, rPortID);

PortQTBufInfo dummy(eID_Pass1Out);

CAM_TRACE_BEGIN("CamIOPipe::dequeOutBuf");

mpCamIOPipe->dequeOutBuf(rPortID, dummy.bufInfo);//此行可删除

CAM_TRACE_END();

//

CAM_TRACE_BEGIN("CamIOPipe::stop");

if ( ! mpCamIOPipe->stop())

{

    MY_LOGE("mpCamIOPipe->stop() fail");

    CAM_TRACE_END();

    return MFALSE;

}

CAM_TRACE_END();

//

FUNCTION_LOG_END;

//

return MTRUE;

}

13.4.1拍照回显定格时间较长

有的项目感觉拍照时回显定格时间较久,shot to shot时间长,很晚才start preview,原因是mtk camera app会在jpeg call back之后delay 1秒多的时间才restart preview。对应代码如下:

在PhotoActor.java(packages/app/camera/src/com/android/camera/actor)中:

private PictureCallback mJpegPictureCallback = new PictureCallback() {

    ……

            if (previewFeature.isDelayRestartPreview()) {

                 long delay = IMAGE_DISPLAY_DURATION
  • mPictureDisplayedToJpegCallbackTime;

                   if (delay <= 0) {
    
                       restartPreview(true);
    
                   } else {
    
                       Message msg = mHandler.obtainMessage(RESTART_PREVIEW);
    
                       mHandler.sendMessageDelayed(msg, delay);
    
                   }
    
              } else {
    
                  restartPreview(true);
    
              }
    
     ……
    

};

若不想delay,把如上代码直接修改为restartPreview(true);即可.

14.HQ Camera整体优化方案介绍

本章节主要是针对HQ在Camera上的一个不断改进的计划.

14.1改进思路

1.从用户使用角度发掘Camera体验的改善点

2.Camera基础效果优化(持续进行中)

3.预览&拍照特效类优化

4.其他影响体验的细节优化

14.2用户需求(竞品机优势)

1.自拍功能(前摄)

– 大视角(80~85度)

– 进入自拍速度快

– 自拍的效果好

– 有各种美化和修饰功能

– 方便的拍照方式

2.主拍照功能

– 优秀的拍照效果

– 快速的对焦和拍照

– 针对不同场景的特定拍照模式

– 连拍功能和快速的连拍速度

14.3 Camera基础效果优化(持续进行中)

| |

改善思路和当前进展

|
|

Camera基础效果优化

|

1.Camera器件选型(sensor&Lens&马达。。)

2.精细化的调试标准

3.专业的调试实验室和调试小组人员

4.精品试点项目落地导入

5.发现并弥补华勤AVL厂商和行业顶尖供应商的技术及制程能力的差距(附AL900的改善报告)

|
|

当前进展

|

以各事业部的精品试产项目进行落地,从器件选型,调试和评测三部分保证camera图像的品质和质量。

|

14.4 软件特效类

| |

特效细节

|
|

自拍特效处理

|

特效类:

1.自拍实时美颜(前期)

2.自拍滤镜。

3.二次美颜(多级美颜:眼睛放大,痩脸,去红眼,去黑眼圈,去痘,人像滤镜)

4.美妆(待开发)

5.拼图+分享功能(明确需要前置和后置都需要)(待开发)

6.水印(预览实时水印+二次水印,开发中)

7.自拍快门的操作方式(确定采用:眨眼控制)

|
|

主照相机特效处理

|

  1. 拍摄模式(风景模式,夜景模式,全景模式)

2其他特效模式

3.评估拍完照片后,是否显示回放动作(不显示回放,从用户感觉上可以极大的提高拍照速度)

|

14.5其他提升体验的细节优化(进展)

|

ITEM

|

识别的改进项

|
|

明确前摄和夜拍选型要求

|

1.明确自拍的选型要求

|
|

2.明确夜拍效果提升的选型要求

|
|

提高对焦速度

|

MTK平台主相机的对焦速度和对焦准确性评估是否优化

1)对比MTK和高通平台的对焦精度的区别

2)对比HQ和竞品机的对焦速度和精度的差距

|
|

关注新的对焦技术PDAF(快速对焦)的导入进展

|
|

提高拍照速度

|

1)拍照时的快门声音速度需要优化,优化从触摸屏幕到拍照音响起的速度(减小延迟)

|
|

2)取消目前的拍照后的延时回显动作.

|
|

3)对于取消回显后,拍完照仍会“停顿一下”的问题,修改capture原理,改为“零延时拍照”

|

14.6前摄选型要求

| |

前摄选型要求

|
|

Camera选型

|

事业部

|

Sensor

|

Lens

|

项目

|
|

光圈

|

视角

|

调焦距离

|
|

200W

|

OV2355

|

F2.8

|

80度

|

30~100cm

| |
|

500W

|

OV5648

|

F2.4

|

80度

|

AL810已经导入

|
|

800W

|

OV8858

|

F2.0/F2.2

|

80度

|

AW950已经导入

|
|

调试重点

|

重点针对人脸及肤色进行调试和优化

|
|

特效及美颜模式

|

加入自拍相关的特效处理和自拍模式

|

14.7夜拍效果提升选型要求

|

内容

|

细节

|

要求

|

说明

|
|

Camera选型要求

|

Sensor

|

【5M】: 1/4inch 1.4um*1.4um

|

影响夜拍效果的主要因素是单位像素的感光面积(pixel size),面积越大,夜拍效果越好

|
|

【5M】: 1/5inch 1.12um*1.12um

|
|

【8M】: 1/4 inch: 1.12um*1.12um

|
|

【8M】: 1/3.2 inch: 1.4um1.4um【推荐】*

|
|

【8M】: 1/3inch: 1.5um1.5um(Iphone5s) 【8M】: 1/2.5inch: 1.5um1.5um(iphone6)

|
|

【13M】: 1/3.0inch: 1.12um1.12um【推荐】*

|
|

LENS

|

【13M】: F2.0+5P

              F2.2+6P(vivo/MI4/iphone6等)

|

影响夜拍效果最主要的因素是光圈,光圈越大,进光量越多,效果越好

|
|

【8M】: F2.0/F2.2+5P

|
|

调试重点

|

帧率

|

调低帧率可以增加每帧的曝光时间,提升夜拍效果

|

调试重点是ISO,帧率,曝光时间的组合调试,找出最佳的比例搭配。

|
|

ISO

|

ISO越高,灵敏度越高,夜拍会更明亮和流畅,但是同时噪点也会增加,要求不超过ISO800,

|

14.8提高拍照速度-修改原理及细节

CaptureAnimManager.java

private static final float CAPTURE_ANIM_DURATION = 700; // milliseconds.

修改为

private static final float CAPTURE_ANIM_DURATION = 10;//此处为拍照回显动画时间


photoactor.java中

public void onPictureTaken(final byte[] jpegData,final android.hardware.Camera camera) {

Message msg = mHandler.obtainMessage(RESTART_PREVIEW);

mHandler.sendMessageDelayed(msg, delay);

修改为

mHandler.sendMessageDelayed(msg, 0);//此处为拍照后相机取消延时重新预览


上层这个修改点:

原因为CMCC有测项要求拍照定格,因此mtk camera app会在jpeg callback之后delay 1 秒多的时间才restart preview。修改点是将这个delay去掉直接进行restart preview操作。入库版本可以保留这个延迟,量产版本取消延时,提升用户体验.//AL810(苏宁/华硕已经导入)

14.9优化暗光下MFLL和MBF功能阀值

图片.png
图片.png

暗光下,由于帧率降低,camera算法会拍四张照片进行降噪合成处理,所以时间会慢,通过优化暗光的启动阀值,来优化拍照速度

14.10 零延时拍照

图片.png

要求sensor的帧率达到 全尺寸 30帧. 没有30fps会有比较明显的拖尾现象.

满足“零延时”的sensor

|

像素

|

sensor

|

厂商

|

帧率

|

零延时是否满足

|
|

2M

|

OV2680

|

OV

|

30fps

|

|
|

GC2355

|

格科微

|

30fps

|

|
|

5M

|

OV5670

|

OV

|

30fps

|

|
|

Hynix551

|

Hynix

|

30fps

|

|
|

OV5648

|

OV

|

15fps

|

X

|
|

8M

|

OV8858

|

OV

|

30fps

|

|
|

OV8865

|

OV

|

30fps

|

|
|

S5K3H7

|

samsung

|

30fps

|

|
|

IMX179

|

sony

|

30fps

|

|
|

S5K4H5

|

samsung

|

30fps

|

|
|

13M

|

OV13850

|

OV

|

30fps

|

|
|

IMX214

|

sony

|

30fps

|

|

14.11下一步计划

|

序号

|

内容

|

进展

|
|

1

|

Camera基础效果优化

|

例行

|
|

2

|

拍照体验细节优化

|

自动对焦速度和精度的优化

|

方案已经完成,待项目调试导入.

预研并关注新的对焦技术的进展

|
|

夜拍效果的优化方案

|

确认夜拍选型和调试方案

|
|

3

|

拍照速度提高

|

1)拍照时的快门声音速度需要优化,优化从触摸屏幕到拍照音响起的速度(减小延迟)

|

AL810/AL820/SL830已经导入

|
|

4

|

2)取消目前的拍照后的延时回显动作.

|
|

5

|

3)对于取消回显后,拍完照仍会“停顿一下”的问题,修改capture原理,默认拍照模式改为“零延时拍照”

|

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

推荐阅读更多精彩内容