Android Camera抢占机制

964 阅读11分钟

1. 背景

某设备 有多个应用需要使用摄像头。其中包括微信、视频通话、畅连等应用,当某个应用释放camera不及时,会导致其他应用出现问题。

Android API: 29

关键日志:

10-07 14:44:52.778 966 1073 D MotionDetectService : receive application state: {"cameraOpt":"on"}

10-07 14:44:52.778 966 1073 D MotionDetectService : on condition changed!!

10-07 14:44:52.779 966 1073 D CmccCameraMonitorManger: getMonitorStatus: isMonitorRunning:true

10-07 14:44:52.779 966 1073 D MotionDetectService : validate params: screen: true ,call enable: true ,video enable: true ,audio enable: true ,camera enable: false ,res:false

10-07 14:44:52.779 966 1073 D MotionDetectService : stop camera monitor now!!!

10-07 14:44:52.779 966 1154 D MotionDetectService : handleMessage: receive MSG_STOP_MOTION_DETECT

10-07 14:44:52.781 966 966 D CmccCameraMonitorManger: stopVideoMonitor() -> stopRecord()

10-07 14:44:52.781 966 966 D CameraMonitor.CameraVideoManager: stopRecord: stop record

// close camera session,耗时约150ms

10-07 14:44:52.781 966 966 D CameraMonitor.CameraVideoManager: stopRecord: prepare to close camera

// close camera,耗时约100ms

10-07 14:44:52.937 966 966 D CameraMonitor.CameraVideoManager: stopRecord: camera close !!!

// camera closed

10-07 14:44:53.028 319 543 I CameraService: disconnect: Disconnected client for camera 0 for PID 966

10-07 14:44:53.010 319 1360 I CameraService: CameraService::connect call (PID -1 "com.cmcc.jarvis", camera ID 0) for HAL version default and Camera API version 1

10-07 14:44:53.011 319 1360 W CameraService: CameraService connect called from same client, but with a different API level, evicting prior client...

10-07 14:44:53.011 319 1360 E CameraService: CameraService::connect X (PID 702) rejected (existing client(s) with higher priority).

10-07 14:44:53.011 341 5043 D MtkCam/Utils: {CamProfile}[device@3.2/internal/0::onCloseLocked] PipelineModel -: (1-th) ===> [start-->now: 64 ms] [last-->now: 64 ms]

10-07 14:44:53.011 319 1360 E CameraService: Conflicts with: Device 0, client package com.cmcc.motiondetect (PID 966, score -800, state 0)

10-07 14:44:53.015 341 5043 D mtkcam-AppStreamMgr: [0-BufferHandleCache::clear] streamId:00 + mBufferHandleMap.size#:6

10-07 14:44:53.016 702 5082 W CameraBase: An error occurred while connecting to camera 0: Status(-8): '7: connectHelper:1339: Higher-priority client using camera, ID "0" currently unavailable'

日志分析:

  1. com.cmcc.motiondetect释放camera:

14:44:53.010 com.cmcc.motiondetect收到{"cameraOpt":"on"},代表有其他应用需要camera,因此com.cmcc.motiondetect立即释放camera。

14:44:52.781,com.cmcc.motiondetect完成逻辑判断,开始释放CameraSession。至14:44:52.937完成释放,耗时约150ms。

14:44:52.937,com.cmcc.motiondetect完成CameraSession,开始释放CameraDevice。至10-07 14:44:53.028,CaneraDevice释放完成,耗时约100ms.

  1. Launcher请求Camera

14:44:52.778 Launcher下发请求{"cameraOpt":"on"},告知com.cmcc.motiondetect自己即将要使用Camera,com.cmcc.motiondetect需要及时释放Camera。

14:44:53.010 Launcher请求连接Camera

14:44:53.016 Launcher请求Camera失败

冲突: com.cmcc.motiondetect还未完成Camera释放,Launcher就请求Camera,Launcher无法请求到Camera。

2. Camera竞争机制:

