Android Camera Service가 여러 프로세스에서 동시에 다른 카메라를 사용하는 것에 대한 제한 분석
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
위에서 설명한 바와 같이 여러 프로세스가 서로 다른 카메라를 동시에 사용할 수 있는 경우에는 다음 조건을 충족해야 합니다.
!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를 약간 크게 해서 수요를 실현했다.
그 전에 여러분이 계속 댓글을 남겨서 잘 모르겠다는 걸 알게 됐어요. 제가 글을 다시 편집했으니까 이제 좀 알 것 같아요. 궁금한 게 있으면 다음 원본을 자세히 분석해 보세요. 원본이 가장 좋은 교재예요.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.