Android14 Camera-启动时Camera 的优先级

186 阅读8分钟

在Android中Camera启动也是有优先级的,在实际开发中可能有时需要将特定的APP的Camera优先级提高或者降低来实现一些特殊需求,下面就一起看学习一下Camera启动时的优先级策略吧,最后会介绍如何设置特定APP使用Camera 的优先级

优先级特性:

优先级高,可以防止Camera 被别的APP抢走,在其他App使用Camera时可以抢到使用权, 优先级低,可以被其他App抢走Camera使用权,在其他App使用Camera时无法获取到使用权

Camera 优先级策略

当有多个APP使用Camera时就用到了优先级策略, 这里需要通过优先级判断Camera 的使用权交给谁。在CameraService中启动过程中进行判断, 这里直接看相关调用函数,启动过程不介绍,不了解的请阅读《Android14 源码- Camera-API2-openCamera执行过程》 framework/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;
    {

        // 省略部无关代码
        // Get current active client PIDs
        // 1. 获取所有使用Camera 的pid , getAllOwners() 函数返回的是pid ,将当前app的pid 放到最后面,这个很重要,后面会用到
        std::vector<int> ownerPids(mActiveClientManager.getAllOwners()); 
        ownerPids.push_back(clientPid);

        // 2. 初始化大小
        std::vector<int> priorityScores(ownerPids.size());
        std::vector<int> states(ownerPids.size());

        // Get priority scores of all active PIDs
        // 3. 获取第一步中获取到的pid应用的priorityScore和state 值
        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;
        // 4. 将或取到的app 对应的priorityScore和state 放到map 中
        for (size_t i = 0; i < ownerPids.size() - 1; i++) {
            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));
        }
        // 5. 更新正在使用Camera的Prioritie
        mActiveClientManager.updatePriorities(pidToPriorityMap);
        
        // 省略部无关代码

        // 6. 获取最后一个 Score 和 State  , 这个是当前app 的数值
        int32_t actualScore = priorityScores[priorityScores.size() - 1];
        int32_t actualState = states[states.size() - 1];

        // Make descriptor for incoming client. We store the oomScoreOffset
        // since we might need it later on new handleEvictionsLocked and
        // ProcessInfoService would not take that into account.
        // 7 . 创建一个当前app的ClientDescriptor
        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();

        // Find clients that would be evicted
        // 8. 比较当前的 ClientDescriptor 是否需要被拒绝
        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
        // 9 . 如果当前app 的 ClientDescriptor 在evicted 中, 则返回异常code , CAMERA_IN_USE  , MAX_CAMERAS_IN_USE
        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);

            for (auto& i : incompatibleClients) {
                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());
            }

            auto current = mActiveClientManager.get(cameraId);
            if (current != nullptr) {
                return -EBUSY; // CAMERA_IN_USE
            } else {
                return -EUSERS; // MAX_CAMERAS_IN_USE
            }
        }

         // 省略部无关代码
    }

     // 省略部无关代码
    return NO_ERROR;
}

分析过程都写到代码块注释中了, 下面看一下mActiveClientManager.updatePriorities(pidToPriorityMap); framework/av/services/camera/libcameraservice/utils/ClientManager.h

template<class KEY, class VALUE, class LISTENER>
void ClientManager<KEY, VALUE, LISTENER>::updatePriorities(
        const std::map<int32_t,ClientPriority>& ownerPriorityList) {
    Mutex::Autolock lock(mLock);
    for (auto& i : mClients) {
        auto j = ownerPriorityList.find(i->getOwnerId());
        if (j != ownerPriorityList.end()) {
            i->setPriority(j->second);
        }
    }
}

遍历mClients中的每个连接,重新设置Priority, mClients是什么后面会介绍。 下面看一下核心的优先级是如何确定的,函数auto evicted = mActiveClientManager.wouldEvict(clientDescriptor);

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) {
        // 遍历当前正在使用的app的连接,比较Priority, 找到优先级最高的cline
        ClientPriority curPriority = i->getPriority();
        if (curPriority <= highestPriority) {
            highestPriority = curPriority;
            highestPriorityOwner = i->getOwnerId();
        }
    }

    // 如果最高优先级和当前app 的优先级相同, 将highestPriorityOwners设置为当前app
    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
    // 遍历正在使用app的 clinet 
    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));
        // returnIncompatibleClients 默认false 
        if (!returnIncompatibleClients) {
            // Find evicted clients
            // 有冲突并且是同一个app 
            if (conflicting && owner == curOwner) {
                // 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) {
                    // 打开了相同的 CameraId , 把i(i 是一个 ClientDescriptor)放到list中
                    evictList.push_back(i);
                    totalCost -= curCost;
                } else {
                    // 如果是不同的CameraId , 把当前的client(client 是一个 ClientDescriptor)放到list中
                    evictList.clear();
                    evictList.push_back(client);
                    return evictList;
                }
            } else if (conflicting && curPriority < priority) {
                // Pre-existing conflicting client with higher priority exists
                // 有冲突并且,当前app的priority大于已经打开的curPriority, 则会把当前的client(client 是一个 ClientDescriptor)放到list中
                evictList.clear();
                evictList.push_back(client);
                return evictList;
            } else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
                    (curPriority >= priority) &&
                    !(highestPriorityOwner == owner && owner == curOwner))) {
                    // 有冲突
                    // 或总的Cost 大于 最大的Cost 并且 已经打开的priority 大于当前打开app的priority 并且已经打开Camera的app和当前不是同一个app
                // 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;
}

上面这段代码主要两步:

  1. 判断是否有冲突
  2. 有冲突,
    1. 是不是同一个app的场景冲突
    2. priority 优先级冲突
    3. Cost 冲突