2.1 前提:

当前有一个进程A正在占用Camera资源,有其他进程B也想要使用Camera资源。此时Android System需要判断Camera需要分配给哪个进程。

2.2 核心代码解析:

2.2.1 camera简要调用流程描述

Camera竞争的调用流程:

CameraService.connectDevice() -> CameraService.connectHelper() -> CameraService.handleEvictionsLocked() -> mActiveClientManager.wouldEvict() -> ClientManager.wouldEvictLocked()

其中wouldEvictLocked()是竞争机制的主要方法。

camera是可抢占资源,为方便理解,将当前使用camera的client称为占有者,想要请求使用camera的称为请求者。

  • 2.2.2 核心方法解析

  •   2.2.2.1 核心代码精读:
ClientManager.h

template<class KEY, class VALUE>
// ClientDescriptor用于记录占用camera的进程和对应的camera资源之间的关系
class ClientDescriptor final {
......
private:
    // cameraId
    KEY mKey; 
    VALUE mValue;
    // 打开这个camera所占用的带宽
    int32_t mCost;
    // 与这个CameraID相冲突的CameraDevice的CameraId
    std::set<KEY> mConflicting;
    // 客户端的优先级,和客户端id:mOwnerId是对应的
    // Client的优先级是由oom_score_adj和state组成的,两个值越小则client优先级越高。
    // oom_score_adj: oom_score_adj是一个整数,它表示进程的内存使用情况,
    // state: state是一个字符串,它表示进程的状态。
    ClientPriority mPriority;
    // 客户端pid
    int32_t mOwnerId;
}; // class ClientDescriptor

class ClientManager {

// 默认的系统资源最大值
static constexpr int32_t DEFAULT_MAX_COST = 100;
// 定义一个动态数组,用于记录当前哪些client正在使用哪些camera
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>> mClients;

template<class KEY, class VALUE, class LISTENER>
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>>
ClientManager<KEY, VALUE, LISTENER>::wouldEvictLocked(
        const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client,
        bool returnIncompatibleClients) const {

    // 定义一个动态数组evictList
    std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>> evictList;

    // 如果client为空,什么都不做,将当前client添加到evictList末尾,然后返回驱逐列表evictList
    if (client == nullptr) {
        evictList.push_back(client);
        return evictList;
    }
    
    // 获取请求者client的cameraId、打开camera占用带宽、进程优先级、进程id等信息
    const KEY& key = client->getKey();
    int32_t cost = client->getCost();
    ClientPriority priority = client->getPriority();
    int32_t owner = client->getOwnerId();
    
    // 计算累计消耗
    int64_t totalCost = getCurrentCostLocked() + cost;


    // 确定最高优先级的所有者的最近最长使用
    // 初始化变量highestPriorityOwner=owner
    int32_t highestPriorityOwner = owner;
    // 初始化highestPriority = priority;
    ClientPriority highestPriority = priority;
    //遍历所有的mClients,找出最高优先级和这个优先级的进程号
    for (const auto& i : mClients) {
        ClientPriority curPriority = i->getPriority();
        if (curPriority <= highestPriority) {
            highestPriority = curPriority;
            highestPriorityOwner = i->getOwnerId();
        }
    }
    
    // 如果最高优先级等于请求者的的优先级
    if (highestPriority == priority) {
        // 如果传入客户端具有最高优先级,则将其切换为所有者,因为它是MRU(最近最常使用)
        highestPriorityOwner = owner;
    }

    // 构建需要删除的客户端驱逐列表
    for (const auto& i : mClients) {
        // 从列表中拿出数据,获取当前占用camera的client的相关信息
        const KEY& curKey = i->getKey();
        int32_t curCost = i->getCost();
        ClientPriority curPriority = i->getPriority();
        int32_t curOwner = i->getOwnerId();

        // 当前占用者与请求者是否有冲突
        bool conflicting = (curKey == key || i->isConflicting(key) ||
                client->isConflicting(curKey));
        // 不返回不兼容的客户端
        if (!returnIncompatibleClients) {
            // 当前占用者和请求者有冲突,并且当前占用者的优先级>请求者优先级
            if (conflicting && curPriority < priority) {
                // 将请求者client丢进驱逐列表evictlist中,并且返回驱逐列表
                evictList.clear();
                evictList.push_back(client);
                return evictList;
            }
            //  如果不满足以上条件,即 没有冲突 || 当前请求者的优先级>占用者优先级,接着做如下判断
            
            //  如果 有冲突 ||  ((带宽累计消耗>最大消耗&&请求者消耗>0) && 
            // (占用者优先级<=请求者优先级) && !(最高优先级进程==占有者进程&&请求者进程==占有者进程))
            else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
                    (curPriority >= priority) &&
                    !(highestPriorityOwner == owner && owner == curOwner))) {
                // 将占用者加入驱逐列表
                evictList.push_back(i);
                // 从总消耗中减去占用者消耗
                totalCost -= curCost;
            }
        } 
        // 返回不兼容的客户端
        else {
            // 查找阻止请求者客户端被添加的客户端
            // 如果 占用者优先级>请求者优先级 && (有冲突 || 总消耗>最大消耗 && 占用者消耗>0)
            if (curPriority < priority && (conflicting || (totalCost > mMaxCost && curCost > 0))) {
                // 那么当前占用者就是阻止请求者被添加的那个客户端(罪魁祸首),把当前占用者添加到驱逐列表
                evictList.push_back(i);
            }
        }
    }

    // 如果需要返回冲突客户端,就在计算完成之后立即返回冲突列表
    if (returnIncompatibleClients) {
        return evictList;
    }

    // 总消耗过高,返回请求者,除非请求者是最高优先级
    // 如果 总消耗>最大消耗 && 最高优先级不是请求者
    if (totalCost > mMaxCost && highestPriorityOwner != owner) {
        evictList.clear();
        // 将请求者放进驱逐列表evictList末尾,然后返回驱逐列表
        evictList.push_back(client);
        return evictList;
    }
    // 返回驱逐列表
    return evictList;

   }
}
2.2.2.2 核心代码解析:
  1. ClientDescriptor类:

