Android 13 相机抢占式策略代码解读

简单介绍

这篇文章主要介绍了在AOSP下,关于相机抢占部分的核心代码的讲解。在抢占机制下策略下,各种场景是如何处理摄像头冲突的。代码注释很多,对安卓一点不懂的小白也可以理解,后续会再进行补充,如有写错的地方还请各位业界大佬斧正!

背景

首先我们先理解一下,什么是抢占式优先权调度算法
所谓抢占式,就是系统把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当系统中出现一个新的就绪进程i 时,就将其优先权Pi与正在执行的进程j 的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i 进程投入执行。

Android 系统中,当一个Client去open Camera时,可能会存在有其他Client也在使用camera的情况,然后由于设备之间的冲突关系或者系统带宽资源因素的限制,client之间不得不进行一次角逐,将某些Client与其所连接的Camera断开以让出资源,给新的client连接。
实现位置位于Android Framework层
主要涉及的文件:

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

核心接口:handleEvictionsLocked

接口位置:/frameworks/av/services/camera/libcameraservice/CameraService.cpp

status_t CameraService::handleEvictionsLocked(const String8& cameraId, int clientPid,
        apiLevel effectiveApiLevel, const sp<IBinder>& remoteCallback, const String8& packageName,
        int oomScoreOffset, bool systemNativeClient,
        /*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;
    {
        // 首先判断API1的情况,通过mediaRecoder会产生client需要单独判断。如果相机已经打开
        // 就判断是否是API1并且binder相同,binder相同意味着是同一个进程,那么就可以正常打开,返回client,否则提示。
        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 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;
                    }
                }
            }
        }
        // 通过遍历CameraManager中的mClients列表遍历来获取所有打开相机的进程的pid
        std::vector<int> ownerPids(mActiveClientManager.getAllOwners());
        ownerPids.push_back(clientPid); // 将当前需要打开相机的进程的pid push到这个数组中

        std::vector<int> priorityScores(ownerPids.size()); // 优先级列表
        std::vector<int> states(ownerPids.size()); // 状态列表

        // 调用到了IProcessInfoService里面,这个里面会进行IPC机制调用到系统服务,来给这些进程进行优先级和状态的赋值
        status_t err = ProcessInfoService::getProcessStatesScoresFromPids(
                ownerPids.size(), &ownerPids[0], /*out*/&states[0],
                /*out*/&priorityScores[0]);
        if (err != OK) {
            ALOGE("%s: Priority score query failed: %d",
                  __FUNCTION__, err);
            return err;
        }

        // Update all active clients' priorities
        std::map<int, resource_policy::ClientPriority> pidToPriorityMap;
        // 将本地已经打开了摄像头的进程(不包含即将打开摄像头的进程)的优先级和状态赋值给一个新的集合pidToPriorityMap
        for (size_t i = 0; i < ownerPids.size() - 1; i++) { // -1是排除了即将打开camera的client进程
            pidToPriorityMap.emplace(ownerPids[i],
                    resource_policy::ClientPriority(priorityScores[i], states[i],
                            /* isVendorClient won't get copied over*/ false,
                            /* oomScoreOffset won't get copied over*/ 0));
        }
        mActiveClientManager.updatePriorities(pidToPriorityMap); // 更新clients的优先级并保存到mActiveClientManager

        // 根据cameraId从本地mCameraStates集合中获取要打开相机的状态信息(cost,conflict)
        auto state = getCameraState(cameraId);
        if (state == nullptr) {
            ALOGE("CameraService::connect X (PID %d) rejected (no camera device with ID %s)",
                clientPid, cameraId.string());
            return BAD_VALUE;
        }

        int32_t actualScore = priorityScores[priorityScores.size() - 1];
        int32_t actualState = states[states.size() - 1];

        // 将上面得到的client进程信息以及cameraState信息保存到ClientDescriptor对象中
        // 通过makeClientDescriptor接口获得
        clientDescriptor = CameraClientManager::makeClientDescriptor(cameraId,
                sp<BasicClient>{nullptr}, static_cast<int32_t>(state->getCost()),
                state->getConflicting(), actualScore, clientPid, actualState,
                oomScoreOffset, systemNativeClient);

        resource_policy::ClientPriority clientPriority = clientDescriptor->getPriority();

        // 通过和即将打开camera的进程的clientDescriptor 对象对比,判断本地已有的mClients列表中(已打开摄像头的)哪些进程是冲突的
        auto evicted = mActiveClientManager.wouldEvict(clientDescriptor); // 返回存放被驱逐的那个client的数组

        // 如果即将client在驱逐列表里,则连接拒绝,说明现有的client拥有更高的优先级
        if (std::find(evicted.begin(), evicted.end(), clientDescriptor) != evicted.end()) {
            ALOGE("CameraService::connect X (PID %d) rejected (existing client(s) with higher"
                    " priority).", clientPid);

            sp<BasicClient> clientSp = clientDescriptor->getValue();
            String8 curTime = getFormattedCurrentTime();
            auto incompatibleClients =
                    mActiveClientManager.getIncompatibleClients(clientDescriptor);

            String8 msg = String8::format("%s : DENIED connect device %s client for package %s "
                    "(PID %d, score %d state %d) due to eviction policy", curTime.string(),
                    cameraId.string(), packageName.string(), clientPid,
                    clientPriority.getScore(), clientPriority.getState());

            for (auto& i : incompatibleClients) {
                msg.appendFormat("\n   - Blocked by existing device %s client for package %s"
                        "(PID %" PRId32 ", score %" PRId32 ", state %" PRId32 ")",
                        i->getKey().string(),
                        String8{i->getValue()->getPackageName()}.string(),
                        i->getOwnerId(), i->getPriority().getScore(),
                        i->getPriority().getState());
                ALOGE("   Conflicts with: Device %s, client package %s (PID %"
                        PRId32 ", score %" PRId32 ", state %" PRId32 ")", i->getKey().string(),
                        String8{i->getValue()->getPackageName()}.string(), i->getOwnerId(),
                        i->getPriority().getScore(), i->getPriority().getState());
            }

            // Log the client's attempt
            Mutex::Autolock l(mLogLock);
            mEventLog.add(msg);

            auto current = mActiveClientManager.get(cameraId);
            if (current != nullptr) {
                // 找到了占用cameraId的进程,所以该cameraId设备正在被使用
                return -EBUSY; // CAMERA_IN_USE
            } else {
                // 没有找到,说明达到了打开Camera的最大数量
                return -EUSERS; // MAX_CAMERAS_IN_USE
            }
        }

        // 遍历驱逐列表,进行日志信息打印
        for (auto& i : evicted) {
            sp<BasicClient> clientSp = i->getValue();
            if (clientSp.get() == nullptr) {
                ALOGE("%s: Invalid state: Null client in active client list.", __FUNCTION__);

                // TODO: Remove this
                LOG_ALWAYS_FATAL("%s: Invalid state for CameraService, null client in active list",
                        __FUNCTION__);
                mActiveClientManager.remove(i);
                continue;
            }

            ALOGE("CameraService::connect evicting conflicting client for camera ID %s",
                    i->getKey().string());
            evictedClients.push_back(i);

            // Log the clients evicted
            logEvent(String8::format("EVICT device %s client held by package %s (PID"
                    " %" PRId32 ", score %" PRId32 ", state %" PRId32 ")\n - Evicted by device %s client for"
                    " package %s (PID %d, score %" PRId32 ", state %" PRId32 ")",
                    i->getKey().string(), String8{clientSp->getPackageName()}.string(),
                    i->getOwnerId(), i->getPriority().getScore(),
                    i->getPriority().getState(), cameraId.string(),
                    packageName.string(), clientPid, clientPriority.getScore(),
                    clientPriority.getState()));

            // Notify the client of disconnection
            clientSp->notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISCONNECTED,
                    CaptureResultExtras());
        }
    }

    // Do not hold mServiceLock while disconnecting clients, but retain the condition blocking
    // other clients from connecting in mServiceLockWrapper if held
    mServiceLock.unlock();

    // Clear caller identity temporarily so client disconnect PID checks work correctly
    int64_t token = CameraThreadState::clearCallingIdentity();

    // Destroy evicted clients
    for (auto& i : evictedClients) {
        // Disconnect is blocking, and should only have returned when HAL has cleaned up
        i->getValue()->disconnect(); // Clients will remove themselves from the active client list
    }

    CameraThreadState::restoreCallingIdentity(token);

    for (const auto& i : evictedClients) {
        ALOGV("%s: Waiting for disconnect to complete for client for device %s (PID %" PRId32 ")",
                __FUNCTION__, i->getKey().string(), i->getOwnerId());
        ret = mActiveClientManager.waitUntilRemoved(i, DEFAULT_DISCONNECT_TIMEOUT_NS);
        if (ret == TIMED_OUT) {
            ALOGE("%s: Timed out waiting for client for device %s to disconnect, "
                    "current clients:\n%s", __FUNCTION__, i->getKey().string(),
                    mActiveClientManager.toString().string());
            return -EBUSY;
        }
        if (ret != NO_ERROR) {
            ALOGE("%s: Received error waiting for client for device %s to disconnect: %s (%d), "
                    "current clients:\n%s", __FUNCTION__, i->getKey().string(), strerror(-ret),
                    ret, mActiveClientManager.toString().string());
            return ret;
        }
    }

    evictedClients.clear();

    // Once clients have been disconnected, relock
    mServiceLock.lock();

    // Check again if the device was unplugged or something while we weren't holding mServiceLock
    if ((ret = checkIfDeviceIsUsable(cameraId)) != NO_ERROR) {
        return ret;
    }

    *partial = clientDescriptor;
    return NO_ERROR;
}

