Android Camera openCamera

由头

今日调休,终于终于闲下来了,可以写一下博客了,刚好打开自己电脑,就有四年前下的谷歌Android 12源码,不是很旧,刚好够用,不用再另外下载新源码了,不得不感慨这时间过得真快啊~废话不多说,开整!

过程分解

我们知道Camera操作过程中最重要的四个步骤(这边仅先以preview,capture流说明,video流还得另说):

CameraManager–>openCamera —> 打开相机
CameraDeviceImpl–>createCaptureSession —> 创建捕获会话
CameraCaptureSession–>setRepeatingRequest —> 设置预览界面
CameraDeviceImpl–>capture —> 开始捕获图片

后面有空的话会按序讲解,这篇文章仅以openCamera过程进行说明

获取CameraManager

首先我们看下上层如果要openCamera需要做什么,以下是一个常见的应用调用api2接口打开相机过程

    private void openCamera() {CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);try {// 获取相机IDcameraId = getCameraId(manager);if (cameraId == null) {Toast.makeText(this, "未找到可用相机", Toast.LENGTH_SHORT).show();return;}// 检查相机权限if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {// 请求相机权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 101);return;}// 获取相机特性CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map != null) {// 获取最佳预览尺寸imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];}// 打开相机manager.openCamera(cameraId, stateCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}

这篇文章我们只关注其中两行代码

CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
manager.openCamera(cameraId, stateCallback, mBackgroundHandler);

首先(CameraManager) getSystemService(Context.CAMERA_SERVICE);为啥能返回CameraManger实例,这就涉及到getSystemService函数,那我们先来看下getSystemService函数实现
frameworks/base/core/java/android/app/ContextImpl.java

    @Overridepublic Object getSystemService(String name) {//直接到关键函数return SystemServiceRegistry.getSystemService(this, name);}

这里需要跳转到另一个类SystemServiceRegistry.java
frameworks/base/core/java/android/app/SystemServiceRegistry.java

    /*** Gets a system service from a given context.* @hide*/public static Object getSystemService(ContextImpl ctx, String name) {final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);if (fetcher == null) {if (sEnableServiceNotFoundWtf) {Slog.wtf(TAG, "Unknown manager requested: " + name);}return null;}final Object ret = fetcher.getService(ctx);return ret}
}

根据返回值可以倒推这个ret就是CameraManger实例,那这个是怎么来的呢,这个是根据
SYSTEM_SERVICE_FETCHERS.get(name);和fetcher.getService(ctx);共同决定的,但要看这两个逻辑具体做了啥需要先知道以下的逻辑,然后我们再回来看

当应用进程启动并首次使用任何系统服务(如 getSystemService)时,SystemServiceRegistry 类会被 ClassLoader 加载,此时静态代码块执行,所有系统服务(包括相机服务)的注册逻辑会一次性完成其中就包括CameraManager

static {registerService(Context.CAMERA_SERVICE, CameraManager.class,new CachedServiceFetcher<CameraManager>() {@Overridepublic CameraManager createService(ContextImpl ctx) {return new CameraManager(ctx);}});}

我们需要知道registerService做了什么,可以看到SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);

    /*** Statically registers a system service with the context.* This method must be called during static initialization only.*/private static <T> void registerService(@NonNull String serviceName,@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());}

