Android Camera API和HAL版本对应关系

前言

Android在版本更新和迭代过程中, Camera相关代码也经历了几个版本的更新, 主要表现为Camera HAL版本更新(HAL1 -> HAL2 -> HAL3), Camera API版本更新(Camera API1 -> Camera API2), 由于Android版本是向后兼容的, 所以为了解决兼容问题, 势必要做一些特殊的处理来保证不同版本调用问题, 本文主要说明 Camera HAL1, Camera HAL3, API1, API2之间的调用关系, 由于Camera HAL2只是一个过渡版本, 并且实际上并没有使用, 所以不做讨论.

说明: Camera API1泛指 android.hardware.camera 包下的相关API, Camera API2泛指
android.hardware.camera2 包下的相关API.

HAL版本和Java API版本是否能交叉调用?

由于HAL版本有HAL1和HAL3, Java API有 API1和API2. 两两组合调用就要4种方式, 那么这4种方式在Android中是否都有其使用场景呢?
答案是肯定的, 即4种组合(API1 -> HAL1, API1 -> HAL3, API2 -> HAL1, API2 -> HAL3)在Android系统中都能正常调用, 根据Android系统HAL层支持情况和API使用情况不同, 就会出现上述四种组合的使用场景, 下面就具体介绍一下4种组合是在那种情况下使用到的, 以及基本实现原理.

版本转换以及代码位置

对于Google最初的设计初衷, Camera API1就是用来调用HAL1的, Camera API2则是用来调用HAL3的, 但由于Android碎片化严重, 加上要兼容版本, 所以API1也可以调用HAL3, API2也可以调用HAL1,转换原理如下:

说明: 以下所有代码片段为高通平台, Android 7.1.1源码中的代码

API1调用HAL3

API1调用HAL3是在CameraService中完成转换, 使用了不同的Client(Client泛指CameraService和HAL层之间的接口, 有三个版本), 代码如下:

源码位置: frameworks/av/services/camera/libcameraservice/CameraService.cpp

代码:

Status CameraService::makeClient(const sp<CameraService>& cameraService,
        const sp<IInterface>& cameraCb, const String16& packageName, int cameraId,
        int facing, int clientPid, uid_t clientUid, int servicePid, bool legacyMode,
        int halVersion, int deviceVersion, apiLevel effectiveApiLevel,
        /*out*/sp<BasicClient>* client) {

    if (halVersion < 0 || halVersion == deviceVersion) {
        // Default path: HAL version is unspecified by caller, create CameraClient
        // based on device version reported by the HAL.
        switch(deviceVersion) {
          case CAMERA_DEVICE_API_VERSION_1_0:
            if (effectiveApiLevel == API_1) {  // Camera1 API route
                sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
                *client = new CameraClient(cameraService, tmp, packageName, cameraId, facing,
                        clientPid, clientUid, getpid(), legacyMode);
            } else { // Camera2 API route
                ALOGW("Camera using old HAL version: %d", deviceVersion);
                return STATUS_ERROR_FMT(ERROR_DEPRECATED_HAL,
                        "Camera device \"%d\" HAL version %d does not support camera2 API",
                        cameraId, deviceVersion);
            }
            break;
          case CAMERA_DEVICE_API_VERSION_3_0:
          case CAMERA_DEVICE_API_VERSION_3_1:
          case CAMERA_DEVICE_API_VERSION_3_2:
          case CAMERA_DEVICE_API_VERSION_3_3:
          case CAMERA_DEVICE_API_VERSION_3_4:
            if (effectiveApiLevel == API_1) { // Camera1 API route
                sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
                *client = new Camera2Client(cameraService, tmp, packageName, cameraId, facing,
                        clientPid, clientUid, servicePid, legacyMode);
            } else { // Camera2 API route
                sp<hardware::camera2::ICameraDeviceCallbacks> tmp =
                        static_cast<hardware::camera2::ICameraDeviceCallbacks*>(cameraCb.get());
                *client = new CameraDeviceClient(cameraService, tmp, packageName, cameraId,
                        facing, clientPid, clientUid, servicePid);
            }
            break;
          default:
            // Should not be reachable
            ALOGE("Unknown camera device HAL version: %d", deviceVersion);
            return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
                    "Camera device \"%d\" has unknown HAL version %d",
                    cameraId, deviceVersion);
        }
    } else {
        // A particular HAL version is requested by caller. Create CameraClient
        // based on the requested HAL version.
        if (deviceVersion > CAMERA_DEVICE_API_VERSION_1_0 &&
            halVersion == CAMERA_DEVICE_API_VERSION_1_0) {
            // Only support higher HAL version device opened as HAL1.0 device.
            sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
            *client = new CameraClient(cameraService, tmp, packageName, cameraId, facing,
                    clientPid, clientUid, servicePid, legacyMode);
        } else {
            // Other combinations (e.g. HAL3.x open as HAL2.x) are not supported yet.
            ALOGE("Invalid camera HAL version %x: HAL %x device can only be"
                    " opened as HAL %x device", halVersion, deviceVersion,
                    CAMERA_DEVICE_API_VERSION_1_0);
            return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,
                    "Camera device \"%d\" (HAL version %d) cannot be opened as HAL version %d",
                    cameraId, deviceVersion, halVersion);
        }
    }
    return Status::ok();
}

