前言
由前面的几篇博客可以知道,openCamera,createCaptureSession,setRepeatingRequest,capture是非常重要的过程,如果其中一个环节出了问题时该如何分析呢,这里我们首先从打开相机流程时,打开相机失败了的情况进行讲述说明。
注:
以下是需要说明的系统源码(源码皆来自谷歌Android12源码,如需要下载对应源码,可根据我之前的博客进行下载。
下载链接为:浅谈Android12系统源码下载
APP
在相机问题排查中,优先从 “上层”(应用层、框架层 API 使用)开始分析,是基于问题发生概率、排查成本、开发者可控性和逻辑递进关系的实践原则。
以下是简单的示例代码,具体使用请按api使用规范使用
// 打开相机
private void openCamera() {
if (mCameraId == null || mCameraManager == null) {
return;
}
try {
// 打开相机
mCameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(this, "打开相机失败", Toast.LENGTH_SHORT).show());
}
}
// 相机设备状态回调
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
// 相机打开成功,保存CameraDevice实例并创建预览会话
mCameraDevice = camera;
// 相机连接成功后开始配流操作
createCaptureSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
// 相机断连记得释放资源
camera.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
// 这里的回调就表示相机打开失败了,可以新增一个提示,告诉用户无法连接相机
mCameraDevice = null;
runOnUiThread(() -> Toast.makeText(Camera2PreviewActivity.this,
"相机打开失败: " + error, Toast.LENGTH_SHORT).show());
}
};
可以看到如果连接失败了,上层能通过回调很快就知道了连接失败的结果,可以加一下打印如果出现问题时可以快速分析定位是否连接过程出现问题了,甚至还可以根据error对应具体发生了什么问题,以下皆为示例代码
public class CameraErrorHandler {
private static final String TAG = "CameraErrorHandler";
// CameraDevice.StateCallback 实现,包含错误处理
public CameraDevice.StateCallback getStateCallback() {
return new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.d(TAG, "相机打开成功");
// 相机打开后的初始化操作(如创建会话)
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.w(TAG, "相机连接断开,关闭相机");
camera.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "相机错误,错误码: " + error);
// 解析错误原因
String errorMessage = getErrorMessage(error);
Log.e(TAG, "错误原因: " + errorMessage);
// 无论何种错误,都需要关闭相机释放资源
camera.close();
// 可根据错误类型执行不同的恢复策略
handleErrorByType(error);
}
};
}
// 解析错误码对应的具体原因
private String getErrorMessage(int error) {
switch (error) {
case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
return "相机正在被其他应用使用(已被占用)";
case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
return "已达到系统同时打开相机的最大数量";
case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
return "相机被系统禁用(如设备管理策略限制)";
case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
return "相机硬件设备发生故障";
case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
return "相机服务(cameraserver)崩溃或未响应";
case CameraDevice.StateCallback.ERROR_PERMISSION_DENIED:
return "没有相机权限(用户未授权)";
case CameraDevice.StateCallback.ERROR_TIMED_OUT:
return "操作超时(如打开相机超时)";
default:
return "未知错误(错误码: " + error + "),可能是设备特定错误";
}
}
// 根据错误类型执行不同的处理策略
private void handleErrorByType(int error) {
switch (error) {
case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
// 相机被占用:提示用户关闭其他应用
showUserMessage("相机正在被使用,请关闭其他应用后重试");
break;
case CameraDevice.StateCallback.ERROR_PERMISSION_DENIED:
// 权限被拒:引导用户去设置页开启权限
showUserMessage("请授予相机权限以使用相机功能");
navigateToPermissionSettings();
break;
case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
// 相机被禁用:提示用户检查设备设置
showUserMessage("相机已被系统禁用,请在设置中启用");
break;
case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
// 硬件或服务错误:尝试重启相机
showUserMessage("相机服务异常,正在尝试恢复...");
retryOpenCamera();
break;
default:
// 其他错误:通用提示
showUserMessage("相机使用失败,请稍后重试");
}
}
// 以下为辅助方法(实际使用时需根据应用场景实现)
private void showUserMessage(String message) {
// 显示Toast或Snackbar等用户提示
Log.d(TAG, "用户提示: " + message);
}
private void navigateToPermissionSettings() {
// 跳转到应用权限设置页
}
private void retryOpenCamera() {
// 重试打开相机的逻辑
}
}
为啥会回调onError继续往下看源码
java framework
我们知道打开相机会调用到下面的函数
frameworks/base/core/java/android/hardware/camera2/CameraManager.java
private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateCallback callback, Executor executor, final int uid,
final int oomScoreOffset) throws CameraAccessException {
try {
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new ServiceSpecificException(
ICameraService.ERROR_DISCONNECTED,
"Camera service is currently unavailable");
}
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
} else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE ||
e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE ||
e.errorCode == ICameraService.ERROR_DISABLED ||
e.errorCode == ICameraService.ERROR_DISCONNECTED ||
e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {
// Received one of the known connection errors
// The remote camera device cannot be connected to, so
// set the local camera to the startup error state
//这里回调onError或者onDisconnected
deviceImpl.setRemoteFailure(e);
}
} catch (RemoteException e) {
// Camera service died - act as if it's a CAMERA_DISCONNECTED case
ServiceSpecificException sse = new ServiceSpecificException(
ICameraService.ERROR_DISCONNECTED,
"Camera service is currently unavailable");
//这里回调onError或者onDisconnected
deviceImpl.setRemoteFailure(sse);
throwAsPublicException(sse);
}
// TODO: factor out callback to be non-nested, then move setter to constructor
// For now, calling setRemoteDevice will fire initial
// onOpened/onUnconfigured callbacks.
// This function call may post onDisconnected and throw CAMERA_DISCONNECTED if
// cameraUser dies during setup.
//这里会回调onOpened
deviceImpl.setRemoteDevice(cameraUser);
device = deviceImpl;
}
return device;
}
直接看deviceImpl.setRemoteFailure做了什么
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
public void setRemoteFailure(final ServiceSpecificException failure) {
int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
boolean failureIsError = true;
switch (failure.errorCode) {
case ICameraService.ERROR_CAMERA_IN_USE:
failureCode = StateCallback.ERROR_CAMERA_IN_USE;
break;
case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
break;
case ICameraService.ERROR_DISABLED:
failureCode = StateCallback.ERROR_CAMERA_DISABLED;
break;
case ICameraService.ERROR_DISCONNECTED:
failureIsError = false;
break;
case ICameraService.ERROR_INVALID_OPERATION:
failureCode = StateCallback.ERROR_CAMERA_DEVICE;
break;
default:
Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode +
failure.getMessage());
break;
}
final int code = failureCode;
final boolean isError = failureIsError;
synchronized(mInterfaceLock) {
mInError = true;
mDeviceExecutor.execute(new Runnable() {
@Override
public void run() {
if (isError) {
mDeviceCallback.onError(CameraDeviceImpl.this, code);
} else {
mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
}
}
});
}
}
ok,这里就回调到app了,app就能知道是什么原因导致的open失败了
native service
这里是这边文章的核心内容,已在关键位置加了注释,后续根据注释一一说明
frameworks/av/services/camera/libcameraservice/CameraService.cpp
template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
int api1CameraId, const String16& clientPackageName,
const std::optional<String16>& clientFeatureId, int clientUid, int clientPid,
apiLevel effectiveApiLevel, bool shimUpdateOnly, int oomScoreOffset, int targetSdkVersion,
/*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) and "
"Camera API version %d", clientPid, clientName8.string(), cameraId.string(),
static_cast<int>(effectiveApiLevel));
nsecs_t openTimeNs = systemTime();
sp<CLIENT> client = nullptr;
int facing = -1;
int orientation = 0;
bool isNdk = (clientPackageName.size() == 0);
{
// 注释一
// Acquire mServiceLock and prevent other clients from connecting
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 validity checks
if(!(ret = validateConnectLocked(cameraId, clientName8,
/*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {
return ret;
}
// Check the shim parameters after acquiring lock, if they have already been updated and
// we were doing a shim update, return immediately
if (shimUpdateOnly) {
auto cameraState = getCameraState(cameraId);
if (cameraState != nullptr) {
if (!cameraState->getShimParams().isEmpty()) return ret;
}
}
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, oomScoreOffset, /*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());
case -EUSERS:
return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
"Too many cameras already open, cannot open camera \"%s\"",
cameraId.string());
default:
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Unexpected error %s (%d) opening camera \"%s\"",
strerror(-err), err, cameraId.string());
}
}
if (clientTmp.get() != nullptr) {
// Handle special case for API1 MediaRecorder where the existing client is returned
device = static_cast<CLIENT*>(clientTmp.get());
return ret;
}
// give flashlight a chance to close devices if necessary.
mFlashlight->prepareDeviceOpen(cameraId);
int deviceVersion = getDeviceVersion(cameraId, /*out*/&facing, /*out*/&orientation);
if (facing == -1) {
ALOGE("%s: Unable to get camera device \"%s\" facing", __FUNCTION__, cameraId.string());
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Unable to get camera device \"%s\" facing", cameraId.string());
}
sp<BasicClient> tmp = nullptr;
bool overrideForPerfClass = SessionConfigurationUtils::targetPerfClassPrimaryCamera(
mPerfClassPrimaryCameraIds, cameraId.string(), targetSdkVersion);
if(!(ret = makeClient(this, cameraCb, clientPackageName, clientFeatureId,
cameraId, api1CameraId, facing, orientation,
clientPid, clientUid, getpid(),
deviceVersion, effectiveApiLevel, overrideForPerfClass,
/*out*/&tmp)).isOk()) {
return ret;
}
client = static_cast<CLIENT*>(tmp.get());
LOG_ALWAYS_FATAL_IF(client.get() == nullptr, "%s: CameraService in invalid state",
__FUNCTION__)
//注释四
err = client->initialize(mCameraProviderManager, mMonitorTags);
if (err != OK) {
ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);
// Errors could be from the HAL module open call or from AppOpsManager
switch(err) {
case BAD_VALUE:
return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,
"Illegal argument to HAL module for camera \"%s\"", cameraId.string());
case -EBUSY:
return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,
"Camera \"%s\" is already open", cameraId.string());
case -EUSERS:
return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
"Too many cameras already open, cannot open camera \"%s\"",
cameraId.string());
case PERMISSION_DENIED:
return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,
"No permission to open camera \"%s\"", cameraId.string());
case -EACCES:
return STATUS_ERROR_FMT(ERROR_DISABLED,
"Camera \"%s\" disabled by policy", cameraId.string());
case -ENODEV:
default:
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Failed to initialize camera \"%s\": %s (%d)", cameraId.string(),
strerror(-err), err);
}
}
// Update shim paremeters for legacy clients
if (effectiveApiLevel == API_1) {
// Assume we have always received a Client subclass for API1
sp<Client> shimClient = reinterpret_cast<Client*>(client.get());
String8 rawParams = shimClient->getParameters();
CameraParameters params(rawParams);
auto cameraState = getCameraState(cameraId);
if (cameraState != nullptr) {
cameraState->setShimParams(params);
} else {
ALOGE("%s: Cannot update shim parameters for camera %s, no such device exists.",
__FUNCTION__, cameraId.string());
}
}
// Set rotate-and-crop override behavior
if (mOverrideRotateAndCropMode != ANDROID_SCALER_ROTATE_AND_CROP_AUTO) {
client->setRotateAndCropOverride(mOverrideRotateAndCropMode);
} else if (CameraServiceProxyWrapper::isRotateAndCropOverrideNeeded(clientPackageName,
orientation, facing)) {
client->setRotateAndCropOverride(ANDROID_SCALER_ROTATE_AND_CROP_90);
}
// Set camera muting behavior
bool isCameraPrivacyEnabled =
mSensorPrivacyPolicy->isCameraPrivacyEnabled(multiuser_get_user_id(clientUid));
if (client->supportsCameraMute()) {
client->setCameraMute(
mOverrideCameraMuteMode || isCameraPrivacyEnabled);
} else if (isCameraPrivacyEnabled) {
// no camera mute supported, but privacy is on! => disconnect
ALOGI("Camera mute not supported for package: %s, camera id: %s",
String8(client->getPackageName()).string(), cameraId.string());
// Do not hold mServiceLock while disconnecting clients, but
// retain the condition blocking other clients from connecting
// in mServiceLockWrapper if held.
mServiceLock.unlock();
// Clear caller identity temporarily so client disconnect PID
// checks work correctly
int64_t token = CameraThreadState::clearCallingIdentity();
// Note AppOp to trigger the "Unblock" dialog
client->noteAppOp();
client->disconnect();
CameraThreadState::restoreCallingIdentity(token);
// Reacquire mServiceLock
mServiceLock.lock();
return STATUS_ERROR_FMT(ERROR_DISABLED,
"Camera \"%s\" disabled due to camera mute", cameraId.string());
}
if (shimUpdateOnly) {
// If only updating legacy shim parameters, immediately disconnect client
mServiceLock.unlock();
client->disconnect();
mServiceLock.lock();
} else {
// Otherwise, add client to active clients list
finishConnectLocked(client, partial, oomScoreOffset);
}
client->setImageDumpMask(mImageDumpMask);
} // lock is destroyed, allow further connect calls
// Important: release the mutex here so the client can call back into the service from its
// destructor (can be at the end of the call)
device = client;
int32_t openLatencyMs = ns2ms(systemTime() - openTimeNs);
CameraServiceProxyWrapper::logOpen(cameraId, facing, clientPackageName,
effectiveApiLevel, isNdk, openLatencyMs);
return ret;
}
注释一
// Acquire mServiceLock and prevent other clients from connecting
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",
mServiceLock 是相机服务的一个核心锁,用于控制多个客户端同时连接相机服务的并发访问。
AutoConditionLock::waitAndAcquire 是一个封装的锁操作:
尝试获取锁,如果当前锁被其他客户端占用,会进入等待状态。
等待超时时间由 DEFAULT_CONNECT_TIMEOUT_NS 定义(通常是一个固定的纳秒值,如几秒钟)。
成功获取锁后,返回一个 lock 对象(用于后续自动释放锁);失败则返回 nullptr。
如果 lock 为 nullptr,表示在超时时间内未能获取到锁,通常原因是同时有太多客户端在尝试连接相机服务,导致当前客户端等待超时。
此时会打印错误日志(ALOGE),并返回 ERROR_MAX_CAMERAS_IN_USE 错误,告知客户端连接失败。
核心作用
这段代码的本质是限制相机服务的并发连接数,相机服务作为系统核心服务,需要处理多个客户端的相机请求,但同时连接的客户端数量过多可能导致资源耗尽、响应缓慢等问题。
通过 mServiceLock 控制并发,确保同一时间只有有限数量的客户端能执行连接逻辑,避免服务过载。
当并发连接数超过系统承载能力(导致当前客户端超时无法获取锁)时,拒绝新连接并返回错误,保护系统稳定性。
注释二
//注释二
// Enforce client permissions and do basic validity checks
if(!(ret = validateConnectLocked(cameraId, clientName8,
/*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {
return ret;
}
该函数内部会执行一系列关键检查(具体逻辑依赖 Android 版本,但通常包括):
权限验证:
检查客户端是否拥有访问相机的权限(如 CAMERA 权限)。
对于特殊相机(如系统相机、受限制的硬件相机),检查是否有额外的权限或签名验证(如系统应用签名)。
参数有效性检查:
验证 cameraId 是否存在(是否为系统中实际可用的相机 ID)。
检查 clientUid/clientPid 的合法性(如是否为有效的进程 ID,是否属于当前系统中的活跃进程)。
客户端身份确认:
确认客户端的真实身份(避免权限伪造),例如通过进程 ID 追溯应用的包名和签名。
在跨进程场景(如通过 binder 调用)中,修正并记录原始客户端的进程 ID(通过 originalClientPid 输出)。
注释三
//注释三
if ((err = handleEvictionsLocked(cameraId, originalClientPid, effectiveApiLevel,
IInterface::asBinder(cameraCb), clientName8, oomScoreOffset, /*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());
case -EUSERS:
return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
"Too many cameras already open, cannot open camera \"%s\"",
cameraId.string());
default:
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Unexpected error %s (%d) opening camera \"%s\"",
strerror(-err), err, cameraId.string());
}
}
这段代码的核心是调用 handleEvictionsLocked() 函数,并根据其返回的错误码(err)进行不同的错误处理,该函数是相机服务内部的关键逻辑,作用是检查当前相机设备(cameraId)的占用情况
处理「资源驱逐」,当新客户端请求打开相机时,如果相机已被占用或系统资源不足,可能需要终止低优先级的现有客户端,为新客户端腾出资源。
参数包括相机 ID、客户端进程 ID、API 版本、回调接口、客户端名称等,用于标识和评估新客户端的优先级
函数返回值 err 表示操作结果:NO_ERROR 表示成功(可能驱逐了低优先级客户端),非零值表示失败。
根据 handleEvictionsLocked() 返回的错误码,返回对应的相机服务错误状态:
-ENODEV:相机设备不存在或不可用(如硬件故障、被移除),返回 ERROR_DISCONNECTED 错误。
-EBUSY:相机已被更高优先级的客户端占用(无法驱逐),返回 ERROR_CAMERA_IN_USE 错误。
-EUSERS:系统中已打开的相机数量达到上限,返回 ERROR_MAX_CAMERAS_IN_USE 错误。
默认情况:其他未预期的错误,返回 ERROR_INVALID_OPERATION 并附带错误详情。
特别需要注意的是,为什么驱逐解决不了 EUSERS问题?
那是因为驱逐只能释放某个特定相机的占用权(让新客户端使用同一个相机),但无法增加「系统允许同时打开的相机总数」。
例如:系统最多允许打开 2 个相机,当前已打开相机 A 和 B。此时新客户端请求打开相机 C,即使 A 和 B 的客户端优先级很低,驱逐它们只能释放 A 或 B 的使用权,但新客户端要打开的是 C(第三个相机),总数量仍然超过上限,因此必然触发 EUSERS。
注释四
err = client->initialize(mCameraProviderManager, mMonitorTags);
if (err != OK) {
ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);
// Errors could be from the HAL module open call or from AppOpsManager
switch(err) {
case BAD_VALUE:
return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,
"Illegal argument to HAL module for camera \"%s\"", cameraId.string());
case -EBUSY:
return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,
"Camera \"%s\" is already open", cameraId.string());
case -EUSERS:
return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,
"Too many cameras already open, cannot open camera \"%s\"",
cameraId.string());
case PERMISSION_DENIED:
return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,
"No permission to open camera \"%s\"", cameraId.string());
case -EACCES:
return STATUS_ERROR_FMT(ERROR_DISABLED,
"Camera \"%s\" disabled by policy", cameraId.string());
case -ENODEV:
default:
return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Failed to initialize camera \"%s\": %s (%d)", cameraId.string(),
strerror(-err), err);
}
}
由前面文章可以知道,这里是连接hal去打开相机的流程,需要注意这个阶段也会有失败的可能
可以看到上面有很多条件会导致openCamera的过程失败直接返回,可以在相对应的位置加特定的日志以方便获取打开相机失败的具体原因,可以归于上层原因,还是系统资源原因,还是底层原因,并且由于在前期相机适配过程中,相机打开失败的问题还是很常见的,所以很有必要去熟悉connectHelper的流程。
底层
底层这里不讨论
总结
还有特别重要的预览也需要先说明下,比如预览卡顿问题,也是可以分为上层,中间层,底层去分析,上层主要是监听onCaptureCompleted,onCaptureFailed回调,中间层的话主要看processCaptureResult函数,底层的话主要看是否有出帧打印,这是一个分析方向,也是一个引子,就先这样吧,后面有空再重新排一下版。