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에 따라 라이센스가 부여됩니다.