在计算那个进程可以抢到优先级之前,需要先定义一个类(结构体):ClientDescriptor。 这个结构体用于记录占用camera的进程和对应的camera资源之间的关系。其中最重要的几个属性为:

    int32_t mCost;
    
    std::set<KEY> mConflicting;
    
    ClientPriority mPriority;
  • 这三个属性含义如下:
    1. mCost:打开camera所需要占用的带宽

    2. mConflicting:与这个CameraID相冲突的CameraDevice的CameraID。一旦对两个logic camera所使用的物理sensor有交集,就说明这两个camera是有冲突的,那么这两者就是冲突设备,这两个logic camera无法同时工作。

    3. mPriority:client优先级。标记client优先级,用于后续抢占camera计算中的比较。和客户端id:mOwnerId是对应的。 Client的优先级是由oom_score_adj和state组成的,两个值越小则client优先级越高。 oom_score_adj: oom_score_adj是一个整数,它表示进程的内存使用情况, state: state是一个字符串,它表示进程的状态。

  1. ClientManager类:
  • ClientManager.wouldEvictLocked()计算camera的client是否能够拿到camera。其中for循环部分逻辑如下:
    1. 每次从以保存的camera占用列表mClients中取出一个clients,获取他的各种属性 并 判断是否和请求者是否有资源冲突

    2. 判断是否需要返回冲突客户端,如果不需要,则执行以下逻辑

      1. 如果请求者和占用着有冲突,并且请求者的优先级小于占有者的优先级,就把请求者添加进驱逐列表,并return。
      2. 否则,判断是否冲突或者(总消耗带宽大于最大带宽&&请求者优先级>=占有者&&占有者不是最高优先级),就把当前占有者放进驱逐列表,从总消耗里减去占有者消耗
    3. 需要返回冲突客户端,执行以下逻辑

      1. 当前占有者优先级大于请求者并且(冲突或者占用带宽过高),将当前占用者加入驱逐列表

