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.pswp.cn/news/918805.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918805.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918805.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

醫美產業科技成果展陳中心:連接微觀肌膚世界與前沿科技的橋梁

作為一名深耕展陳設計施工的從業者&#xff0c;當接到醫美產業科技成果展陳中心的項目時&#xff0c;我深知這不是簡單的 “技術堆砌”&#xff0c;而是要在 “科學嚴謹性” 與 “美學體驗感” 之間找到平衡 —— 讓參觀者既能看懂激光設備的波長原理&#xff0c;又能感知膠原蛋…

在 Android 系統中清理應用數據但保留應用程序本身,可以通過以下幾種方法實現

在 Android 系統中清理應用數據但保留應用程序本身&#xff0c;可以通過以下幾種方法實現在 Android 系統中清理應用數據但保留應用程序本身&#xff0c;可以通過以下幾種方法實現&#xff1a;方法 1: 使用 Android 設置&#xff08;無需 root&#xff09;方法 2: 使用 ADB 命令…

Linux中tty與8250-uart的虐戀(包括雙中斷發送接收機制)

串口通用驅動文件在哪里&#xff1f; drivers/tty/serial/哪一個是正確的compatible&#xff1f; arch/arm64/boot/dts/rockchip/rk3568.dtsi uart3: serialfe670000 {compatible "rockchip,rk3568-uart", "snps,dw-apb-uart";reg <0x0 0xfe670000 0…

GitHub 倉庫代碼上傳指南

文章目錄 ??? 一、環境準備 ?? 二、創建 GitHub 倉庫 ?? 三、本地代碼上傳流程 首次上傳 更新已有代碼 ?? 四、認證問題解決(必看!) 方案 1:個人訪問令牌(PAT) 方案 2:SSH 密鑰(推薦長期使用) ? 五、常見錯誤處理 ?? 六、最佳實踐建議 ?? 高級技巧 ??…

介紹一下 自動駕駛 感知多任務訓練模型設計

自動駕駛感知多任務訓練模型是指在一個統一的模型架構中&#xff0c;同時完成自動駕駛場景下的多個感知任務&#xff08;如目標檢測、語義分割、深度估計、車道線檢測等&#xff09;的模型設計。其核心目標是通過特征共享和任務協同&#xff0c;在提升單任務性能的同時&#xf…

huggingface文件下載過慢/中斷怎么辦

huggingface上匯集了各個大模型和預訓練模型的權重文件&#xff0c;但是訪問huggingface需要連接外網&#xff0c;即時連接外網之后下載仍然過慢甚至會出現中斷&#xff0c;因此本文將使用兩種方法教你解決上述問題。 文章目錄1.使用國內鏡像下載2.使用Python腳本自動化下載1.使…

Spring Boot + Redis Sentinel (一主兩從)測試案例

&#x1f680; Spring Boot Redis Sentinel 完整測試案例 &#x1f3f7;? 標簽&#xff1a;Redis 、Redis Sentinel、Spring Boot 實戰 &#x1f4da; 目錄導航 &#x1f4dd; 前言&#x1f3d7;? Redis Sentinel 架構說明&#x1f4e6; Docker Compose 搭建 Redis 哨兵環境…

力扣-295.數據流的中位數