priority 优先级: 这里说一个priority优先级,这个priority哪里来的, priority是在app中获取到的,不同类型的应用priority会不同, 系统应用priority较高,普通应用priority一般默认为0,需要注意一下:priority 这个值越大优先级越低

Cost 代表相机的资源,每种设备都有一个最大值,实际使用中不能超过最大值

查缺补漏

mClients在上面介绍中一直出现,到底是个什么,下面一起从源码中找一下 framework/av/services/camera/libcameraservice/utils/ClientManager.h

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>::addAndEvict(
        const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client) {
    // 省略部分代码
    mClients.push_back(client);
    mRemovedCondition.broadcast();

    return evicted;
}

mClients是一个ClientDescriptor集合,看到这里就知道为什么上面代码中提到的iclientClientDescriptor了吧。 在调用addAndEvict函数将ClientDescriptor添加到mClients中, 那么这个函数是怎么调用过来的呢 framework/av/services/camera/libcameraservice/CameraService.cpp

template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
        int api1CameraId, const String16& clientPackageNameMaybe, bool systemNativeClient,
        const std::optional<String16>& clientFeatureId, int clientUid, int clientPid,
        apiLevel effectiveApiLevel, bool shimUpdateOnly, int oomScoreOffset, int targetSdkVersion,
        bool overrideToPortrait, bool forceSlowJpegMode,
        /*out*/sp<CLIENT>& device) {
        // 省略部分代码
    {
        // 省略部分代码
        if ((err = handleEvictionsLocked(cameraId, originalClientPid, effectiveApiLevel,
                IInterface::asBinder(cameraCb), clientName8, oomScoreOffset, systemNativeClient,
                /*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());
                case -EUSERS:
                    return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
                            "Too many cameras already open, cannot open camera \"%s\"",
                            cameraId.string());
                default:
                    return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
                            "Unexpected error %s (%d) opening camera \"%s\"",
                            strerror(-err), err, cameraId.string());
            }
        }

        // 省略部分代码
        if (shimUpdateOnly) {
            // If only updating legacy shim parameters, immediately disconnect client
            mServiceLock.unlock();
            client->disconnect();
            mServiceLock.lock();
        } else {
            // Otherwise, add client to active clients list
            finishConnectLocked(client, partial, oomScoreOffset, systemNativeClient);
        }

        client->setImageDumpMask(mImageDumpMask);
        client->setStreamUseCaseOverrides(mStreamUseCaseOverrides);
        client->setZoomOverride(mZoomOverrideValue);
    } // lock is destroyed, allow further connect calls

   // 省略部分代码

    return ret;
}

CameraService::connectHelper函数调用了handleEvictionsLockedfinishConnectLocked两个函数,handleEvictionsLocked不介绍了上面说了, 那么看一下finishConnectLocked函数

void CameraService::finishConnectLocked(const sp<BasicClient>& client,
        const CameraService::DescriptorPtr& desc, int oomScoreOffset, bool systemNativeClient) {

    // Make a descriptor for the incoming client
    auto clientDescriptor =
            CameraService::CameraClientManager::makeClientDescriptor(client, desc,
                    oomScoreOffset, systemNativeClient);

    clientDescriptor->getValue().get()->mClientPackageName

    auto evicted = mActiveClientManager.addAndEvict(clientDescriptor);
     // 省略部分代码
}

这里面创建一个ClientDescriptor然后调用addAndEvict函数将ClientDescriptor 设置到mClients

定制特定APP使用Camera优先及方法

现在我们知道了打开相机优先级是怎么回事之后就容易了, 如果我们想让指定的APP一直享有Camera的使用权,那么就让该App有较高的优先级就可以了,反之则有较低优先级。直接上代码吧

示例代码

framework/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;
    {

        // 省略部无关代码
        // Get current active client PIDs
        std::vector<int> ownerPids(mActiveClientManager.getAllOwners()); 
        ownerPids.push_back(clientPid);

        std::vector<int> priorityScores(ownerPids.size());
        std::vector<int> states(ownerPids.size());

        // Get priority scores of all active PIDs
        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;
        }
        // 第一步:打开时,修改指定app的优先级
        if(strcmp("AppPackageName", packageName) == 0) {
            priorityScores[priorityScores.size() - 1] = 1000;
        }

        // Update all active clients' priorities
        std::map<int,resource_policy::ClientPriority> pidToPriorityMap;
        for (size_t i = 0; i < ownerPids.size() - 1; i++) {
            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);
        
        // 第二步:获取到所有已经打开的ClientDescriptor, 遍历找到特定的app 在次修改优先级
        for (auto& i : mActiveClientManager.getAll()) {
            auto clientSp = i->getValue();
            if (strcmp("AppPackageName", String8(clientSp->mClientPackageName)) == 0) {
                i->setPriority(resource_policy::ClientPriority(1000, i->getPriority().getState(),
                        /* isVendorClient won't get copied over*/ false,
                        /* oomScoreOffset won't get copied over*/ 0));
            }
        }

        // Get state for the given cameraId
        auto state = getCameraState(cameraId);
        if (state == nullptr) {
            ALOGE("CameraService::connect X (PID %d) rejected (no camera device with ID %s)",
                clientPid, cameraId.string());
            // Should never get here because validateConnectLocked should have errored out
            return BAD_VALUE;
        }

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

         // 省略部无关代码
    }

     // 省略部无关代码
    return NO_ERROR;
}

上面代码中AppPackageName是要指定的app包名。注释中,第一步打开的时候调整优先级, 第二步是在有多个app竞争的时候保持第一次打开时设置的优先级,如果没有第二步,当有多个app竞争的时候,打开时设置的优先级将被重置,无法一直延续