根据注册的匹配规则直接看CachedServiceFetcher

    /*** Override this class when the system service constructor needs a* ContextImpl and should be cached and retained by that context.*/static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {private final int mCacheIndex;@Override@SuppressWarnings("unchecked")public final T getService(ContextImpl ctx) {T service = null;@ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;try {// This thread is the first one to get here. Instantiate the service// *without* the cache lock held.service = createService(ctx);newState = ContextImpl.STATE_READY;} catch (ServiceNotFoundException e) {onServiceNotFound(e);} finally {synchronized (cache) {cache[mCacheIndex] = service;gates[mCacheIndex] = newState;cache.notifyAll();}}ret = service;return ret;}

所以上面的 final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);根据匹配Context.CAMERA_SERVICE对应到CachedServiceFetcher,然后再在final Object ret = fetcher.getService(ctx);这里调用createService回调

    registerService(Context.CAMERA_SERVICE, CameraManager.class,new CachedServiceFetcher<CameraManager>() {@Overridepublic CameraManager createService(ContextImpl ctx) {return new CameraManager(ctx);}});

从而获取到了CameraManager对象实例

manager.openCamera

那下面就继续说下manager.openCamera(cameraId, stateCallback, mBackgroundHandler);做了什么吧,首先

frameworks/base/core/java/android/hardware/camera2/CameraManager.java

    @RequiresPermission(android.Manifest.permission.CAMERA)public void openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)throws CameraAccessException {openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),USE_CALLING_UID);}

这里的源码提示了需要android.Manifest.permission.CAMERA权限,如果应用调用openCamera的时候需要动态申请,并且这是公共方法,应用开发者可以直接调用

    /*** Open a connection to a camera with the given ID, on behalf of another application* specified by clientUid.** <p>The behavior of this method matches that of {@link #openCamera}, except that it allows* the caller to specify the UID to use for permission/etc verification. This can only be* done by services trusted by the camera subsystem to act on behalf of applications and* to forward the real UID.</p>** @param clientUid*             The UID of the application on whose behalf the camera is being opened.*             Must be USE_CALLING_UID unless the caller is a trusted service.** @hide*/public void openCameraForUid(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,int clientUid) throws CameraAccessException {openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0);}//此方法已经是隐藏方法了/*** Open a connection to a camera with the given ID, on behalf of another application* specified by clientUid. Also specify the minimum oom score and process state the application* should have, as seen by the cameraserver.** <p>The behavior of this method matches that of {@link #openCamera}, except that it allows* the caller to specify the UID to use for permission/etc verification. This can only be* done by services trusted by the camera subsystem to act on behalf of applications and* to forward the real UID.</p>** @param clientUid*             The UID of the application on whose behalf the camera is being opened.*             Must be USE_CALLING_UID unless the caller is a trusted service.* @param oomScoreOffset*             The minimum oom score that cameraservice must see for this client.* @hide*/public void openCameraForUid(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,int clientUid, int oomScoreOffset) throws CameraAccessException {if (cameraId == null) {throw new IllegalArgumentException("cameraId was null");} else if (callback == null) {throw new IllegalArgumentException("callback was null");}if (CameraManagerGlobal.sCameraServiceDisabled) {throw new IllegalArgumentException("No cameras available on device");}openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);}这个方法上个openCameraForUid的进一步延续,接着继续往下看```bashprivate CameraDevice openCameraDeviceUserAsync(String cameraId,CameraDevice.StateCallback callback, Executor executor, final int uid,final int oomScoreOffset) throws CameraAccessException {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);}

连接cameraService 并调用connectDevice函数,下来看下connectDevice具体做了什么

frameworks/av/services/camera/libcameraservice/CameraService.cpp

下面代码已有删减,只讲一些重点

