Android CameraService对多进程同时使用不同相机的限制分析

    之前在做相机应用时,发现只要有一个进程打开了相机,其他相机应用再次尝试打开相机,会提示“相机设备被占用”,“打开失败”的错误信息,即使是打开不同的相机。最近项目需要后台长时间运行一个相机作为监控机,还需要不影响三方相机应用的正常使用,即需要多个进程同时使用不同的相机,于是研究了下CameraService的源码,找到了CameraService限制多进程同时使用相机的限制。做下简单的修改就可实现: 多进程同时使用不同的相机
    先分析下CameraService打开相机时的权限相关的代码

1.CameraServiced打开相机设备的限制代码分析

我们直接从CameraService::connectHelper开始分析,至于其调用流程请参考Android Camera学习总结

//cameraCb类型为ICameraDeviceCallbacks
//cameraId打开相机的ID
//halVersion使用的HAL1/3
//clientPackageName 应用包名
//clientUid为USE_CALLING_UID
//clientPid 进程号:USE_CALLING_PID
//effectiveApiLevel 使用的API1/2
//legacyMode:false
//shimUpdateOnlyfalse
template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
        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));

    sp<CLIENT> client = nullptr;
    {
        // Acquire mServiceLock and prevent other clients from connecting
        //1)加这个锁的主要目的是防止不同应用同一时刻尝试打开相机,比如一个应用正在打开相机,另外一个应用也在尝试打开相机,这个时候就需要等待了
        std::unique_ptr<AutoConditionLock> lock =
                AutoConditionLock::waitAndAcquire(mServiceLockWrapper, DEFAULT_CONNECT_TIMEOUT_NS);

        if (lock == nullptr) {
            ALOGE("CameraService::connect (PID %d) rejected (too many other clients connecting)."
                    , clientPid);
            return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
                    "Cannot open camera %s for \"%s\" (PID %d): Too many other clients connecting",
                    cameraId.string(), clientName8.string(), clientPid);
        }

        // Enforce client permissions and do basic sanity checks
        //2)这个主要是对应用做一些基本的检查
        //包括:
        //1.检测UID是否可信
        //2.检测PID是否可信
        //3.检测是否有anddroid.permission.CAMERA权限
        //4.检测用户是否在mAllowedusers中,即检测是否是有效用户,
        //mAllowedusers的赋值实在CameraserviceProxy.java

        if(!(ret = validateConnectLocked(cameraId, clientName8,
                /*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {
            return ret;
        }

        sp<BasicClient> clientTmp = nullptr;
        std::shared_ptr<resource_policy::ClientDescriptor<String8, sp<BasicClient>>> partial;
        //3)handleEvictionsLocked是处理多进程互斥逻辑的地方,
        //多进程同时打开相机的互斥逻辑就是在这个函数实现。
        //我们重点分析下这个函数
        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());
            }
        }
        ....
        sp<BasicClient> tmp = nullptr;
        //上述三个检测过程全部通过后,开始创建对应的client
        if(!(ret = makeClient(this, cameraCb, clientPackageName, cameraId, facing, clientPid,
                clientUid, getpid(), legacyMode, halVersion, deviceVersion, effectiveApiLevel,
                /*out*/&tmp)).isOk()) {
            return ret;
        }
        client = static_cast<CLIENT*>(tmp.get());
       //真正去打开相机的函数
        err = client->initialize(mCameraProviderManager);


        if (shimUpdateOnly) {
          ...
        } else {
            // Otherwise, add client to active clients list
            //如果打开成功,则将这个client添加到active clients list中
            finishConnectLocked(client, partial);
        }
    } // lock is destroyed, allow further connect calls


    device = client;
    return ret;
}

