和你一起一生学习,这里是程序员 Android
本篇文章次要介绍 Android
开发中的局部知识点,通过浏览本篇文章,您将播种以下内容:
一、Android Hal3回顾
二、Qcom Hal3 CamX架构
三、Qcom Hal3 Camx 重点
一、 Android Hal3回顾
Camera HAL3学习
HAL层操作简略总结:
- Framework层发送捕捉数据的异步申请。
- HAL层设施必须依照秩序解决申请。对于每个申请,HAL层须要输入元数据和一个或者多个图像数据。
- 对于申请和后果都须要遵循先进先出的准则;这个数据流将被后续的申请所参考。
- 对于同一个申请,所有输入数据的工夫戳必须雷同,以便framework层同步输入数据,如果需要的话。
- 在申请和后果数据总,所有捕捉数据的配置和状态(除了3A解决),都须要封装起来。
二、Qcom Hal3 CamX架构
Qcom作为平台厂商会依据谷歌定义的HAL3接口来实现本人的Camera HAL3,新的支流的Qcom Camera HAL3 架构就是CamX了.
Camx的具体过程可参考高通文档:
80-pc212-1_d_qualcomm_spectra_isp_camera_chi_api_reference.pdf
最次要还是要看code,手机厂商对该层代码有本人的改变,可能差异还是有的,具体我的项目大体框架统一但细节有区别,差别和机型的高通基线也保持一致.
2.1 CamX架构总体构造
简略总结下:
- Camx的架构入口为Camx包中的camxhal3entry.cpp,Camx中是高通平台Camx架构的外围跳转及解决业务的代码,个别手机厂商不会去更改,代码目录在vendor/qcom/proprietary/camx/下,编译后果是camera.qcom.so
- Camx通过chxentensionInterface调用到chi-cdk 包下的代码,这外面个别是手机厂商本人定制性能的中央,代码目录在vendor/qcom/proprietary/chi-cdk/,编译后果是com.qti.chi.override.so
- 从这张图中大略晓得一个request来业务流程交给camx解决,但会通过chi-cdk进行request定制化从新打包再交给camx理论去执行或和kernel driver层进行交互,camx局部代码即外围流程管控的代码,而chi-cdk正是手机厂商想要实现本人定制化性能的代码中央.
2.2 CamX架构中重要的数据结构及关系
缺一张图
Usecase /Session/ Feature /pipeline/ Node
- Chi对Camx的操作,须要通过 ExtensionModule 进行操作,因而,camx对外提供的接口扩大须要通过ExtensionModule进行,外面一个重要的变量就是g_chiContextOps。
- Camx对Chi的操作,是通过HAL3Module接口的m_ChiAppCallbacks进行的,因而chi外面开释的接口,都会在m_ChiAppCallbacks外面体现
- Usecase:基本上与App上的各种模式有肯定的对应关系,包含PreviewZSL,VideoLiveShot, SAT(Multicamera), RTB(Reatime Bokeh),QCFA, Dual, superslowmotionfrc…, 一个usecase蕴含了realtime和snapshot的session, 须要蕴含所有的在这个usecase的所有feature的pipeline列表
- Feature: 在Usecase下关上某个功能设计的一种架构,通常状况下一个Usecase能够启用各种不同的feature,feture蕴含了usecase外面的局部pipeline, 个别都是snapshot的才会蕴含feature进行解决,因而,usecase与feautre的界线并不显著。
- Session: 蕴含了ChxSession,ChiSession和CamxSession。Chxsession是对chi接口外面对CamxSession的封装,Usecase外面创立的Session都是要创立这一个。ChiSession是camx外面的一个局部,是对camxSession的继承和透传。
- pipeline-蕴含单个通过验证的topology的可重用容器。驱动程序通过pipeline来理解所应用的引擎以及数据处理的流程。
- Node—camera pipeline 内的逻辑功能块,在单个引擎上执行。node链接在一起形成一个topology。在CHI API的初始版本中,ISP内部的所有节点都是通过CPU代码调用的,调的是native API。
不同机型,产品性能及定位不同,即便基线一样usecase等也有可能不一样,高通这么做给了手机厂商极大的自定义空间,举个某机型例子,UseCase能够场景复用,对应的pipeline也能够不必或复用.
2.3 CamX操作过程
基本操作,截图自高通文档:
具体过程:
2.3.1 Open Camera
2.3.2 ConfigureStream
2.3.3 Request & Result
request:
result:
一旦底层有事件上传就会走到SyncManagerPollMethod中:file: vendor/qcom/proprietary/camx/src/csl/hw/camxsyncmanager.cpp--> VOID* SyncManager::SyncManagerPollMethod(VOID* pPollData) |--> rc = poll(fds, 2, -1) //监听的syncFd有事件上传 | |--> VOID* pData[] = {pEv, NULL}; //这里的pEv是蕴含回调办法的,依据setRepeatingRequest中的剖析,这里的回调就是Node::CSLFenceCallback | |--> ioctl(pCtrl->syncFd, VIDIOC_DQEVENT, pEv); //取出事件 | |--> result = pCtrl->pThreadManager->PostJob(pCtrl->hJob, SyncManager::StoppedCbDispatchJob, pData, FALSE, FALSE); //这里就会调到之前注册的线程并回调SyncManager::CbDispatchJob办法 file: vendor/qcom/proprietary/camx/src/csl/hw/camxsyncmanager.cpp | |--> VOID* SyncManager::CbDispatchJob(VOID* pData) | | |--> Utils::Memcpy(&ev, reinterpret_cast<struct v4l2_event*> (pData), sizeof(ev)); | | |--> pPayloadData = CAM_SYNC_GET_PAYLOAD_PTR(ev, uint64_t); | | |--> reinterpret_cast<CSLFenceHandler>(pPayloadData[0]))(reinterpret_cast<VOID* >(pPayloadData[1]), pEvHeader->sync_obj, fenceResult); //回调Node::CSLFenceCallback file: vendor/qcom/proprietary/camx/src/core/camxnode.cpp | | |--> VOID Node::CSLFenceCallback(...) | | | |--> result = pNode->GetThreadManager()->PostJob(pNode->GetJobFamilyHandle(), NULL, &pData[0], FALSE, FALSE) //将工作放到JobFamilyHandle线程去做,回调Node::NodeThreadJobFamilyCb办法 | | | | |--> VOID* Node::NodeThreadJobFamilyCb(...) //因为是另一个线程解决,所以这个以缩进代表异步关系 | | | | | |--> FenceCallbackData* pFenceCallbackData = static_cast<FenceCallbackData*>(pCbData); | | | | | |--> NodeFenceHandlerData* pNodeFenceHandlerData = static_cast<NodeFenceHandlerData*>(pFenceCallbackData->pNodePrivateData); | | | | | |--> pFenceCallbackData->pNode->ProcessFenceCallback(pNodeFenceHandlerData); | | | | | | |--> OutputPort* pOutputPort = pFenceHandlerData->pOutputPort; | | | | | | |--> UINT64 requestId = pFenceHandlerData->requestId; | | | | | | |--> UINT requestIdIndex = requestId % MaxRequestQueueDepth; | | | | | | |--> m_pPipeline->NonSinkPortFenceSignaled(&pFenceHandlerData->hFence, pFenceHandlerData->requestId); //如果是no sink port file: vendor/qcom/proprietary/camx/src/core/camxpipeline.cpp | | | | | | |--> VOID Pipeline::NonSinkPortFenceSignaled(...) | | | | | | | |--> m_pDeferredRequestQueue->FenceSignaledCallback(phFence, requestId); file: vendor/qcom/proprietary/camx/src/core/camxdeferredrequestqueue.cpp | | | | | | | |--> VOID DeferredRequestQueue::FenceSignaledCallback(...) | | | | | | | | |--> UpdateDependency(PropertyIDInvalid, phFence, NULL, requestId, 0, TRUE); | | | | | | | | |--> DispatchReadyNodes(); | | | | | | | | | |--> CamxResult result = m_pThreadManager->PostJob(m_hDeferredWorker, NULL, &pData[0], FALSE, FALSE); //针对所有ready Nodes 循环解决,m_hDefferredWorker 对应的回调是DeferredWorkerWrapper | | | | | | | | | | |--> VOID* DeferredRequestQueue::DeferredWorkerWrapper(VOID* pData) //异步调用,以缩进示意 | | | | | | | | | | | |--> Dependency* pDependency = reinterpret_cast<Dependency*>(pData); | | | | | | | | | | | |--> DeferredRequestQueue* pDeferredQueue = pDependency->pInstance; | | | | | | | | | | | |--> result = pDeferredQueue->DeferredWorkerCore(pDependency); | | | | | | | | | | | | |--> pNode->ProcessRequest(&processRequest, pDependency->requestId) file: vendor/qcom/proprietary/camx/src/core/camxnode.cpp | | | | | | | | | | | | |--> CamxResult Node::ProcessRequest(NodeProcessRequestData* pNodeRequestData, UINT64 requestId) | | | | | | | | | | | | | |--> result = ExecuteProcessRequest(&executeProcessData); //这里临时以ipe node为例进行剖析 file: vendor/qcom/proprietary/camx/src/hwl/ipe/camxipenode.cpp | | | | | | | | | | | | | |--> CamxResult IPENode::ExecuteProcessRequest(ExecuteProcessRequestData* pExecuteProcessRequestData) | | | | | | | | | | | | | | |--> GetHwContext()->Submit(GetCSLSession(), m_hDevice, pIQPacket); //发送设置命令 file: vendor/qcom/proprietary/camx/src/core/camxhwcontext.cpp | | | | | | | | | | | | | | |--> CamxResult HwContext::Submit(CSLHandle hCSLSession, CSLDeviceHandle hDevice, Packet* pPacket) | | | | | | | | | | | | | | | |--> result = CSLSubmit(hCSLSession, hDevice, pPacket->GetMemHandle(), pPacket->GetOffset()); file: vendor/qcom/proprietary/camx/src/csl/camxcsl.cpp | | | | | | | | | | | | | | | |--> CamxResult CSLSubmit(...) | | | | | | | | | | | | | | | | |--> return pJumpTable->CSLSubmit(hCSL, hDevice, hPacket, offset); //跳转办法见func_list_camx_chi ⑤ ,接下来我就不剖析了 | | | | | | |--> m_pPipeline->SinkPortFenceSignaled(pOutputPort->sinkTargetStreamId, ...) //如果是sink port file: vendor/qcom/proprietary/camx/src/core/camxpipeline.cpp | | | | | | |--> VOID Pipeline::SinkPortFenceSignaled(...) | | | | | | | |--> ResultsData resultsData = {}; | | | | | | | |--> resultsData.pPrivData = pPerRequestInfo->request.pPrivData; | | | | | | | |--> resultsData.type = CbType::Buffer; | | | | | | | |--> m_pSession->NotifyResult(&resultsData); file: vendor/qcom/proprietary/camx/src/core/camxsession.cpp | | | | | | | |--> VOID Session::NotifyResult(ResultsData* pResultsData) | | | | | | | | |--> switch (pResultsData->type) | | | | | | | | |--> case CbType::Buffer: | | | | | | | | |--> HandleBufferCb(&pResultsData->cbPayload.buffer, pResultsData->pipelineIndex, pResultsData->pPrivData); | | | | | | | | | |--> InjectResult(ResultType::BufferOK, &outBuffer, pPayload->sequenceId, pPrivData, pipelineIndex); | | | | | | | | | | |--> result = m_pThreadManager->PostJob(m_hJobFamilyHandle, NULL, &pData[0], FALSE, FALSE); //这里的线程cb是CHISession::ThreadJobCallback,为什么呢? file: vendor/qcom/proprietary/camx/src/core/chi/camxchisession.cpp | | | | | | | | | | |--> VOID* CHISession::ThreadJobCallback(VOID* pData) | | | | | | | | | | | |--> result = pSession->ThreadJobExecute(); | | | | | | | | | | | | |--> result = ProcessResults(); file: vendor/qcom/proprietary/camx/src/core/camxsession.cpp | | | | | | | | | | | | |--> CamxResult Session::ProcessResults() | | | | | | | | | | | | | |--> LightweightDoublyLinkedListNode* pNode = m_resultHolderList.Head(); | | | | | | | | | | | | | |--> bufferReady = ProcessResultBuffers(pResultHolder, metadataReady, &numResults); //针对每一个node都调用这个办法,感觉这里就是全副node解决buffer的一个过程,前期须要确认下 | | | | | | | | | | | | | |--> pNode = m_resultHolderList.NextNode(pNode); //取出下一个node进行操作 | | | | | | | | | | | | | |--> DispatchResults(&m_pCaptureResult[0], numResults); //发送results | | | | | | | | | | | | | | |--> m_chiCallBacks.ChiProcessCaptureResult(&pCaptureResults[index], m_pPrivateCbData); //这里的m_chiCallBacks的介绍见func_list_camx_chi ⑦ file: vendor/qcom/proprietary/chi-cdk/vendor/chioverride/default/chxadvancedcamerausecase.h | | | | | | | | | | | | | | |--> static VOID ProcessResultCb(...) | | | | | | | | | | | | | | | |--> static_cast<AdvancedCameraUsecase*>(pCbData->pUsecase)->ProcessResult(pResult, pPrivateCallbackData); file: vendor/qcom/proprietary/chi-cdk/vendor/chioverride/default/chxadvancedcamerausecase.cpp | | | | | | | | | | | | | | | |--> VOID AdvancedCameraUsecase::ProcessResult(...) | | | | | | | | | | | | | | | | |--> pFeature->ProcessResult(pResult, pPrivateCallbackData); //如果是AdvancedFeatureEnabled,这里咱们思考的是预览,所以不是这分支 | | | | | | | | | | | | | | | | |--> CameraUsecaseBase::SessionCbCaptureResult(pResult, pPrivateCallbackData); | | | | | | | | | | | | | | | | | |--> pCameraUsecase->SessionProcessResult(pCaptureResult, pSessionPrivateData); | | | | | | | | | | | | | | | | | | |--> camera3_capture_result_t* pUsecaseResult = GetCaptureResult(resultFrameIndex); | | | | | | | | | | | | | | | | | | |--> ChxUtils::PopulateChiToHALStreamBuffer(&pResult->pOutputBuffers[i], pResultBuffer); | | | | | | | | | | | | | | | | | | |--> ProcessAndReturnFinishedResults(); | | | | | | | | | | | | | | | | | | | |--> Usecase::ReturnFrameworkResult(&result, m_cameraId); file: vendor/qcom/proprietary/chi-cdk/vendor/chioverride/default/chxusecase.cpp | | | | | | | | | | | | | | | | | | | |--> VOID Usecase::ReturnFrameworkResult(...) | | | | | | | | | | | | | | | | | | | | |--> ExtensionModule::GetInstance()->ReturnFrameworkResult(pResult, cameraID); file: vendor/qcom/proprietary/chi-cdk/vendor/chioverride/default/chxextensionmodule.cpp | | | | | | | | | | | | | | | | | | | | |--> VOID ExtensionModule::ReturnFrameworkResult(const camera3_capture_result_t* pResult, UINT32 cameraID) | | | | | | | | | | | | | | | | | | | | | |--> m_pHALOps->process_capture_result(m_logicalCameraInfo[cameraID].m_pCamera3Device, pResult); //这里就将result给到下面了
2.3.4 Flush
三、Qcom Hal3 Camx 重点
上面是具体我的项目上的工作者须要晓得的camera相干的点,具体细节不赘述,这里记录下,不便记忆.
3.1 定制化
定制pipeline要走那些算法及节点配置在xml中,相似鱼如下门路
vendor/qcom/proprietary/chi-cdk/vendor/topology/qcom/sdm845/sdm845_usecase.xml
源码下可能因为共线有多个,要看清楚makefile中指定的是哪个.
bypass 是高通做的一套框架,目标在于缩小不必要的拷贝。
3.2 性能perflock
高通的perflock调试,须要把握,能够通过抓取systrace来看cpu相干的变动,也能够通过读取
/sys/devices/system/cpu/下的信息来考察cpu变动状况.
3.3 常见的node及作用
Node | 作用 |
---|---|
BPS | Bayer Processing Segment,Bayer解决阶段。仅做snapshot的噪点升高和Bayer解决,不良像素、PDAF、LSC校对,绿色不均衡校对,彩色级别,通道增益,demosaic,Down scaler,HDR的合并与记录,Bayer混合降噪等 |
IFE | Image front-end engine,图像前端引擎。仅做video/preview的Bayer解决,做些色彩校对,Down scaler,demosaic,统计3A数据等 |
IPE | Image-processing engine,图像处理引擎。由NPS、PPS两局部组成,次要做些硬件降噪(MFNR、MFSR)、调整大小、噪声解决、色彩解决(色差校对、色度克制)、细节加强(肤色加强) |
JPEG | 打包jpeg |
STATS | for 3A,ISP硬件给出的3A数据,用于前面的3A算法 |
IS : | 图像稳固 (Image Stabilization) |
RDI : | 原始数据转储接口 (Raw Dump Interface) |
RoI AF : | 感兴趣区域 (AF Region of Interest) |
SNoC 零碎: | NoC (System NoC) |
SOF: | start of frame |
ANR: | application not response |
MCC: | mutil camera control |
LPM: | low power manager(低功耗下运行) |
CTS/ITS : | Android Camera Image Test Suite (ITS) is part of Android Compatibility Test Suite (CTS),Android相机图像测试套件(ITS)是Android兼容性测试套件(CTS)的一部分验证程序,包含验证图像内容的测试。从CTS 7.0_r8开始,CTS Verifier通过一体式摄像机ITS反对ITS测试自动化。持续反对手动测试,以确保涵盖所有Android设施尺寸。 |
HAF: | 混合主动变焦 |
CRM: | camera request manager |
Sensor CRA(主光线角): | 从镜头的传感器一侧,能够聚焦到像素上的光线的最大角度被定义为一个参数,称为主光角(CRA)。对于主光角的一般性定义是:此角度处的像素响应升高为零度角像素响应(此时,此像素是垂直于光线的)的80%,https://blog.csdn.net/weixin_...,lens CRA与sensor 不配会使sensor 的pixel 呈现在光检测区域四周,使pixel 曝光有余,亮度不够,会使整个画面造成亮度不平均的状况。还有可能造成chroma shading 或部分色偏。部分色偏比较严重,无奈用算法弥补 |
Sensor HDR: | sensor在一幅图像里可能同时体现高光和暗影局部内容的能力 |
lens fov: | 视场角,视场角与焦距的关系:个别状况下,视场角越大,焦距就越短. |
IFE : | Image Front End, Bayer processing for video/preview only, HDR/De-mosic, color correction ,scaler,也能够间接输入Raw到RDI |
RDI : | Raw Dump Interface,间接从IFE吐出来用于capture的 |
STATS: | for 3A,ISP硬件给出的3A数据,用于前面的3A算法 |
PDPC: | PhaseDetection Pixel Correction,相位检测像素校对 |
ASD: | Auto scene detection,主动场景检测 |
CSIC: | Camera serial interface decoder,摄像机串行接口解码器 |
CAMIF: | Ideal Raw的第一个dump点 |
FD: | Face-based,基于人脸,也就是人脸识别 |
ICA: | 图像校对和调整 是一个硬件单元,次要用于由镜头和静止引起的几何扭曲 |
LENR: | 低/中频加强和降噪 |
TMC: | 色调映射管制 |
CSID: | 摄像机串行接口解码器模块 |
IFE: | 图像前端 |
BPS: | Bayer解决段 |
IPE: | 图像处理引擎 |
VPU: | 视频处理单元 |
DPU: | 显示处理单元 |
BPC: | 坏像素校对 |
BCC: | 坏群集校对 |
ABF: | 自适应拜耳滤波器 |
GIC: | 绿色不均衡校对 |
GTM: | 全局色调映射 |
HNR: | 混合降噪 |
ANR: | 先进的降噪性能 |
TF: | 工夫过滤器 |
MFNR: | 多帧降噪 |
LTM: | 部分色调映射 |
CS: | 色度克制 |
ASF: | 自适应空间滤波器 |
Upscaler: | 升频器 |
GRA: | Grain Adder(纹理减少器?)?? |
CPAS: | 相机外围设备和反对 |
CAMIF: | 摄像头接口???它是VFE(video front-end)硬件的第一局部,次要工作是同步sensor发送数据过程中波及到的行、场同步信号。另外它还具备图像提取和图像镜像能力,CAMIF hardware使内部camera sensor可能通过一些简略的内部协定链接到用户单元。为camera提供了数据和时钟接口,但并没有提供管制接口,最具代表行的是用I2C做配置和状态接口。当然,也能够是其余的一次管制信号做一些动态管制。例如:睡眠唤醒模式管制。 |
NPS: | 噪声解决局部 |
PPS: | 后处理局部 |
MCTF: | 静止弥补工夫滤波 CAC、CCM、GLUT、2D LUT(二维查找表?)、CV(色彩转换)、CC(色彩校对)、SCE(肤色加强)、MCE(记忆色调加强):??? |
SIMO: | 单输出多输入 |
Pedestal Correction: | 基座校对 |
Down Scaler: | 升高规模(尺寸) |
Chroma Enhancement: | 色度加强 |
Chroma Suppression: | 色度克制 |
PDAF: | 相位检测主动对焦 |
LSC: | 镜头暗影校对 |
PNR: | 峰值降噪 |
ADRC: | 主动动静范畴压缩 |
Backlit scene: | 背光场景 |
Garage scene: | 车库场景 |
HNR: | 升高亮度噪声,但放弃纹理细节 |
LDC: | 镜头畸变校对 |
EIS: | 电子稳像 |
Multi pass spatial noise filtering: | 多通空间噪声滤波 |
LNR: | 镜头降噪 |
Invert gamma: | 反转伽玛 |
hue, saturation, lightness: | 色调,饱和度,亮度 |
Upscaler: | 升频器 |
ACE: | 高级色度加强 |
CPP: | 相机后处理器(相当于新版的BPS、IPE) |
BSP: | board support package,板级反对安装包?也就是“做出反对安装包,来实现手机上各个硬件的基本功能”。 |
CCT: | correlated color temperature,相干色温,具体不详; |
chi-cdk: | Camera Hardward Interface 相机硬件接口;Camera Development Kit,相机开发包; |
HFR: | High Frame Rate, min HFR=90, means>=90时,须要enable HFR高帧率,目前最高960,然而是利用插值算法计算得出的,非理论960 |
3.4 日志材料
Camx日志由属性管制,具体也是一套规定,首先获取权限:adb root && adb remount
,如果remount报错:failed: Read-only file system
,则执行adb disable-verity && adb reboot
解决,而后从新adb root && adb remount
.
Camera user mode driver (UMD) ——相机用户模式驱动
UMD的显示格局
CamX: [<Verbosity Level>][<Group>] <File>:<Line Number> <Function Name> <Message>
例如:CamX : INFO camxhal3.cpp:1086 process_capture_request() frame_number 140
这里将log分为不同的level,不同的level上面有不同的group
如何关上指定level的指定group的log?其各level和group见相应的camxtypes.h
如何关上指定level? level间接通过Name设定;
group通过二进制的每一位来确定,每个group对应一个bit,置1示意关上
例如:logVerboseMask=0xFFFFFFFF //关上所有group的verbose级别的log
logWarningMask=0x82 //Warning级别关上第1个位和第6位示意的group,其实是Sensor和HAL的group
平时测试开的log:
如何理论进行关上log
在手机中的: /vendor/etc/camera/camxoverridesettings.txt
例如:adb shell "echo logInfoMask=0xFFFFFFFF>> /vendor/etc/camera/camxoverridesettings.txt"
chi log:
例如:CHX_LOG_ERROR(fmt, args);
adb shell "echo overrideLogLevels=0x1f >> /vendor/etc/camera/camxoverridesettings.txt"或:adb shell setprop vendor.debug.camera.overrideLogLevels 0x1F (camxsettings.xml中定义,不同的代码可能有区别)
平时开的log:
adb shell "echo logInfoMask=0x50080 >> /vendor/etc/camera/camxoverridesettings.txt" hal/core/chiadb shell setprop persist.vendor.camera.logVerboseMask 0xFFFFFFFFadb shell setprop persist.vendor.camera.logEntryExitMask 0xFFFFFFFFadb shell setprop persist.vendor.camera.logInfoMask 0xFFFFFFFFadb shell setprop persist.vendor.camera.logWarningMask 0xFFFFFFFFadb shell setprop persist.vendor.camera.logConfigMask 0xFFFFFFFFadb shell setprop persist.vendor.camera.systemLogEnable TRUEadb shell setprop persist.vendor.camera.logLogDRQMask 0xFFFFFFFF
camx log:
adb shell "echo overrideLogLevels=0xF >> /vendor/etc/camera/camxoverridesettings.txt" adb shell "echo logVerboseMask=0x1000 >> /vendor/etc/camera/camxoverridesettings.txt"adb shell "echo logInfoMask=0xFFFFFFFF >> /vendor/etc/camera/camxoverridesettings.txt"
原文链接:https://blog.csdn.net/TaylorP...
情谊举荐:
Android 干货分享
至此,本篇已完结,如有不对的中央,欢迎您的倡议与斧正。同时期待您的关注,感谢您的浏览,谢谢!