Status CameraService::connectDevice(const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,const String16& cameraId,const String16& clientPackageName,const std::optional<String16>& clientFeatureId,int clientUid, int oomScoreOffset, int targetSdkVersion,/*out*/sp<hardware::camera2::ICameraDeviceUser>* device) {int callingPid = CameraThreadState::getCallingPid();//这里通常可以加一下日志,看下是谁调用的相机,再加一下调用的时间戳,可以帮助分析线上问题//顺带提一下,这里会强行校验前面提到的相机权限,所以没有权限是行不通的,老实申请// enforce system camera permissionsif (oomScoreOffset > 0 &&!hasPermissionsForSystemCamera(callingPid, CameraThreadState::getCallingUid())) {String8 msg =String8::format("Cannot change the priority of a client %s pid %d for ""camera id %s without SYSTEM_CAMERA permissions",String8(clientPackageNameAdj).string(), callingPid, id.string());ALOGE("%s: %s", __FUNCTION__, msg.string());return STATUS_ERROR(ERROR_PERMISSION_DENIED, msg.string());}//这里是openCamera的关键流程,主要是调用到了connectHelper,其他的我们先不关注,这里主要关注的就是open camera 设备流程ret = connectHelper<hardware::camera2::ICameraDeviceCallbacks,CameraDeviceClient>(cameraCb, id,/*api1CameraId*/-1, clientPackageNameAdj, clientFeatureId,clientUid, USE_CALLING_PID, API_2, /*shimUpdateOnly*/ false, oomScoreOffset,targetSdkVersion, /*out*/client);}

这里直接跳转到connectHelper函数,只贴了下需要关注的代码

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) {
**//handleEvictionsLocked这个函数很关键,是进行相机多进程互斥逻辑的方法。多个应用无法同时打开相机就是在这个方法中进行管理的,所以建议为了方便调试定位问题,在这里加一下日志**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());}//相机打开的情况下,此时会关闭所有闪光灯的控制,这也是为了保持相机相应硬件资源的独占性,保证相机功能的正常使用// give flashlight a chance to close devices if necessary.mFlashlight->prepareDeviceOpen(cameraId);// 这里进入了真正的client流程,通过getDeviceVersion方法取得对应底层HAL的版本,这里的版本与HAL本身的版本略有不同。作用是根据版本的不同确定HAL中的小版本,各个版本的区别是库中方法有所不同。int deviceVersion = getDeviceVersion(cameraId, /*out*/&facing, /*out*/&orientation);//根据Device version调用makeClient方法创建Camera Client对象,makeClient方法主要有两个操作,1是判断API+HAL版本的通路; 2是根据判定结果决定创建哪个client。client类有三个,分别是CameraClient、Camera2Client和CameraDeviceClient。if(!(ret = makeClient(this, cameraCb, clientPackageName, clientFeatureId,cameraId, api1CameraId, facing, orientation,clientPid, clientUid, getpid(),deviceVersion, effectiveApiLevel, overrideForPerfClass,/*out*/&tmp)).isOk()) {return ret;}//下面是连接CameraProvider ,然后连接hal,最后操作驱动打开相机的,这里也可以加一下日志打印连接过程如果出现异常可以方便排查异常原因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);}}

接着
frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp

status_t CameraDeviceClient::initialize(sp<CameraProviderManager> manager,const String8& monitorTags) {return initializeImpl(manager, monitorTags);
}template<typename TProviderPtr>
status_t CameraDeviceClient::initializeImpl(TProviderPtr providerPtr, const String8& monitorTags) {//关键跳转res = Camera2ClientBase::initialize(providerPtr, monitorTags);
}

直接到下一步:

frameworks/av/services/camera/libcameraservice/common/Camera2ClientBase.cpp

template <typename TClientBase>
status_t Camera2ClientBase<TClientBase>::initialize(sp<CameraProviderManager> manager,const String8& monitorTags) {return initializeImpl(manager, monitorTags);
}template <typename TClientBase>
template <typename TProviderPtr>
status_t Camera2ClientBase<TClientBase>::initializeImpl(TProviderPtr providerPtr,const String8& monitorTags) {res = mDevice->initialize(providerPtr, monitorTags);if (res != OK) {ALOGE("%s: Camera %s: unable to initialize device: %s (%d)",__FUNCTION__, TClientBase::mCameraIdStr.string(), strerror(-res), res);return res;}
}

接着跳转到Camera3Device.cpp,继续往下看
frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp

status_t Camera3Device::initialize(sp<CameraProviderManager> manager, const String8& monitorTags) {status_t res = manager->openSession(mId.string(), this,/*out*/ &session);}

frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.cpp

通过HIDL最终调用到了HAL层实现,由于CameraProvider就是谷歌专门用来隔离HAL和层和native service的耦合,所以接下来都是各个厂家自行的实现了,而interface->open作为统一开发接口

status_t CameraProviderManager::openSession(const std::string &id,const sp<device::V3_2::ICameraDeviceCallback>& callback,/*out*/sp<device::V3_2::ICameraDeviceSession> *session) {ret = interface->open(callback, [&status, &session](Status s, const sp<device::V3_2::ICameraDeviceSession>& cameraSession) {status = s;if (status == Status::OK) {*session = cameraSession;}});if (!ret.isOk()) {removeRef(DeviceMode::CAMERA, id);ALOGE("%s: Transaction error opening a session for camera device %s: %s",__FUNCTION__, id.c_str(), ret.description().c_str());return DEAD_OBJECT;}}

interface对应代码

hardware/interfaces/camera/common/1.0/default/CameraModule.cpp

int CameraModule::open(const char* id, struct hw_device_t** device) {int res;ATRACE_BEGIN("camera_module->open");res = filterOpenErrorCode(mModule->common.methods->open(&mModule->common, id, device));ATRACE_END();return res;
}

该方法非常简洁,就是调用mModule类的common.methods的open方法处理,它的mModule也是在CameraModule类的构造函数中传入的,而CameraModule的构造方法是在CameraProvider类的initialize()方法中调用的,源码如下
hardware/interfaces/camera/provider/2.4/default/LegacyCameraProviderImpl_2_4.cpp

bool LegacyCameraProviderImpl_2_4::initialize() {camera_module_t *rawModule;int err = hw_get_module(CAMERA_HARDWARE_MODULE_ID,(const hw_module_t **)&rawModule);if (err < 0) {ALOGE("Could not load camera HAL module: %d (%s)", err, strerror(-err));return true;}mModule = new CameraModule(rawModule);err = mModule->init();if (err != OK) {ALOGE("Could not initialize camera HAL module: %d (%s)", err, strerror(-err));mModule.clear();return true;}

在CameraProvider类的initialize()方法中调用hw_get_module获取到的就是这里定义的camera_module_t

hardware/qcom/camera/msm8998/QCamera2/QCamera2Hal.cpp

static hw_module_t camera_common = {.tag                    = HARDWARE_MODULE_TAG,.module_api_version     = CAMERA_MODULE_API_VERSION_2_5,.hal_api_version        = HARDWARE_HAL_API_VERSION,.id                     = CAMERA_HARDWARE_MODULE_ID,.name                   = "QCamera Module",.author                 = "Qualcomm Innovation Center Inc",.methods                = &qcamera::QCamera2Factory::mModuleMethods,.dso                    = NULL,.reserved               = {0}
};camera_module_t HAL_MODULE_INFO_SYM = {.common                 = camera_common,.get_number_of_cameras  = qcamera::QCamera2Factory::get_number_of_cameras,.get_camera_info        = qcamera::QCamera2Factory::get_camera_info,.set_callbacks          = qcamera::QCamera2Factory::set_callbacks,.get_vendor_tag_ops     = qcamera::QCamera3VendorTags::get_vendor_tag_ops,.open_legacy            = NULL,.set_torch_mode         = qcamera::QCamera2Factory::set_torch_mode,.init                   = NULL,.get_physical_camera_info = qcamera::QCamera2Factory::get_physical_camera_info,.is_stream_combination_supported = qcamera::QCamera2Factory::is_stream_combination_supported,.reserved               = {0}
};

hardware/qcom/camera/msm8998/QCamera2/QCamera2Hal.cpp

struct hw_module_methods_t QCamera2Factory::mModuleMethods = {.open = QCamera2Factory::camera_device_open,
};

这里的open又指向了QCamera2Factory类的camera_device_open方法,该方法的源码如下:

int QCamera2Factory::camera_device_open(const struct hw_module_t *module, const char *id,struct hw_device_t **hw_device)
{int rc = NO_ERROR;if (module != &HAL_MODULE_INFO_SYM.common) {LOGE("Invalid module. Trying to open %p, expect %p",module, &HAL_MODULE_INFO_SYM.common);return INVALID_OPERATION;}if (!id) {LOGE("Invalid camera id");return BAD_VALUE;}
#ifdef QCAMERA_HAL1_SUPPORTif(gQCameraMuxer)rc =  gQCameraMuxer->camera_device_open(module, id, hw_device);else
#endifrc = gQCamera2Factory->cameraDeviceOpen(atoi(id), hw_device);return rc;
}

调用cameraDeviceOpen方法来处理,它的cameraDeviceOpen方法的源码如下:

int QCamera2Factory::cameraDeviceOpen(int camera_id,struct hw_device_t **hw_device)
{rc = hw->openCamera(&hw_dev[i]);return rc;
}

hardware/qcom/camera/msm8998/QCamera2/HAL3/QCamera3HWI.cpp

int QCamera3HardwareInterface::openCamera(struct hw_device_t **hw_device)
{rc = openCamera();return rc;
}
int QCamera3HardwareInterface::openCamera()
{rc = camera_open((uint8_t)mCameraId, &mCameraHandle);return NO_ERROR;  
}

hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera_interface.c

int32_t camera_open(uint8_t camera_idx, mm_camera_vtbl_t **camera_vtbl)
{rc = mm_camera_open(cam_obj);return rc;
}

hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera.c

int32_t mm_camera_open(mm_camera_obj_t *my_obj)
{char dev_name[MM_CAMERA_DEV_NAME_LEN];int32_t rc = 0;int8_t n_try=MM_CAMERA_DEV_OPEN_TRIES;uint8_t sleep_msec=MM_CAMERA_DEV_OPEN_RETRY_SLEEP;int cam_idx = 0;const char *dev_name_value = NULL;int l_errno = 0;LOGD("begin\n");if (NULL == my_obj) {goto on_error;}dev_name_value = mm_camera_util_get_dev_name_by_num(my_obj->my_num,my_obj->my_hdl);if (NULL == dev_name_value) {goto on_error;}snprintf(dev_name, sizeof(dev_name), "/dev/%s",dev_name_value);sscanf(dev_name, "/dev/video%d", &cam_idx);LOGD("dev name = %s, cam_idx = %d", dev_name, cam_idx);do{n_try--;errno = 0;my_obj->ctrl_fd = open(dev_name, O_RDWR | O_NONBLOCK);l_errno = errno;LOGD("ctrl_fd = %d, errno == %d", my_obj->ctrl_fd, l_errno);if((my_obj->ctrl_fd >= 0) || (errno != EIO && errno != ETIMEDOUT) || (n_try <= 0 )) {break;}LOGE("Failed with %s error, retrying after %d milli-seconds",strerror(errno), sleep_msec);usleep(sleep_msec * 1000U);}while (n_try > 0);if (my_obj->ctrl_fd < 0) {LOGE("cannot open control fd of '%s' (%s)\n",dev_name, strerror(l_errno));if (l_errno == EBUSY)rc = -EUSERS;elserc = -1;goto on_error;} else {mm_camera_get_session_id(my_obj, &my_obj->sessionid);LOGH("Camera Opened id = %d sessionid = %d", cam_idx, my_obj->sessionid);}return rc;
}

HAL端也就是从这里进入内核,调用驱动来处理的,这里的逻辑是通过do/while循环来处理的,有一个重试机制,重试次数n_try不断的减小,当它等于0时,相机设备还未正常打开,就退出do/while循环了,它的初值为MM_CAMERA_DEV_OPEN_TRIES,该宏定义的值为20, (my_obj->ctrl_fd >= 0)的意思是就camera打开成功,返回的FD有效;(errno != EIO && errno != ETIMEDOUT)的意思是未出现IO或者超时错误;(n_try <= 0 )意思是重试次数已用完,打开成功后,还要进行一些其他初始化的操作。

接下来的驱动层调用不再赘述,以实际平台实现为准。

题外话

这篇文章仅是简单说明Camera 打开设备的一个过程说明,并且给了些加日志调试分析的一些建议,先当个草稿吧,后续有时间了再完善一下,哈哈哈,连个图都没有,直接啃代码~

注:
此文皆为通用谷歌实现方案为参考,由于各个手机厂家都会自定义hal及其驱动实现,需以实际厂家指导操作文档为准。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915752.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神经网络——池化层

目录 池化层 最大池化层 MaxPool2d 最大池化操作图示 最大池化操作代码演示 综合代码案例 池化层 池化层&#xff08;Pooling Layer&#xff09; 核心作用&#xff1a;通过降采样减少特征图尺寸&#xff0c;降低计算量&#xff0c;增强特征鲁棒性。 1. 常见类型 …

Android 默认图库播放视频没有自动循环功能,如何添加2

Android 默认图库播放视频没有自动循环功能, 如何添加 按如下方式修改可以添加 开发云 - 一站式云服务平台 --- a/packages/apps/Gallery2/src/com/android/gallery3d/app/MovieActivity.java +++ b/packages/apps/Gallery2/src/com/android/gallery3d/app/MovieActivity.java…

数字孪生赋能智慧能源电力传输管理新模式

在“双碳”战略和能源数字化转型的双重驱动下&#xff0c;智慧能源系统亟需更高效、精细和智能的管理手段。数字孪生技术作为融合物理世界与数字空间的桥梁&#xff0c;为电力传输系统的全生命周期管理提供了强有力的技术支撑。本文聚焦数字孪生在智慧能源电力传输中的应用&…

Jmeter的元件使用介绍:(二)线程组详解

Jmeter线程组默认包含三种&#xff1a;线程组、setUp线程组、tearDown线程组。线程组之间的执行顺序为&#xff1a;setUp线程组->线程组->tearDown线程组。多数情况都是选用线程组&#xff0c;setUp线程组用于做一些脚本的前置准备&#xff0c;比如&#xff1a;跨线程组设…

AI替代人工:浪潮中的沉浮与觉醒

当AlphaGo以4:1的比分战胜围棋大师李世石之时&#xff0c;人机博弈的疆界被重新划定&#xff1b;当工厂车间里机械臂以惊人精度与不知疲倦的姿态取代了工人重复的手势&#xff1b;当客服电话那头响起的不再是温存人声&#xff0c;而成了准确但缺乏温度的AI语音&#xff1b;当算…

数学建模--matplot.pyplot(结尾附线条样式表格)

matplotlib.pyplot绘图接口 1. 用法 导入模块 import matplotlib.pyplot as plt import numpy as np # 用于生成示例数据绘制简单图表 # 生成数据 x np.linspace(0, 10, 100) y np.sin(x)# 创建图形和坐标轴 plt.figure(figsize(8, 4)) # 设置图表大小 plt.plot(x, y, …

NumPy 实现三维旋转变换

在三维空间中,物体的旋转变换是计算机图形学、机器人学以及三维建模等领域中一个至关重要的操作。这种变换可以通过构造特定的旋转矩阵并将其应用于三维点或向量来实现。本文将深入探讨如何利用 NumPy 这一强大的 Python 科学计算库来实现三维旋转变换,从基本的数学原理到具体…

基于Springboot的中药商城管理系统/基于javaweb的中药材销售系统

管理员&#xff1a;登录&#xff0c;个人中心&#xff0c;用户管理&#xff0c;药材分类管理&#xff0c;药材信息管理&#xff0c;药材入库管理&#xff0c; 药材出库管理&#xff0c;订单管理&#xff0c;云端药馆&#xff0c;系统设置用户&#xff1a;注册&#xff0c;登录&…

试用SAP BTP 02A:试用SAP HANA Cloud

进入SAP BTP主控室 -> 子账 -> 服务市场&#xff0c;选择【数据和分析】-> 点击SAP HANA Cloud点击创建选择服务、计划、运行时环境、空间&#xff0c;输入实例名称&#xff0c;点击下一步在JSON文件中配置HANA管理员密码&#xff0c;点击下一步审核hana 实例信息&…

纯CPU场景下C++的分布式模型训练框架设计思路

0. 参数分配 稠密参数 → MPI 集合通信&#xff08;All-Reduce / Broadcast / Reduce-Scatter&#xff09;。稀疏参数 → brpc Parameter Server 异步推拉。 完全去掉 NCCL/GPU 相关部分。1. 整体拓扑 ┌----------------┐ ┌----------------┐ │ Worker-0 │…

训练日志7.21

conda环境&#xff0c;服务器原因无法使用&#xff0c;需重新搭建 学习一下预训练和微调相关内容&#xff0c;对于预训练整体的流程&#xff0c;还不太清楚&#xff0c;自己估计是训练不动&#xff0c;只能微调

Java 高频算法

Java高频算法面试题 以下是Java面试中常见的高频算法题目&#xff0c;涵盖了数据结构、算法思想和实际应用场景。 一、数组与字符串 1. 两数之和 public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map new HashMap<>();for (int i 0; i <…

汽车控制系统——CAPL脚本

CAPL (Communication Access Programming Language) 是一种专门用于嵌入式系统和汽车电子测试领域的编程语言&#xff0c;特别是在 CAN (Controller Area Network) 总线和汽车网络通信系统中被广泛使用。它由 Vector 公司开发&#xff0c;主要用于编写与汽车控制单元 (ECU) 进行…

深入解析Hive SQL转MapReduce的编译原理:从AST抽象语法树到Operator执行树

Hadoop与Hive SQL简介Hadoop生态系统的核心架构作为大数据处理领域的基石&#xff0c;Hadoop生态系统采用分布式架构设计&#xff0c;其核心组件构成了一套完整的解决方案框架。HDFS&#xff08;Hadoop Distributed File System&#xff09;作为底层存储系统&#xff0c;采用主…

在 React 中实现全局防复制hooks

用于防止页面内容被复制、剪切或通过右键菜单操作。它接受三个可配置参数&#xff1a;disableCopy&#xff08;禁用复制&#xff0c;默认true&#xff09;、disableCut&#xff08;禁用剪切&#xff0c;默认true&#xff09;和 disableContextMenu&#xff08;禁用右键菜单&…

InfluxDB HTTP API 接口调用详解(一)

引言 ** 在当今数字化时代&#xff0c;时间序列数据无处不在&#xff0c;从物联网设备产生的传感器数据&#xff0c;到金融领域的交易记录&#xff0c;再到系统运维中的监控指标&#xff0c;这些数据蕴含着丰富的信息&#xff0c;对于企业的决策制定、业务优化以及问题排查等…

使用JMeter进行压力测试(以黑马点评为例、详细图解)

目录 一、前言 二、使用JMeter进行压力测试 一、前言 本博客主要记录如何使用JMeter进行压力测试&#xff0c;以黑马点评P44利用互斥锁解决缓存击穿问题课程为例。至于如何完成JMeter的安装配置及创建桌面快捷方式可以看我的另一篇博客&#xff0c;链接如下&#xff1a; 压测…

旧手机部署轻量级服务器

将旧手机改造为Linux系统设备&#xff0c;不仅能赋予闲置设备新生&#xff0c;还能作为轻量级服务器、开发环境或学习平台使用。以下是三种主流方案&#xff0c;涵盖不同技术需求和安全等级&#xff0c;附操作步骤与避坑指南&#xff1a; ⚙️ 一、三种安装方案对比与选择 方法…

micro avg、macro avg 和 weighted avg 的区别

问题描述&#xff1a; 在多分类任务的评估报告中&#xff0c;经常看到 micro avg、macro avg 和 weighted avg 三种平均指标&#xff0c;请解释它们的区别以及各自的适用场景。&#x1f3af; 参考答案&#xff1a; 这三种平均指标是用来评估多分类模型性能的不同方式&#xff0…

Kafka灰度方案

一、kafka灰度方案架构设计方案&#xff1a;1、外部生产-内部消费场景&#xff1a;&#xff08;外部生产-内部消费&#xff09;解决方案&#xff1a;先通过kdis分流服务---消费外部生产的消息&#xff0c;然后根据管理后台-灰度配置mcs-mimp-core应用默认的环境版本&#xff0c…