冲突场景解决接口:wouldEvict

该接口位于文件:/frameworks/av/services/camera/libcameraservice/utils/ClientManager.h
最终会调用到ClientManager下的wouldEvictLocked接口

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
    // 判断即将打开camera的client的进程是否是空进程
    if (client == nullptr) {
        evictList.push_back(client);
        return evictList;
    }

    // 即将打开camera的client进程的相关信息
    const KEY& key = client->getKey();
    int32_t cost = client->getCost();
    ClientPriority priority = client->getPriority();
    int32_t owner = client->getOwnerId();

    // 当前所有cost消耗
    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) { // 遍历当前已打开的camera的进程以及相关信息
        const KEY& curKey = i->getKey();
        int32_t curCost = i->getCost();
        ClientPriority curPriority = i->getPriority();
        int32_t curOwner = i->getOwnerId();

        // 冲突条件
        // 1. 相同camera id
        // 2. 设备冲突
        bool conflicting = (curKey == key || i->isConflicting(key) ||
                client->isConflicting(curKey));

        if (!returnIncompatibleClients) {
            // Find evicted clients

            if (conflicting && owner == curOwner) { // 硬件设备冲突 同一个client 应用进程
                // Pre-existing conflicting client with the same client owner exists
                // Open the same device twice -> most recent open wins
                // Otherwise let the existing client wins to avoid behaviors difference
                // due to how HAL advertising conflicting devices (which is hidden from
                // application)
                if (curKey == key) { // 1. camera id相同, 相当于二次打开,将当前也就是第一次打开camera的进程驱逐,再进行第二次打开
                    evictList.push_back(i);
                    totalCost -= curCost;
                } else { // 2. camera id不同,将即将打开camera的client进程驱逐
                    evictList.clear();
                    evictList.push_back(client);
                    return evictList;
                }
            } else if (conflicting && curPriority < priority) { // 硬件设备冲突 不同client 应用进程
                // Pre-existing conflicting client with higher priority exists
                // 3. 将要打开camera的client进程的优先级小于先前打开camera的client优先级,则驱逐即将要打开camera的client进程
                evictList.clear();
                evictList.push_back(client);
                return evictList;
                // 4. 冲突 cost总消耗大于最大资源 且即将要打开的进程优先级高于已打开的,则驱逐已打开的
            } 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
            
            // 这里就不带即将要打开camera的那个client玩了,内部比较 已经存在的mclient列表
            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
    // 5. 即将打开camera的client进程加上已打开的进程总消耗cost大于最大资源,并且即将打开的优先级低于已打开的,则驱逐即将打开的
    if (totalCost > mMaxCost && highestPriorityOwner != owner) {
        evictList.clear();
        evictList.push_back(client);
        return evictList;
    }

    return evictList;

}

概括

具体的场景以及决策流程如下:

  1. 同一个logical camera设备(冲突),不同的应用进程,优先级低的驱逐。
  2. 同一个logical camera设备(冲突),同一个应用进程。意味着优先级是一样的。后来先得,则相当于二次打开,将之前的驱逐并释放占有的带宽资源,再第二次打开。
  3. 不同的logical camera设备,相同的应用进程。先来先得,即将要打开的驱逐掉,则让先前应用进程连接的camera设备保持连接。
  4. 不同的logical camera设备,不同的应用进程。如果系统带宽资源不足,比较两者的优先级,优先级高的抢占成功。反之,则与下一个进行对比。
  5. 基于4,遍历完所有应用进程之后,如果还是超过系统最大带宽资源,且当下应用进程的优先级不是最高,且则将驱逐列表里面已存在的应用进程驱逐,将当下应用进程添加进驱逐名单,意味着抢占失败。

其实以上关于摄像头抢占问题的处理是openCamera必走的一步,后面会
补上从openCamera->connectDevice->connectHelper->handleEvictionsLocked的过程
未完待续。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值