当前位置: 首页 > news >正文

Camera open failed

前言

由前面的几篇博客可以知道,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() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {// 相机打开成功,保存CameraDevice实例并创建预览会话mCameraDevice = camera;// 相机连接成功后开始配流操作createCaptureSession();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {// 相机断连记得释放资源camera.close();mCameraDevice = null;}@Overridepublic 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() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {Log.d(TAG, "相机打开成功");// 相机打开后的初始化操作(如创建会话)}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Log.w(TAG, "相机连接断开,关闭相机");camera.close();}@Overridepublic 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或者onDisconnecteddeviceImpl.setRemoteFailure(e);}} catch (RemoteException e) {// Camera service died - act as if it's a CAMERA_DISCONNECTED caseServiceSpecificException sse = new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED,"Camera service is currently unavailable");//这里回调onError或者onDisconnecteddeviceImpl.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.//这里会回调onOpeneddeviceImpl.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() {@Overridepublic 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 connectingstd::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 checksif(!(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 immediatelyif (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 returneddevice = 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 AppOpsManagerswitch(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 clientsif (effectiveApiLevel == API_1) {// Assume we have always received a Client subclass for API1sp<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 behaviorif (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 behaviorbool 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! => disconnectALOGI("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 correctlyint64_t token = CameraThreadState::clearCallingIdentity();// Note AppOp to trigger the "Unblock" dialogclient->noteAppOp();client->disconnect();CameraThreadState::restoreCallingIdentity(token);// Reacquire mServiceLockmServiceLock.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 clientmServiceLock.unlock();client->disconnect();mServiceLock.lock();} else {// Otherwise, add client to active clients listfinishConnectLocked(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 connectingstd::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 checksif(!(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 AppOpsManagerswitch(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函数,底层的话主要看是否有出帧打印,这是一个分析方向,也是一个引子,就先这样吧,后面有空再重新排一下版。

http://www.dtcms.com/a/325345.html

相关文章:

  • Flutter SharedPreferences存储数据基本使用
  • Apollo平台下相机和激光雷达手眼联合标定
  • 面试题-----RabbitMQ
  • RabbitMQ 消息转换器详解
  • OV5640 相机开发流程
  • 闸机控制系统从设计到实现全解析:第 5 篇:RabbitMQ 消息队列与闸机通信设计
  • C语言:贪吃蛇游戏
  • MiniCPM-V 4.0开源,号称是手机上的GPT-4V
  • 41.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--网关集成Swagger
  • 量子计算:叩响金融定价革命的大门——期权定价的范式转移
  • 用Python实现Excel转PDF并去除Spire.XLS水印
  • glide缓存策略和缓存命中
  • 基于 JavaWeb+MySQL设计实现博客管理系统
  • [激光原理与应用-230]:物理学主要分支、研究对象、衍生技术及职业方向解析
  • 智慧零售的本质重构与技术创新:基于定制开发开源AI智能名片S2B2C商城小程序的实践路径
  • Redis应⽤-缓存与分布式锁
  • MySQL误删数据了,如何快速恢复?
  • GraalVM !拥抱云原生的 JVM
  • AI驱动的智能编码革命:从Copilot到全流程开发自动化
  • 2024年ESWA SCI1区TOP,自适应种群分配和变异选择差分进化算法iDE-APAMS,深度解析+性能实测
  • SysTick定时器的工作原理是什么
  • 在Linux中模拟配置高性能web服务器
  • docker compose和docker-compose命令的区别
  • 【数据可视化-86】中国育儿成本深度可视化分析(基于《中国统计年鉴2023》数据):用Python和pyecharts打造炫酷可视化大屏
  • linux常见故障 实用故障系列文章-2获取挂掉的进程pid
  • Linux kernel network stack, some good article
  • AI模型服务接入WAF防火墙
  • WebSocket-java篇
  • 有序矩阵中第K小的元素+二分查找
  • 矩阵游戏(二分图最大匹配)