1. 背景
某设备 有多个应用需要使用摄像头。其中包括微信、视频通话、畅连等应用,当某个应用释放camera不及时,会导致其他应用出现问题。
Android API: 29
关键日志:
10-07 14:44:52.778 966 1073 D MotionDetectService : receive application state: {"cameraOpt":"on"}
10-07 14:44:52.778 966 1073 D MotionDetectService : on condition changed!!
10-07 14:44:52.779 966 1073 D CmccCameraMonitorManger: getMonitorStatus: isMonitorRunning:true
10-07 14:44:52.779 966 1073 D MotionDetectService : validate params: screen: true ,call enable: true ,video enable: true ,audio enable: true ,camera enable: false ,res:false
10-07 14:44:52.779 966 1073 D MotionDetectService : stop camera monitor now!!!
10-07 14:44:52.779 966 1154 D MotionDetectService : handleMessage: receive MSG_STOP_MOTION_DETECT
10-07 14:44:52.781 966 966 D CmccCameraMonitorManger: stopVideoMonitor() -> stopRecord()
10-07 14:44:52.781 966 966 D CameraMonitor.CameraVideoManager: stopRecord: stop record
// close camera session,耗时约150ms
10-07 14:44:52.781 966 966 D CameraMonitor.CameraVideoManager: stopRecord: prepare to close camera
// close camera,耗时约100ms
10-07 14:44:52.937 966 966 D CameraMonitor.CameraVideoManager: stopRecord: camera close !!!
// camera closed
10-07 14:44:53.028 319 543 I CameraService: disconnect: Disconnected client for camera 0 for PID 966
10-07 14:44:53.010 319 1360 I CameraService: CameraService::connect call (PID -1 "com.cmcc.jarvis", camera ID 0) for HAL version default and Camera API version 1
10-07 14:44:53.011 319 1360 W CameraService: CameraService connect called from same client, but with a different API level, evicting prior client...
10-07 14:44:53.011 319 1360 E CameraService: CameraService::connect X (PID 702) rejected (existing client(s) with higher priority).
10-07 14:44:53.011 341 5043 D MtkCam/Utils: {CamProfile}[device@3.2/internal/0::onCloseLocked] PipelineModel -: (1-th) ===> [start-->now: 64 ms] [last-->now: 64 ms]
10-07 14:44:53.011 319 1360 E CameraService: Conflicts with: Device 0, client package com.cmcc.motiondetect (PID 966, score -800, state 0)
10-07 14:44:53.015 341 5043 D mtkcam-AppStreamMgr: [0-BufferHandleCache::clear] streamId:00 + mBufferHandleMap.size#:6
10-07 14:44:53.016 702 5082 W CameraBase: An error occurred while connecting to camera 0: Status(-8): '7: connectHelper:1339: Higher-priority client using camera, ID "0" currently unavailable'
日志分析:
- com.cmcc.motiondetect释放camera:
14:44:53.010 com.cmcc.motiondetect收到{"cameraOpt":"on"},代表有其他应用需要camera,因此com.cmcc.motiondetect立即释放camera。
14:44:52.781,com.cmcc.motiondetect完成逻辑判断,开始释放CameraSession。至14:44:52.937完成释放,耗时约150ms。
14:44:52.937,com.cmcc.motiondetect完成CameraSession,开始释放CameraDevice。至10-07 14:44:53.028,CaneraDevice释放完成,耗时约100ms.
- Launcher请求Camera
14:44:52.778 Launcher下发请求{"cameraOpt":"on"},告知com.cmcc.motiondetect自己即将要使用Camera,com.cmcc.motiondetect需要及时释放Camera。
14:44:53.010 Launcher请求连接Camera
14:44:53.016 Launcher请求Camera失败
冲突: com.cmcc.motiondetect还未完成Camera释放,Launcher就请求Camera,Launcher无法请求到Camera。
2. Camera竞争机制:
2.1 前提:
当前有一个进程A正在占用Camera资源,有其他进程B也想要使用Camera资源。此时Android System需要判断Camera需要分配给哪个进程。
2.2 核心代码解析:
2.2.1 camera简要调用流程描述
Camera竞争的调用流程:
CameraService.connectDevice() -> CameraService.connectHelper() -> CameraService.handleEvictionsLocked() -> mActiveClientManager.wouldEvict() -> ClientManager.wouldEvictLocked()
其中wouldEvictLocked()是竞争机制的主要方法。
camera是可抢占资源,为方便理解,将当前使用camera的client称为占有者,想要请求使用camera的称为请求者。
-
2.2.2 核心方法解析
-
2.2.2.1 核心代码精读:
ClientManager.h
template<class KEY, class VALUE>
// ClientDescriptor用于记录占用camera的进程和对应的camera资源之间的关系
class ClientDescriptor final {
......
private:
// cameraId
KEY mKey;
VALUE mValue;
// 打开这个camera所占用的带宽
int32_t mCost;
// 与这个CameraID相冲突的CameraDevice的CameraId
std::set<KEY> mConflicting;
// 客户端的优先级,和客户端id:mOwnerId是对应的
// Client的优先级是由oom_score_adj和state组成的,两个值越小则client优先级越高。
// oom_score_adj: oom_score_adj是一个整数,它表示进程的内存使用情况,
// state: state是一个字符串,它表示进程的状态。
ClientPriority mPriority;
// 客户端pid
int32_t mOwnerId;
}; // class ClientDescriptor
class ClientManager {
// 默认的系统资源最大值
static constexpr int32_t DEFAULT_MAX_COST = 100;
// 定义一个动态数组,用于记录当前哪些client正在使用哪些camera
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>::wouldEvictLocked(
const std::shared_ptr<ClientDescriptor<KEY, VALUE>>& client,
bool returnIncompatibleClients) const {
// 定义一个动态数组evictList
std::vector<std::shared_ptr<ClientDescriptor<KEY, VALUE>>> evictList;
// 如果client为空,什么都不做,将当前client添加到evictList末尾,然后返回驱逐列表evictList
if (client == nullptr) {
evictList.push_back(client);
return evictList;
}
// 获取请求者client的cameraId、打开camera占用带宽、进程优先级、进程id等信息
const KEY& key = client->getKey();
int32_t cost = client->getCost();
ClientPriority priority = client->getPriority();
int32_t owner = client->getOwnerId();
// 计算累计消耗
int64_t totalCost = getCurrentCostLocked() + cost;
// 确定最高优先级的所有者的最近最长使用
// 初始化变量highestPriorityOwner=owner
int32_t highestPriorityOwner = owner;
// 初始化highestPriority = priority;
ClientPriority highestPriority = priority;
//遍历所有的mClients,找出最高优先级和这个优先级的进程号
for (const auto& i : mClients) {
ClientPriority curPriority = i->getPriority();
if (curPriority <= highestPriority) {
highestPriority = curPriority;
highestPriorityOwner = i->getOwnerId();
}
}
// 如果最高优先级等于请求者的的优先级
if (highestPriority == priority) {
// 如果传入客户端具有最高优先级,则将其切换为所有者,因为它是MRU(最近最常使用)
highestPriorityOwner = owner;
}
// 构建需要删除的客户端驱逐列表
for (const auto& i : mClients) {
// 从列表中拿出数据,获取当前占用camera的client的相关信息
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));
// 不返回不兼容的客户端
if (!returnIncompatibleClients) {
// 当前占用者和请求者有冲突,并且当前占用者的优先级>请求者优先级
if (conflicting && curPriority < priority) {
// 将请求者client丢进驱逐列表evictlist中,并且返回驱逐列表
evictList.clear();
evictList.push_back(client);
return evictList;
}
// 如果不满足以上条件,即 没有冲突 || 当前请求者的优先级>占用者优先级,接着做如下判断
// 如果 有冲突 || ((带宽累计消耗>最大消耗&&请求者消耗>0) &&
// (占用者优先级<=请求者优先级) && !(最高优先级进程==占有者进程&&请求者进程==占有者进程))
else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
(curPriority >= priority) &&
!(highestPriorityOwner == owner && owner == curOwner))) {
// 将占用者加入驱逐列表
evictList.push_back(i);
// 从总消耗中减去占用者消耗
totalCost -= curCost;
}
}
// 返回不兼容的客户端
else {
// 查找阻止请求者客户端被添加的客户端
// 如果 占用者优先级>请求者优先级 && (有冲突 || 总消耗>最大消耗 && 占用者消耗>0)
if (curPriority < priority && (conflicting || (totalCost > mMaxCost && curCost > 0))) {
// 那么当前占用者就是阻止请求者被添加的那个客户端(罪魁祸首),把当前占用者添加到驱逐列表
evictList.push_back(i);
}
}
}
// 如果需要返回冲突客户端,就在计算完成之后立即返回冲突列表
if (returnIncompatibleClients) {
return evictList;
}
// 总消耗过高,返回请求者,除非请求者是最高优先级
// 如果 总消耗>最大消耗 && 最高优先级不是请求者
if (totalCost > mMaxCost && highestPriorityOwner != owner) {
evictList.clear();
// 将请求者放进驱逐列表evictList末尾,然后返回驱逐列表
evictList.push_back(client);
return evictList;
}
// 返回驱逐列表
return evictList;
}
}
2.2.2.2 核心代码解析:
- ClientDescriptor类:
在计算那个进程可以抢到优先级之前,需要先定义一个类(结构体):ClientDescriptor。 这个结构体用于记录占用camera的进程和对应的camera资源之间的关系。其中最重要的几个属性为:
int32_t mCost;
std::set<KEY> mConflicting;
ClientPriority mPriority;
- 这三个属性含义如下:
-
-
mCost:打开camera所需要占用的带宽
-
mConflicting:与这个CameraID相冲突的CameraDevice的CameraID。一旦对两个logic camera所使用的物理sensor有交集,就说明这两个camera是有冲突的,那么这两者就是冲突设备,这两个logic camera无法同时工作。
-
mPriority:client优先级。标记client优先级,用于后续抢占camera计算中的比较。和客户端id:mOwnerId是对应的。 Client的优先级是由oom_score_adj和state组成的,两个值越小则client优先级越高。 oom_score_adj: oom_score_adj是一个整数,它表示进程的内存使用情况, state: state是一个字符串,它表示进程的状态。
-
- ClientManager类:
- ClientManager.wouldEvictLocked()计算camera的client是否能够拿到camera。其中for循环部分逻辑如下:
-
-
每次从以保存的camera占用列表mClients中取出一个clients,获取他的各种属性 并 判断是否和请求者是否有资源冲突
-
判断是否需要返回冲突客户端,如果不需要,则执行以下逻辑
- 如果请求者和占用着有冲突,并且请求者的优先级小于占有者的优先级,就把请求者添加进驱逐列表,并return。
- 否则,判断是否冲突或者(总消耗带宽大于最大带宽&&请求者优先级>=占有者&&占有者不是最高优先级),就把当前占有者放进驱逐列表,从总消耗里减去占有者消耗
-
需要返回冲突客户端,执行以下逻辑
- 当前占有者优先级大于请求者并且(冲突或者占用带宽过高),将当前占用者加入驱逐列表
-
2.2.3 详细调用流程
代码位置:/frameworks/av/services/camera/libcameraservice/CameraService.cpp
Status CameraService::connectDevice(
const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
const String16& cameraId,
const String16& clientPackageName,
int clientUid,
/*out*/
sp<hardware::camera2::ICameraDeviceUser>* device) {
ATRACE_CALL();
Status ret = Status::ok();
String8 id = String8(cameraId);
sp<CameraDeviceClient> client = nullptr;
ret = connectHelper<hardware::camera2::ICameraDeviceCallbacks,CameraDeviceClient>(cameraCb, id,
/*api1CameraId*/-1,
CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageName,
clientUid, USE_CALLING_PID, API_2,
/*legacyMode*/ false, /*shimUpdateOnly*/ false,
/*out*/client);
if(!ret.isOk()) {
logRejected(id, getCallingPid(), String8(clientPackageName),
ret.toString8());
return ret;
}
*device = client;
return ret;
}
template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
int api1CameraId, 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));
......
status_t err;
sp<BasicClient> clientTmp = nullptr;
std::shared_ptr<resource_policy::ClientDescriptor<String8, sp<BasicClient>>> partial;
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());
}
}
......
device = client;
return ret;
}
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) {
ATRACE_CALL();
status_t ret = NO_ERROR;
std::vector<DescriptorPtr> evictedClients;
DescriptorPtr clientDescriptor;
{
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);
if (current != nullptr) {
auto clientSp = current->getValue();
if (clientSp.get() != nullptr) { // should never be needed
if (!clientSp->canCastToApiClient(effectiveApiLevel)) {
ALOGW("CameraService connect called from same client, but with a different"
" API level, evicting prior client...");
} else if (clientSp->getRemote() == remoteCallback) {
ALOGI("CameraService::connect X (PID %d) (second call from same"
" app binder, returning the same client)", clientPid);
*client = clientSp;
return NO_ERROR;
}
}
}
}
......
// Find clients that would be evicted
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
if (std::find(evicted.begin(), evicted.end(), clientDescriptor) != evicted.end()) {
ALOGE("CameraService::connect X (PID %d) rejected (existing client(s) with higher"
" priority).", clientPid);
......
return -EBUSY;
}
......
return NO_ERROR;
}
/frameworks/av/services/camera/libcameraservice/CameraService.h
CameraClientManager mActiveClientManager;
/frameworks/av/services/camera/libcameraservice/utils/ClientManager.h
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) {
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;
}
// 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();
bool conflicting = (curKey == key || i->isConflicting(key) ||
client->isConflicting(curKey));
if (!returnIncompatibleClients) {
// Find evicted clients
if (conflicting && curPriority < priority) {
// Pre-existing conflicting client with higher priority exists
evictList.clear();
evictList.push_back(client);
return evictList;
} else if (conflicting || ((totalCost > mMaxCost && curCost > 0) &&
(curPriority >= priority) &&
!(highestPriorityOwner == owner && owner == curOwner))) {
// 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;
}