Android Camera Service가 여러 프로세스에서 동시에 다른 카메라를 사용하는 것에 대한 제한 분석

84039 단어 Androidcamera
이전에 카메라 앱을 할 때 한 프로세스가 카메라를 켜면 다른 카메라 앱이 다시 카메라를 켜려고 시도하면 '카메라 장치가 점용되었다', '켜는 데 실패했다' 는 잘못된 정보를 알려 다른 카메라를 켜도 안 된다는 것을 발견했다.최근 프로젝트는 한 대의 카메라를 모니터로 장시간 운행해야 하기 때문에 삼자 카메라의 정상적인 사용에 영향을 주지 않아야 한다. 즉, 여러 프로세스가 서로 다른 카메라를 동시에 사용해야 한다는 것이다. 그래서 아래CameraService의 원본 코드를 연구하여 CameraService 여러 프로세스가 동시에 카메라를 사용하는 것을 제한하는 제한을 찾았다.간단한 수정을 하면 다중 프로세스가 서로 다른 카메라를 동시에 사용할 수 있다.먼저 CameraService 카메라를 열 때의 권한과 관련된 코드를 분석해 봅시다.
1.CameraServiced 카메라 장치 열기 제한 코드 분석
***
//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::connectHelper의 실현:
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("
- 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 ")
- 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:
%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:
%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; }

다음은 CameraService::handleEvictionsLocked 함수를 분석하고 어떤 상황에서 두 프로세스가 카메라를 켜면 충돌이 존재하는지 분석한다.
//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
  • 클라이언트 프로세스 우선순위가 activeClient 프로세스 우선순위보다 높거나 같으면 activeClient는 사용 중인 카메라를 닫아야 합니다.
  • client 우선순위가activeClient 프로세스 우선순위보다 낮으면 카메라를 열 수 없음
  • 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를 약간 크게 해서 수요를 실현했다.
    그 전에 여러분이 계속 댓글을 남겨서 잘 모르겠다는 걸 알게 됐어요. 제가 글을 다시 편집했으니까 이제 좀 알 것 같아요. 궁금한 게 있으면 다음 원본을 자세히 분석해 보세요. 원본이 가장 좋은 교재예요.

    좋은 웹페이지 즐겨찾기