上述代码中基本判断逻辑是:

  1. 打开HAL版本没有特殊指定(即正常调用Camera.open()),或者指定的HAL版本和要打开的Camera默认的HAL版本相同, 则根据要打开的Camera的默认HAL版本创建Client, 此时,如果HAL层默认使用HAL1,则创建 CameraClient, 如果HAL层默认使用HAL3, 根据使用API进行判断, 使用API1则创建 Camera2Client, 使用API2则创建 CameraDeviceClient.
  2. 如果指定了一个特殊的HAL版本, 并且不是默认的HAL版本, 此时只会创建 CameraClient, 其他情况不支持, 原因注释里面也说明了.

上述几个Client代码位置为:

frameworks/av/services/camera/libcameraservice/api1/
frameworks/av/services/camera/libcameraservice/api2/

其中 CameraClient.cpp 是API调用HAL1的接口, Camera2Client.cpp 是API1调用HAL3接口, CameraDeviceClient 是API2调用HAL3接口, 是不是发现没有API2调用HAL1接口? 接下来就会讲这个了, 和上述转换有区别.

API2调用HAL1

在4种调用组合里面, 有3种是通过在CameraService中创建不同的Client来实现的, 只有API2调用HAL1是在API层面实现的,即把Camera API2的API调用转为Camera API1, 下面通过源码来看下使用API2调用HAL1时打开Camera操作是如何转换的.

Camera API2 打开Camera流程
frameworks/base/core/java/android/hardware/camera2/CameraManager.java

 public void openCamera(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
            throws CameraAccessException {

        openCameraForUid(cameraId, callback, handler, USE_CALLING_UID);
    }

调用 openCameraForUid

public void openCameraForUid(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler,
            int clientUid)
            throws CameraAccessException {

        if (cameraId == null) {
            throw new IllegalArgumentException("cameraId was null");
        } else if (callback == null) {
            throw new IllegalArgumentException("callback was null");
        } else if (handler == null) {
            if (Looper.myLooper() != null) {
                handler = new Handler();
            } else {
                throw new IllegalArgumentException(
                        "Handler argument is null, but no looper exists in the calling thread");
            }
        }

        openCameraDeviceUserAsync(cameraId, callback, handler, clientUid);
    }

调用 openCameraDeviceUserAsync

 private CameraDevice openCameraDeviceUserAsync(String cameraId,
            CameraDevice.StateCallback callback, Handler handler, final int uid)
            throws CameraAccessException {
//部分源码省略......
 ICameraDeviceUser cameraUser = null;
try {
    if (supportsCamera2ApiLocked(cameraId)) {
        // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
        ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
        if (cameraService == null) {
            throw new ServiceSpecificException(
                ICameraService.ERROR_DISCONNECTED,
                "Camera service is currently unavailable");
        }
        cameraUser = cameraService.connectDevice(callbacks, id,
                mContext.getOpPackageName(), uid);
    } else {
        // Use legacy camera implementation for HAL1 devices
        Log.i(TAG, "Using legacy camera HAL.");
        cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
    }
}
//部分源码省略......

上面的 supportsCamera2ApiLocked(cameraId) 就是判断是否支持HAL3, 如果不支持, 就调用
CameraDeviceUserShim.connectBinderShim(callbacks, id); 来获取
ICameraDeviceUser, 最后看下函数 connectBinderShim() 代码:

import android.hardware.Camera;

public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
                                                     int cameraId) {
    //部分代码省略...
    // TODO: Make this async instead of blocking
    int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
    Camera legacyCamera = init.getCamera();
    //部分代码省略...
    LegacyCameraDevice device = new LegacyCameraDevice(
            cameraId, legacyCamera, characteristics, threadCallbacks);
    return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
}

从上面import语句和 Camera legacyCamera = init.getCamera(); 就能看出来,此处是调用Camera API1接口来实现的, 所以Camera API2调用HAL1其实是在Framework层将API2接口转为了API1, 对CameraService来说, 就是使用API1来调用. 转换代码路径在: frameworks/base/core/java/android/hardware/camera2/legacy/, 有兴趣的可以看下.

各种版本调用使用场景

从上面的分析大家都了解了API和HAL之间不同版本转换关系, 之所以有这么多转换, 就是为了实现兼容, 因此下面简单说一下各个版本在什么场景下有用武之地.

  1. API1 调用 HAL1
    Android 5.0之前, 几乎所有手机都使用的场景,也是最开始Camera API1设计的初衷
  2. API1 调用 HAL3
    对于支持HAL3手机,并默认使用HAL3作为HAL的版本, 此种类型是手机想使用HAL3和Camera API2, 单由于没法限制第三方应用也使用Camera API2,而做出的一种兼容方式.
  3. API2 调用 HAL1
    手机不支持HAL3, 但应用使用了Camera API2, 常见于中低端手机, Android版本是5.0以上, 但底层不支持HAL3.
  4. API2 调用 HAL3
    Camera API2设计初衷就是调用HAL3的, 现在大多数中高端手机基本都是API2调用HAL3, 最大限度使用Camera提供的功能

总结

  • 不同API和HAL版本直接调用可简单总结为以下几点:
    API1 -> HAL1, 原始Camera流程, CameraService中使用的Client为 CameraClient
    API1 -> HAL3, 兼容使用API1的应用, CameraService中使用的Client为 Camera2Client
    API2 -> HAL1, 底层只支持HAL1, 为了兼容API2应用, 通过Framework层将API2转为API1实现
    API2 -> HAL3, 未来趋势, 提供更好的Camera控制能力, CameraService使用的Client为CameraDeviceClient
  • API1调用HAL3和API2调用HAL1对于应用来说, 都没有发挥HAL3和API2的功能, 只是一个兼容方案.

最后通过一张图片来直观明了的说明版本直接关系:


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

推荐阅读更多精彩内容