2.2.3 详细调用流程

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

Status CameraService::connectDevice(
        const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
        const String16& cameraId,
        const String16& clientPackageName,
        int clientUid,
        /*out*/
        sp<hardware::camera2::ICameraDeviceUser>* device) {

    ATRACE_CALL();
    Status ret = Status::ok();
    String8 id = String8(cameraId);
    sp<CameraDeviceClient> client = nullptr;
    ret = connectHelper<hardware::camera2::ICameraDeviceCallbacks,CameraDeviceClient>(cameraCb, id,
            /*api1CameraId*/-1,
            CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageName,
            clientUid, USE_CALLING_PID, API_2,
            /*legacyMode*/ false, /*shimUpdateOnly*/ false,
            /*out*/client);

    if(!ret.isOk()) {
        logRejected(id, getCallingPid(), String8(clientPackageName),
                ret.toString8());
        return ret;
    }

    *device = client;
    return ret;
}


template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
        int api1CameraId, int halVersion, const String16& clientPackageName, int clientUid,
        int clientPid, apiLevel effectiveApiLevel, bool legacyMode, bool shimUpdateOnly,
        /*out*/sp<CLIENT>& device) {
        
    binder::Status ret = binder::Status::ok();
    String8 clientName8(clientPackageName);
    int originalClientPid = 0;
    ALOGI("CameraService::connect call (PID %d "%s", camera ID %s) for HAL version %s and "
            "Camera API version %d", clientPid, clientName8.string(), cameraId.string(),
            (halVersion == -1) ? "default" : std::to_string(halVersion).c_str(),
            static_cast<int>(effectiveApiLevel));
        ......
        status_t err;
        sp<BasicClient> clientTmp = nullptr;
        std::shared_ptr<resource_policy::ClientDescriptor<String8, sp<BasicClient>>> partial;
        if ((err = handleEvictionsLocked(cameraId, originalClientPid, effectiveApiLevel,
                IInterface::asBinder(cameraCb), clientName8, /*out*/&clientTmp,
                /*out*/&partial)) != NO_ERROR) {
            switch (err) {
                case -ENODEV:
                    return STATUS_ERROR_FMT(ERROR_DISCONNECTED,
                            "No camera device with ID "%s" currently available",
                            cameraId.string());
                case -EBUSY:
                    return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,
                            "Higher-priority client using camera, ID "%s" currently unavailable",
                            cameraId.string());
                default:
                    return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
                            "Unexpected error %s (%d) opening camera "%s"",
                            strerror(-err), err, cameraId.string());
            }
        }
    ......
    device = client;
    return ret;
}