下面分析CameraService::handleEvictionsLocked的实现:

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) {

    status_t ret = NO_ERROR;
    std::vector<DescriptorPtr> evictedClients;
    DescriptorPtr clientDescriptor;
    {
         //API1处理逻辑
        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);
            //current != nullptr说明,cameraId之前已经被打开了
            if (current != nullptr) {
                auto clientSp = current->getValue();
                if (clientSp.get() != nullptr) { // should never be needed
                    //检查下是不是API1
                    if (!clientSp->canCastToApiClient(effectiveApiLevel)) {
                         //说明cameraId已经使用API2打开了,现在又尝试使用API1打开cameraId,会关闭使用API2打开的client,下边会分析为啥会关闭
                        ALOGW("CameraService connect called from same client, but with a different"
                                " API level, evicting prior client...");
                    } else if (clientSp->getRemote() == remoteCallback) {
                        //remoteCallback相同说明是同一个应用。
                        //同一个应用尝试使用API1再次打开同一个cameraId,则直接返回直接已经创建的client
                        ALOGI("CameraService::connect X (PID %d) (second call from same"
                                " app binder, returning the same client)", clientPid);
                        *client = clientSp;
                        return NO_ERROR;
                    }
                }
            }
        }

        // Get current active client PIDs
        //获取所有正在使用相机的进程号保存到ownerPids vector中
        std::vector<int> ownerPids(mActiveClientManager.getAllOwners());
        //将当前正在打开相机的应用进程号也保存到ownerPids 中
        ownerPids.push_back(clientPid);
        //创建进程priorityScores,states ventor
        std::vector<int> priorityScores(ownerPids.size());
        std::vector<int> states(ownerPids.size());

        // Get priority scores of all active PIDs
        // 获取所有ownerPids中的进程priority和states
        status_t err = ProcessInfoService::getProcessStatesScoresFromPids(
                ownerPids.size(), &ownerPids[0], /*out*/&states[0],
                /*out*/&priorityScores[0]);


        // Update all active clients' priorities
        //将上一步获取的进程优先级更新到mActiveClientManager中
        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]));
        }
        mActiveClientManager.updatePriorities(pidToPriorityMap);

        // Get state for the given cameraId
        //获取cameraId相机的状态,Cameraservice启动的时候会获取所有相机的状态
        auto state = getCameraState(cameraId);
        //如果state为空,说明设备不存在该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;
        }

        // Make descriptor for incoming client
        //由当前正在打开相机的cameraId,priorityScores,states,clientPid等信息
        //创建ClientDescriptor对象,key=cameraId;value=null;
        //ClientDescriptor未填充client,因为还未makeclient
        clientDescriptor = CameraClientManager::makeClientDescriptor(cameraId,
                sp<BasicClient>{nullptr}, static_cast<int32_t>(state->getCost()),
                state->getConflicting(),
                //当前正在打开相机的priorityScores
                priorityScores[priorityScores.size() - 1],
                clientPid,
                //当前正在打开相机的states
                states[states.size() - 1]);

        // Find clients that would be evicted
        // 在mActiveClientManager查找和上一步创建的ClientDescriptor存在冲突的所有client
        // 如果有冲突的client,都保存到evicted队列中
        // evicted队列中的所有client都需要关闭正在使用的相机,
        // 如果当前正在打开相机的client也在该evicted,说明当前client不能打开相机
        // 接下来会重点分析该函数实现
        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
        // 检查当前正在打开相机的client是否在evicted中
        // 如果在,则说明当前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();
            //获取与当前client存在冲突的client
            auto incompatibleClients =
                    mActiveClientManager.getIncompatibleClients(clientDescriptor);
            //打印当前client信息
            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,
                    priorityScores[priorityScores.size() - 1],
                    states[states.size() - 1]);
           //打印与当前client冲突的client信息
            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());
            }
            ....
            return -EBUSY;
        }
   
        //关闭evicted中client正在使用的相机
        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());
             //将有效evicted的client push到evictedClients
            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,
                    priorityScores[priorityScores.size() - 1],
                    states[states.size() - 1]));

            // Notify the client of disconnection
            //通知evicted中client,相机马上就要关闭了
            clientSp->notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISCONNECTED,
                    CaptureResultExtras());
        }
    }

    ....

    // Destroy evicted clients
    //关闭evictedClients中的相机
    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
    }



    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,不会影响下次检查
    evictedClients.clear();

    // 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;
}

下面分析下wouldEvictLocked函数,分析什么情况下,两个进程打开相机会存在冲突。

//returnIncompatibleClients为默认值false
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;
    }
    //创建eviction list,这个队列就是存在冲突的client

    // 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();
        // 判断是否冲突:
        //1.camera id 相同,存在冲突
        //isConflicting检查底层是否存在冲突
        bool conflicting = (curKey == key || i->isConflicting(key) ||
                client->isConflicting(curKey));

        if (!returnIncompatibleClients) {
            // Find evicted clients
            //查找冲突client
            if (conflicting && curPriority < priority) {
                // Pre-existing conflicting client with higher priority exists
                //之前存在的client具有较高优先级,在将当前client添加到evictList中,
                //表示当前clieng不能打开相机
                evictList.clear();
                evictList.push_back(client);
                return evictList;
            } else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
                    (curPriority >= priority) &&
                    !(highestPriorityOwner == owner && owner == curOwner))) {
                //在满足下边的条件时,将正在使用相机的client添加到evictList,表示在打开当前相机时,
                //正在使用相机的client因为和正在打开相机的client冲突需要关闭正在使用相机的client使用的相机。
                //1)存在冲突,且正在使用相机的client优先级低
                //2) 不存在冲突,但是totalCost 大于最大允许值mMaxCost 
                //在当满足如下条件时也需要将正在使用相机的client添加到evictList:
                //1.如果正在打开相机的client和正在使用相机的client不是同一进程而且
                //正在打开相机的client优先级高于或者等于正在使用相机的client
    
                //2.如果正在打开相机的client和正在使用相机的client是同一进程
                //但是该进程不是最高优先级。

                // 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
    //如果经过上一步检测,totalCost还是超过最大允许值且当前client不具有最高优先级,则将当前client添加到evictList,
    //表示当前client不能打开相机
    if (totalCost > mMaxCost && highestPriorityOwner != owner) {
        evictList.clear();
        evictList.push_back(client);
        return evictList;
    }

    return evictList;
}

通过分析wouldEvictLocked,可以得出的结论是:

当正在打开相机应用对应的client和正在使用相机的activeClient存在如下情况的时候会存在冲突:

wouldEvicted = 
	client.cameraID==activeClient.cameraID
	||client.isConflicting(activeClient)
	||activeClient.isConflicting(client)
	||totalCost > mMaxCost
  1. 如果client进程优先级高于或等于activeClient进程优先级,则activeClient需要关闭其正在使用的相机。
  2. 如果client优先级低于activeClient进程优先级,则client不能打开相机

2. 多个进程如何能够同时使用不同相机的条件

从上述分析已经比较清楚了,多个进程如果能够同时使用不同相机
只需满足如下条件:

!wouldEvicted = 
	!(client.cameraID==activeClient.cameraID
	||client.isConflicting(activeClient)
	||activeClient.isConflicting(client)
	||totalCost > mMaxCost)

=
	(client.cameraID!=activeClient.cameraID
	&&!client.isConflicting(activeClient)
	&&!activeClient.isConflicting(client)
	&&totalCost <= mMaxCost)

由于我们项目isConflicting正好为false,但是totalCost > mMaxCost
我直接将mMaxCost稍微调大了一点,就实现了需要。

之前发现大家一直留言说不明白,我重新编辑了文章,现在应该好明白些了,如果还有疑问,建议仔细去分析下源码,源码是最好的教材。

你可能感兴趣的:(Android,camera)