題目鏈接 295.數據流的中位數 class MedianFinder {PriorityQueue<Integer> left;//隊頭最大PriorityQueue<Integer> right;//隊頭最小public MedianFinder() {left new PriorityQueue<>(new Comparator<Integer>() {Overridepublic int compare(In…

【數據分享】2014-2023年長江流域 (0.05度)5.5km分辨率的每小時日光誘導葉綠素熒光SIF數據

而今天要說明數據就是2014-2023年長江流域 &#xff08;0.05度&#xff09;5.5km分辨率的每小時日光誘導葉綠素熒光SIF數據。數據介紹一、數據集概況&#xff1a;長江流域植被動態的 “每小時快照”本文分享的核心數據集為2014 年 9 月至 2023 年 9 月長江流域日光誘導葉綠素熒…

計算機二級 Web —— HTML 全面精講(含真題實戰)

例題來源: web.code2ji.cn 0. HTML 基礎與全局常識 0.1 HTML 是什么 HTML&#xff08;HyperText Markup Language&#xff09;是網頁結構語言&#xff0c;用“標簽”描述內容、層次與含義。 0.2 基本文檔骨架&#xff08;必須熟練&#xff09; <!DOCTYPE html> <…

Linux中的日志管理

注&#xff1a;在 centos7/Rocky9 中&#xff0c;系統日志消息由兩個服務負責處理&#xff1a;systemd-journald 和 rsyslog一、常見日志文件的作用實驗一&#xff1a;測試查看暴力破解系統密碼的IP地址步驟一&#xff1a;故意輸錯密碼3次&#xff0c;在日志文件中查看步驟二&a…

C++ 性能優化擂臺:挑戰與突破之路

一、引言&#xff08;一&#xff09;C 在性能關鍵領域的地位在當今數字化時代&#xff0c;C 語言憑借其高效性、靈活性和對硬件的直接操控能力&#xff0c;在眾多對性能要求極高的領域中占據著舉足輕重的地位。無論是構建高性能的游戲引擎&#xff0c;實現金融領域毫秒級響應的…

五、Elasticsearch在Linux的安裝部署

五、Elasticsearch在Linux的安裝部署 文章目錄五、Elasticsearch在Linux的安裝部署1.Elasticsearch的作用2.安裝0. 安裝前準備1.使用包管理器安裝&#xff08;推薦&#xff0c;自動服務化&#xff09;Ubuntu / DebianRHEL / CentOS / Rocky / Alma2. 使用 tar.gz 安裝&#xff…

Kubernetes集群部署全攻略

目錄 一、 服務器環境及初始化 1、架構分析 2、初始化 2.1、清空Iptales默認規則及關閉防火墻 2.2、關閉SELINUX 2.3、關閉Swap交換空間 2.4、設置主機名 2.5、編寫hosts文件 2.6、設置內核參數 二、安裝Docker環境 1、安裝Docker 1.1、配置阿里源 1.2、安裝docke…

Ceph存儲池詳解

Ceph 存儲池&#xff08;Pool&#xff09;詳解 Ceph 的 存儲池&#xff08;Pool&#xff09; 是邏輯存儲單元&#xff0c;用于管理數據的分布、冗余和訪問策略。它是 Ceph 存儲集群的核心抽象&#xff0c;支持 對象存儲&#xff08;RGW&#xff09;、塊存儲&#xff08;RBD&…

使用 Docker 部署 PostgreSQL

通過 Docker 部署 PostgreSQL 是一種快速、高效的方式&#xff0c;適用于開發和測試環境。 步驟 1&#xff1a;拉取 PostgreSQL 鏡像 運行以下命令從 Docker Hub 拉取最新的 PostgreSQL 鏡像&#xff1a; docker pull postgres 如果需要其他的鏡像&#xff0c;可以指定版本…

P1886 滑動窗口 /【模板】單調隊列【題解】

P1886 滑動窗口 /【模板】單調隊列 題目描述 有一個長為 nnn 的序列 aaa&#xff0c;以及一個大小為 kkk 的窗口。現在這個窗口從左邊開始向右滑動&#xff0c;每次滑動一個單位&#xff0c;求出每次滑動后窗口中的最小值和最大值。 例如&#xff0c;對于序列 [1,3,?1,?3,5,3…

河南萌新聯賽2025第(五)場:信息工程大學補題

文章目錄[TOC](文章目錄)前言A.宇宙終極能量調和與多維時空穩定性驗證下的基礎算術可行性研究B.中位數C.中位數1F.中位數4G.簡單題H.簡單題I.Re:從零開始的近世代數復習&#xff08;easy&#xff09;K.狂飆追擊L.防k題前言 這次萌新聯賽考到了很多數學知識 A.宇宙終極能量調和…

SuperMap GIS基礎產品FAQ集錦(20250804)

一、SuperMap iServer 問題1&#xff1a;iServer的名稱和logo怎么自定義&#xff1f; 11.3.0 【解決辦法】參考&#xff1a;https://blog.csdn.net/supermapsupport/article/details/144744640 問題2&#xff1a;iServer 刷新工作空間&#xff0c;當數據庫是 PostGIS 時&#x…

AWS CloudFormation批量刪除指南:清理Clickstream Analytics堆棧

概述 在AWS環境管理中,經常會遇到需要批量刪除CloudFormation堆棧的情況。本文記錄了一次完整的Clickstream Analytics堆棧清理過程,包括遇到的問題和解決方案,希望能為其他開發者提供參考。 背景 我們的AWS賬戶中部署了多個Clickstream Analytics解決方案的CloudFormati…