status_t CameraService::handleEvictionsLocked(const String8& cameraId, int clientPid,
        apiLevel effectiveApiLevel, const sp<IBinder>& remoteCallback, const String8& packageName,
        /*out*/
        sp<BasicClient>* client,
        std::shared_ptr<resource_policy::ClientDescriptor<String8, sp<BasicClient>>>* partial) {
    ATRACE_CALL();
    status_t ret = NO_ERROR;
    std::vector<DescriptorPtr> evictedClients;
    DescriptorPtr clientDescriptor;
    {
        if (effectiveApiLevel == API_1) {
            // If we are using API1, any existing client for this camera ID with the same remote
            // should be returned rather than evicted to allow MediaRecorder to work properly.

            auto current = mActiveClientManager.get(cameraId);
            if (current != nullptr) {
                auto clientSp = current->getValue();
                if (clientSp.get() != nullptr) { // should never be needed
                    if (!clientSp->canCastToApiClient(effectiveApiLevel)) {
                        ALOGW("CameraService connect called from same client, but with a different"
                                " API level, evicting prior client...");
                    } else if (clientSp->getRemote() == remoteCallback) {
                        ALOGI("CameraService::connect X (PID %d) (second call from same"
                                " app binder, returning the same client)", clientPid);
                        *client = clientSp;
                        return NO_ERROR;
                    }
                }
            }
        }
        ......
        // Find clients that would be evicted
        auto evicted = mActiveClientManager.wouldEvict(clientDescriptor);

        // If the incoming client was 'evicted,' higher priority clients have the camera in the
        // background, so we cannot do evictions
        if (std::find(evicted.begin(), evicted.end(), clientDescriptor) != evicted.end()) {
            ALOGE("CameraService::connect X (PID %d) rejected (existing client(s) with higher"
                    " priority).", clientPid);
            ......
            return -EBUSY;
        }
    ......
    return NO_ERROR;
}

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

CameraClientManager mActiveClientManager;

/frameworks/av/services/camera/libcameraservice/utils/ClientManager.h

template<class KEY, class VALUE, class LISTENER>
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>>
ClientManager<KEY, VALUE, LISTENER>::wouldEvict(
        const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client) const {
    Mutex::Autolock lock(mLock);
    return wouldEvictLocked(client);
}

template<class KEY, class VALUE, class LISTENER>
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>>
ClientManager<KEY, VALUE, LISTENER>::wouldEvictLocked(
        const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client,
        bool returnIncompatibleClients) const {

    std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>> evictList;

    // Disallow null clients, return input
    if (client == nullptr) {
        evictList.push_back(client);
        return evictList;
    }

    const KEY& key = client->getKey();
    int32_t cost = client->getCost();
    ClientPriority priority = client->getPriority();
    int32_t owner = client->getOwnerId();

    int64_t totalCost = getCurrentCostLocked() + cost;

    // Determine the MRU of the owners tied for having the highest priority
    int32_t highestPriorityOwner = owner;
    ClientPriority highestPriority = priority;
    for (const auto& i : mClients) {
        ClientPriority curPriority = i->getPriority();
        if (curPriority <= highestPriority) {
            highestPriority = curPriority;
            highestPriorityOwner = i->getOwnerId();
        }
    }

    if (highestPriority == priority) {
        // Switch back owner if the incoming client has the highest priority, as it is MRU
        highestPriorityOwner = owner;
    }

    // Build eviction list of clients to remove
    for (const auto& i : mClients) {
        const KEY& curKey = i->getKey();
        int32_t curCost = i->getCost();
        ClientPriority curPriority = i->getPriority();
        int32_t curOwner = i->getOwnerId();

        bool conflicting = (curKey == key || i->isConflicting(key) ||
                client->isConflicting(curKey));

        if (!returnIncompatibleClients) {
            // Find evicted clients

            if (conflicting && curPriority < priority) {
                // Pre-existing conflicting client with higher priority exists
                evictList.clear();
                evictList.push_back(client);
                return evictList;
            } else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
                    (curPriority >= priority) &&
                    !(highestPriorityOwner == owner && owner == curOwner))) {
                // Add a pre-existing client to the eviction list if:
                // - We are adding a client with higher priority that conflicts with this one.
                // - The total cost including the incoming client's is more than the allowable
                //   maximum, and the client has a non-zero cost, lower priority, and a different
                //   owner than the incoming client when the incoming client has the
                //   highest priority.
                evictList.push_back(i);
                totalCost -= curCost;
            }
        } else {
            // Find clients preventing the incoming client from being added

            if (curPriority < priority && (conflicting || (totalCost > mMaxCost && curCost > 0))) {
                // Pre-existing conflicting client with higher priority exists
                evictList.push_back(i);
            }
        }
    }

    // Immediately return the incompatible clients if we are calculating these instead
    if (returnIncompatibleClients) {
        return evictList;
    }

    // If the total cost is too high, return the input unless the input has the highest priority
    if (totalCost > mMaxCost && highestPriorityOwner != owner) {
        evictList.clear();
        evictList.push_back(client);
        return evictList;
    }

    return evictList;

}