From a40330ee8a9303ec64c3073889fb27c1d29f5d76 Mon Sep 17 00:00:00 2001 From: guan_renjie Date: Fri, 24 Sep 2021 16:17:29 +0800 Subject: [PATCH] add driver-peripherals-camera-des.md Signed-off-by: guan_renjie --- zh-cn/device-dev/driver/Readme-CN.md | 1 + .../driver/driver-peripherals-camera-des.md | 643 ++++++++++++++++++ .../figure/logic-view-of-camera-hal-zh.png | Bin 0 -> 41343 bytes 3 files changed, 644 insertions(+) create mode 100644 zh-cn/device-dev/driver/driver-peripherals-camera-des.md create mode 100644 zh-cn/device-dev/driver/figure/logic-view-of-camera-hal-zh.png diff --git a/zh-cn/device-dev/driver/Readme-CN.md b/zh-cn/device-dev/driver/Readme-CN.md index 27caa89536..ad61b92f4e 100755 --- a/zh-cn/device-dev/driver/Readme-CN.md +++ b/zh-cn/device-dev/driver/Readme-CN.md @@ -33,4 +33,5 @@ - [TOUCHSCREEN](driver-peripherals-touch-des.md) - [SENSOR](driver-peripherals-sensor-des.md) - [WLAN](driver-peripherals-external-des.md) + - [CAMERA](driver-peripherals-camera-des.md) diff --git a/zh-cn/device-dev/driver/driver-peripherals-camera-des.md b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md new file mode 100644 index 0000000000..0a89a7196e --- /dev/null +++ b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md @@ -0,0 +1,643 @@ +# Camera + +- [概述](#section11660541593) +- [开发指导](#section161941989596) + - [接口说明](#section1551164914237) + - [开发步骤](#section19806524151819) + - [开发实例](#section1564411661810) + + +## 概述 + +OpenHarmony 相机驱动框架模型对上实现相机HDI(Hardware Driver Interface)接口,对下实现相机Pipeline模型,管理相机各个硬件设备。 +各层的基本概念如下: + +1. HDI实现层,对上实现OHOS相机标准南向接口。 + +2. 框架层,对接HDI实现层的控制、流的转发,实现数据通路的搭建、管理相机各个硬件设备等功能。 + +3. 适配层,屏蔽底层芯片和OS差异,支持多平台适配。 + +**图 1 基于HDF驱动框架的Camera驱动模型** + +![](figure/logic-view-of-camera-hal-zh.png) + +1) + +系统启动时创建CameraDeviceHost进程。进程创建后,首先枚举底层设备,创建(也可以通过配置表创建)管理设备树的DeviceManager类及其内部各个底层设备的对象,创建对应的CameraHost类实例并且将其注册到UHDF服务中,方便上层通过UHDF服务获取到底层CameraDeviceHost的服务从而操作底层设备。 + +2) + +Service设备通过获取CameraDeviceHost的服务,获取到CameraHost实例,通过CameraHost可以获取底层的Camera能力,打开手电筒、调用Open接口打开Camera创建连接、创建DeviceManager(负责底层硬件模块上电)、创建CameraDevice(向上提供设备控制接口)。创建CameraDevice的时候会实例化PipelineCore的各个子模块,其中StreamPiplineCore负责创建Pipeline,MetaQueueManager负责上报meta。 + +3) + +Service通过底层的CameraDevice配置流、创建Stream类。StreamPipelineStrategy模块通过上层下发的模式和查询配置表创建对应流的Node连接方式,通过StreamPipelineBuilder模块创建Node实例并且连接返回该Pipline给StreamPipelineDispatcher。StreamPipelineDispatcher提供统一的Pipline调用管理。 + +4) + +Service通过Stream控制整个流的操作,AttachBufferQueue会将从显示模块申请的BufferQueue下发到底层,由CameraDeviceDriverModel自行管理buffer,通过Capture接口下发命令,底层开始向上传递buffer。首先,底层的ispNode,启动线程依次从各个BufferQueue中获取buffer下发到底层ISP硬件填充,ISP填充完毕之后将buffer传递给CameraDeviceDriverModel,CameraDeviceDriverModel通过循环线程将buffer填到已经创建好的Pipeline中,各个Node处理后,通过回调传递给上层,同时buffer返回BufferQueue等待下一次下发。 + +5) + +Service通过Capture接口下发拍照命令,具体操作和预览一致。通过ChangeToOfflineStream查询拍照buffer位置,如果isp已经出图,并且图像数据已经送到IPP node,可以将普通拍照流转换为离线流,否则直接走关闭流程。通过传递Streaminfo使离线流获取到普通流的流信息并且通过配置表确认离线流的具体Node连接方式,创建离线流的Node连接(如果已创建则通过CloseCamera释放非离线流所需的Node)等待buffer从底层Pipeline回传到上层再释放持有的Pipeline相关的资源。 + +6) + +Service通过CameraDevice的UpdateSettings接口向下发送CaptureSetting参数,CameraDeviceDriverModel通过StreamPipelineDispatcher模块向各个Node转发;StartStreamingCapture和Capture接口携带的CaptureSetting通过StreamPipelineDispatcher模块向该流所属的Node转发。 + +7) + +Service可以通过EnableResult和DisableResult接口控制底层meta的上报。如果需要底层meta上报,pipeline会创建CameraDeviceDriverModel内部的一个Bufferqueue用来收集和传递meta,通过StreamPipelineStrategy模块查询配置表并通过StreamPipelineBuilder创建和连接Node,MetaQueueManager下发buffer至底层,通过底层相关Node填充数据,通过MetaQueueManager模块调用上层回调传递给上层。 + +8) + +Service调用CameraDevice的Close接口,CameraDevice调用对应的DeviceManager模块对各个硬件下电;如果此时存在OfflineStream,并且offlinestream在ipp的subpipeline中,则需要保留offlineStream,直到执行完毕。 + +9) + +动态帧率控制:StreamOperator中起一个CollectBuffer线程,CollectBuffer线程,会去从每一路stream中的BufferQueue获取buffer,如果某一路流的帧率需要控制(为sensor出帧帧率的1/n),可以根据需求控制每一帧的buffer打包,是否collect此路流的buffer(比如sensor出帧帧率为120fps,预览流的帧率为30fps,CollectBuffer线程collect预览流的buffer时,每隔4fps collect一次)。 + + + +## 开发指导 + +### HDI接口说明 +旨在了解接口作用以及函数参数的传递规则,详情可见[Camera驱动子系统HDI使用说明](https://gitee.com/openharmony/drivers_peripheral/blob/master/camera/README_zh.md) + + +### 开发步骤 + +下面分步骤描述了Camera 驱动框架从注册,检测到如何创建,捕获,销毁流。打开和关闭设备的主要接口(该章节源码展示部分有可能删除了部分判错和 LOG等代码,是为了更清晰的展示和描述主要功能实现部分) +#### 步骤1 注册Camera host: + +定义Camera的HdfDriverEntry结构体,该结构体中定义了camera host初始化的一些方法。 + + struct HdfDriverEntry g_cameraHostDriverEntry = { + .moduleVersion = 1, + .moduleName = "camera_service", + .Bind = HdfCameraHostDriverBind, + .Init = HdfCameraHostDriverInit, + .Release = HdfCameraHostDriverRelease, + }; + HDF_INIT(g_cameraHostDriverEntry); //将Camera 的HdfDriverEntry结构体注册到HDF上 + + +#### 步骤2 Camera host初始化: +上文提到的HdfCameraHostDriverBind接口被调用后提供了CameraServiceDispatch和CameraHostStubInstance的注册。这两个接口一个是远端调用Camer Host 的方法如OpenCamera(),SetFlashlight()等。一个是Camera 设备的初始化,在开机时被调用。 + + int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject) + { + HDF_LOGI("HdfCameraHostDriverBind enter!"); + if (deviceObject == nullptr) { + HDF_LOGE("HdfCameraHostDriverBind: HdfDeviceObject is NULL !"); + return HDF_FAILURE; + } + HdfCameraService *hdfCameraService = reinterpret_cast(OsalMemAlloc(sizeof(HdfCameraService))); + if (hdfCameraService == nullptr) { + HDF_LOGE("HdfCameraHostDriverBind OsalMemAlloc HdfCameraService failed!"); + return HDF_FAILURE; + } + hdfCameraService->ioservice.Dispatch = CameraServiceDispatch; //提供远端Camera Host 调用方法 + hdfCameraService->ioservice.Open = nullptr; + hdfCameraService->ioservice.Release = nullptr; + hdfCameraService->instance = CameraHostStubInstance(); // 初始化Cmaera 设备 + deviceObject->service = &hdfCameraService->ioservice; + return HDF_SUCCESS; + } + +下面的函数包含了远端CameraHost 调用的方法 + + int32_t CameraHostStub::CameraHostServiceStubOnRemoteRequest(int cmdId, MessageParcel &data, + MessageParcel &reply, MessageOption &option) + { + switch(cmdId) { + case CMD_CAMERA_HOST_SET_CALLBACK: { + return CameraHostStubSetCallback(data, reply, option); + } + case CMD_CAMERA_HOST_GET_CAMERAID: { + return CameraHostStubGetCameraIds(data, reply, option); + } + case CMD_CAMERA_HOST_GET_CAMERA_ABILITY: { + return CameraHostStubGetCameraAbility(data, reply, option); + } + case CMD_CAMERA_HOST_OPEN_CAMERA: { + return CameraHostStubOpenCamera(data, reply, option); + } + case CMD_CAMERA_HOST_SET_FLASH_LIGHT: { + return CameraHostStubSetFlashlight(data, reply, option); + } + default: { + HDF_LOGE("%s: not support cmd %d", __func__, cmdId); + return HDF_ERR_INVALID_PARAM; + } + } + return HDF_SUCCESS; + } + +CameraHostStubInstance()接口最终会调用到CameraHostImpl::Init(),改方法里会获取物理Camera并对DeviceManager和PipelineCore进行一些初始化。 + + +#### 步骤3 获取CamerHost: +调用该接口会从远端CameraService中获取CameraHost对象 +` sptr ICameraHost::Get(const char *serviceName) + { + do { + using namespace OHOS::HDI::ServiceManager::V1_0; + auto servMgr = IServiceManager::Get(); + if (servMgr == nullptr) { + HDF_LOGE("%s: IServiceManager failed!", __func__); + break; + } + auto remote = servMgr->GetService(serviceName); //根据serviceName名称获取CameraHost + if (remote != nullptr) { + sptr hostSptr = iface_cast(remote); //将CameraHostProxy对象返回给调用者,该对象中包含OpenCamera()等方法。 + return hostSptr; + } + HDF_LOGE("%s: GetService failed! serviceName = %s", __func__, serviceName); + } while(false); + HDF_LOGE("%s: get %s failed!", __func__, serviceName); + return nullptr; + }` + +#### 步骤4 OpenCamera: +获取到CameraHost对象,该对象中有五个方法,SetCallback,GetCameraIds,GetCameraAbility,OpenCamera,SetFlashlight。下面着重描述OpenCamera接口 + + CameraHostProxy的OpenCamera主要是通过CMD_CAMERA_HOST_OPEN_CAMERA调用到远端的CameraHostStubOpenCamera()并获取ICameraDevice对象。 + CamRetCode CameraHostProxy::OpenCamera(const std::string &cameraId, const OHOS::sptr &callback, OHOS::sptr &pDevice) + { + int32_t ret = Remote()->SendRequest(CMD_CAMERA_HOST_REMOTE_OPEN_CAMERA, data, reply, option); + if (ret != HDF_SUCCESS) { + HDF_LOGE("%{public}s: SendRequest failed, error code is %{public}d", __func__, ret); + return INVALID_ARGUMENT; + } + CamRetCode retCode = static_cast(reply.ReadInt32()); + bool flag = reply.ReadBool(); + if (flag) { + sptr remoteCameraDevice = reply.ReadRemoteObject(); + if (remoteCameraDevice == nullptr) { + HDF_LOGE("%{public}s: CameraHostProxy remoteCameraDevice is null", __func__); + } + pDevice = OHOS::iface_cast(remoteCameraDevice); + } + return retCode; + } + +Remote()->SendRequest调用到上文提到的CameraHostServiceStubOnRemoteRequest()中,根据cmdId进入CameraHostStubOpenCamera()接口。最终会调用到CameraHostImpl::OpenCamera()中。该接口获取了 +CameraDevice并对硬件进行上电等操作。 + + CamRetCode CameraHostImpl::OpenCamera(const std::string &cameraId, const OHOS::sptr &callback, OHOS::sptr &device) + { + std::shared_ptr cameraDevice = std::static_pointer_cast(itr->second); + if (cameraDevice == nullptr) { + CAMERA_LOGE("camera device is null."); + return INSUFFICIENT_RESOURCES; + } + CamRetCode ret = cameraDevice->SetCallback(callback); + if (ret != NO_ERROR) { + CAMERA_LOGW("set camera device callback faild."); + return ret; + } + CameraHostConfig *config = CameraHostConfig::GetInstance(); + if (config == nullptr) { + return INVALID_ARGUMENT; + } + std::vector phyCameraIds; + RetCode rc = config->GetPhysicCameraIds(cameraId, phyCameraIds); + if (rc != RC_OK) { + CAMERA_LOGE("get physic cameraId failed."); + return DEVICE_ERROR; + } + if (CameraPowerUp(cameraId, phyCameraIds) != RC_OK) { //对Camera硬件上电 + CAMERA_LOGE("camera powerup failed."); + CameraPowerDown(phyCameraIds); + return DEVICE_ERROR; + } + + auto sptrDevice = deviceBackup_.find(cameraId); + if (sptrDevice == deviceBackup_.end()) { + deviceBackup_[cameraId] = cameraDevice.get(); + } + device = deviceBackup_[cameraId]; //将ICameraDevice带出。 + cameraDevice->SetStatus(true); + return NO_ERROR; + } + +#### 步骤5 通过CameraDevice获取GetStreamOperator对象: +IStreamOperator定义了一系列对流控制和操作的接口,主要有CreateStreams,CommitStreams,Capture,CancelCapture等。 + + CamRetCode CameraDeviceImpl::GetStreamOperator(const OHOS::sptr &callback, + OHOS::sptr &streamOperator) + { + if (callback == nullptr) { + CAMERA_LOGW("input callback is null."); + return INVALID_ARGUMENT; + } + spCameraDeciceCallback_ = callback; + if (spStreamOperator_ == nullptr) { + //这里new了一个spStreamOperator对象传递给调用者,以便对stream的各种操作。 + spStreamOperator_ = new(std::nothrow) StreamOperatorImpl(spCameraDeciceCallback_, shared_from_this()); + if (spStreamOperator_ == nullptr) { + CAMERA_LOGW("create stream operator failed."); + return DEVICE_ERROR; + } + ismOperator_ = spStreamOperator_; + } + streamOperator = ismOperator_; + + spStreamOperator_->SetRequestCallback([this](){ + cameraDeciceCallback_->OnError(REQUEST_TIMEOUT, 0); + }); + } + +#### 步骤6 创建流: +调用CreateStreams创建流前需要填充StreamInfo结构体,如下: + using StreamInfo = struct _StreamInfo { + int streamId_; + int width_; //数据流宽 + int height_; //数据流高 + int format_; //数据流格式,如PIXEL_FMT_YCRCB_420_SP + int datasapce_; + StreamIntent intent_; //StreamIntent 如PREVIEW + bool tunneledMode_; + OHOS::sptr bufferQueue_; //数据流bufferQueue 可用streamCustomer->CreateProducer();接口创建 + int minFrameDuration_; + EncodeType encodeType_; + }; + +CreateStreams接口,该接口为StreamOperatorImpl类中的方法,主要作用是创建一个StreamBase对象,通过StreamBase的Init方法去初始化去CreateBufferPool等操作。 + + RetCode StreamOperatorImpl::CreateStream(const std::shared_ptr& streamInfo) + { + static std::map typeMap = { + {PREVIEW, "PREVIEW"}, + {VIDEO, "VIDEO"}, + {STILL_CAPTURE, "STILL_CAPTURE"}, + {POST_VIEW, "POST_VIEW"}, {ANALYZE, "ANALYZE"}, + {CUSTOM, "CUSTOM"} + }; + + auto itr = typeMap.find(streamInfo->intent_); + if (itr == typeMap.end()) { + CAMERA_LOGE("do not support stream type. [type = %{public}d]", streamInfo->intent_); + return RC_ERROR; + } + std::shared_ptr stream = StreamFactory::Instance().CreateShared(itr->second); //创建StreamBase实例 + RetCode rc = stream->Init(streamInfo); //调用StreamBase Init方法,CreateBufferPool + return RC_OK; + } + + + +#### 步骤7 CommitStreams() 该接口主要作用是init 和创建Pipeline + CamRetCode StreamOperatorImpl::CommitStreams(OperationMode mode, const std::shared_ptr& modeSetting) + { + auto cameraDevice = cameraDevice_.lock(); + if (cameraDevice == nullptr) { + CAMERA_LOGE("camera device closed."); + return CAMERA_CLOSED; + } + std::shared_ptr PipelineCore = + std::static_pointer_cast(cameraDevice)->GetPipelineCore(); + if (PipelineCore == nullptr) { + CAMERA_LOGE("get pipeline core failed."); + return CAMERA_CLOSED; + } + + streamPipeCore_ = PipelineCore->GetStreamPipelineCore(); + if (streamPipeCore_ == nullptr) { + CAMERA_LOGE("get stream pipeline core failed."); + return DEVICE_ERROR; + } + + RetCode rc = streamPipeCore_->Init(); //对pipelinecore的初始化 + if (rc != RC_OK) { + CAMERA_LOGE("stream pipeline core init failed."); + return DEVICE_ERROR; + } + rc = streamPipeCore_->CreatePipeline(mode); //创建一个pipeline + if (rc != RC_OK) { + CAMERA_LOGE("create pipeline failed."); + return INVALID_ARGUMENT; + } + return NO_ERROR; + } + +#### 步骤8 Capture: +在调用Capture()接口前需要先填充CaptureInfo结构体: + + using CaptureInfo = struct _CaptureInfo { + std::vector streamIds_; //需要Capture的streamIds + std::shared_ptr captureSetting_; // 这里填充camera ability 可通过CameraHost 的GetCameraAbility()接口获取 + bool enableShutterCallback_; + }; + +StreamOperatorImpl中的Capture方法主要调用CreateCapture()接口去捕获数据流: + + CamRetCode StreamOperatorImpl::Capture(int captureId, const std::shared_ptr& captureInfo, bool isStreaming) + { + if (!ValidCaptureInfo(captureId, captureInfo)) { + CAMERA_LOGE("capture streamIds is empty. [captureId = %d]", captureId); + return INVALID_ARGUMENT; + } + std::shared_ptr cameraCapture = nullptr; + RetCode rc = CreateCapture(captureId, captureInfo, isStreaming, cameraCapture); + if (rc != RC_OK) { + CAMERA_LOGE("create capture is failed."); + return DEVICE_ERROR; + } + + { + std::unique_lock lock(captureMutex_); + camerCaptureMap_.insert(std::make_pair(captureId, cameraCapture)); + } + + rc = StartThread(); + if (rc != RC_OK) { + CAMERA_LOGE("preview start failed."); + return DEVICE_ERROR; + } + return NO_ERROR; + } + +#### 步骤9 CancelCapture和ReleaseStreams。 +StreamOperatorImpl类中的CancelCapture接口的主要作用是取消数据流的捕获 + + CamRetCode StreamOperatorImpl::CancelCapture(int captureId) + { + auto itr = camerCaptureMap_.find(captureId); //根据captureId 在Map中查找对应的CameraCapture对象 + RetCode rc = itr->second->Cancel(); //调用CameraCapture中Cancel方法结束数据捕获 + std::unique_lock lock(captureMutex_); + camerCaptureMap_.erase(itr); //擦除该CameraCapture对象 + return NO_ERROR; + } + +StreamOperatorImpl类中的ReleaseStreams接口的主要作用是释放之前通过CreateStream() CommitStreams()接口创建的流。并销毁Pipeline. + + CamRetCode StreamOperatorImpl::ReleaseStreams(const std::vector& streamIds) + { + RetCode rc = DestroyStreamPipeline(streamIds); //销毁该streamIds 的pipeline + rc = DestroyHostStreamMgr(streamIds); + rc = DestroyStreams(streamIds); //销毁该streamIds 的 Stream + return NO_ERROR; + } + + +#### 步骤10 关闭Camera 设备 +调用CameraDeviceImpl中的Close()来关闭Camera Device。 +该接口调用deviceManager中的PowerDown()来给设备下电。 + + + +### 开发实例 + +在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo, 开机后会在/system/bin下有叫ohos_camera_demo的可执行文件,该demo可完成cmaera的预览,拍照等基础功能。下面我们就以此demo为例讲述怎样用HDI接口去编写预览和拍照的用例。 + +1,Main函数中构造一个Hos3516Demo对象,该对象中有对camera初始化,启停流,释放等控制的方法。 +下面mainDemo->InitSensors();为初始化CameraHost,mainDemo->InitCameraDevice();为初始化CameraDevice. + + int main(int argc, char** argv) + { + RetCode rc = RC_OK; + + auto mainDemo = std::make_shared(); + rc = mainDemo->InitSensors(); //初始化CmaeraHost + if (rc == RC_ERROR) { + CAMERA_LOGE("main test: mainDemo->InitSensors() error\n"); + return -1; + } + rc = mainDemo->InitCameraDevice(); //初始化CameraDevice + if (rc == RC_ERROR) { + CAMERA_LOGE("main test: mainDemo->InitCameraDevice() error\n"); + return -1; + } + + rc = PreviewOn(0, mainDemo); //配流和启流 + if (rc != RC_OK) { + CAMERA_LOGE("main test: PreviewOn() error demo exit"); + return -1; + } + + ManuList(mainDemo, argc, argv); //打印菜单到控制台 + + return RC_OK; + } + +初始化CmaeraHost函数实现如下,这里调用了HDI接口ICameraHost::Get去获取demoCameraHost + + RetCode Hos3516Demo::InitSensors() + { + demoCameraHost_ = ICameraHost::Get(DEMO_SERVICE_NAME); + if (demoCameraHost_ == nullptr) { + CAMERA_LOGE("demo test: ICameraHost::Get error"); + return RC_ERROR; + } + + hostCallback_ = new CameraHostCallback(); + rc = demoCameraHost_->SetCallback(hostCallback_); + return RC_OK; + } + +初始化CameraDevice函数实现如下,这里调用了GetCameraIds(cameraIds_);,GetCameraAbility(cameraId, ability_);,OpenCamera(cameraIds_.front(), callback, demoCameraDevice_);等接口实现了demoCameraHost的获取。 + + RetCode Hos3516Demo::InitCameraDevice() + { + (void)demoCameraHost_->GetCameraIds(cameraIds_); + const std::string cameraId = cameraIds_.front(); + demoCameraHost_->GetCameraAbility(cameraId, ability_); + + sptr callback = new CameraDeviceCallback(); + rc = demoCameraHost_->OpenCamera(cameraIds_.front(), callback, demoCameraDevice_); + return RC_OK; + } + +2,PreviewOn()接口里包含了流的配置,启流和启Capture动作。该接口执行完成后,Camera预览通路已经开始运转。具体是启了两路流,preview和capture或者video,但只对preview流进行了capture动作。 + + static RetCode PreviewOn(int mode, const std::shared_ptr& mainDemo) + { + rc = mainDemo->StartPreviewStream(); //配置preview流 + if (mode == 0) { + rc = mainDemo->StartCaptureStream(); //配置capture流 + } else { + rc = mainDemo->StartVideoStream();//配置video流 + } + + rc = mainDemo->CaptureON(STREAM_ID_PREVIEW, CAPTURE_ID_PREVIEW, CAPTURE_PREVIEW); //将preview流capture + return RC_OK; + } + + +StartCaptureStream(), StartVideoStream(),StartPreviewStream()接口都会调用CreatStream()接口,只是传入参数不同。 + + RetCode Hos3516Demo::StartVideoStream() + { + RetCode rc = RC_OK; + if (isVideoOn_ == 0) { + isVideoOn_ = 1; + rc = CreatStream(STREAM_ID_VIDEO, streamCustomerVideo_, VIDEO); //如需启preview或者capture流更改该接口参数即可。 + } + return RC_OK; + } + +CreatStream()方法实际调用了HDI接口去配置和创建流,首先调用HDI接口去GetStreamOperator, 然后创建一个StreamInfo。调用CreateStreams()和CommitStreams(),实际创建流。 + + RetCode Hos3516Demo::CreatStreams(const int streamIdSecond, StreamIntent intent) + { + std::vector> streamInfos; + std::vector>().swap(streamInfos); + GetStreamOpt(); //获取StreamOperator对象 + std::shared_ptr previewStreamInfo = std::make_shared(); + SetStreamInfo(previewStreamInfo, streamCustomerPreview_, STREAM_ID_PREVIEW, PREVIEW); //填充StreamInfo + if (previewStreamInfo->bufferQueue_ == nullptr) { + CAMERA_LOGE("demo test: CreatStream CreateProducer(); is nullptr\n"); + return RC_ERROR; + } + streamInfos.push_back(previewStreamInfo); + + std::shared_ptr secondStreamInfo = std::make_shared(); + if (streamIdSecond == STREAM_ID_CAPTURE) { + SetStreamInfo(secondStreamInfo, streamCustomerCapture_, STREAM_ID_CAPTURE, intent); + } else { + SetStreamInfo(secondStreamInfo, streamCustomerVideo_, STREAM_ID_VIDEO, intent); + } + + if (secondStreamInfo->bufferQueue_ == nullptr) { + CAMERA_LOGE("demo test: CreatStreams CreateProducer() secondStreamInfo is nullptr\n"); + return RC_ERROR; + } + streamInfos.push_back(secondStreamInfo); + + rc = streamOperator_->CreateStreams(streamInfos); //创建流 + if (rc != Camera::NO_ERROR) { + CAMERA_LOGE("demo test: CreatStream CreateStreams error\n"); + return RC_ERROR; + } + + rc = streamOperator_->CommitStreams(Camera::NORMAL, ability_); //commit配置流 + if (rc != Camera::NO_ERROR) { + CAMERA_LOGE("demo test: CreatStream CommitStreams error\n"); + std::vector streamIds = {STREAM_ID_PREVIEW, streamIdSecond}; + streamOperator_->ReleaseStreams(streamIds); + return RC_ERROR; + } + return RC_OK; + } + +CaptureON接口会调用streamOperator的Capture方法实际去获取camera数据并轮转buffer. 拉起一个线程去接收相应类型的数据。 + + RetCode Hos3516Demo::CaptureON(const int streamId, const int captureId, CaptureMode mode) + { + std::shared_ptr captureInfo = std::make_shared(); //创建并填充CaptureInfo + captureInfo->streamIds_ = {streamId}; + captureInfo->captureSetting_ = ability_; + captureInfo->enableShutterCallback_ = false; + + int rc = streamOperator_->Capture(captureId, captureInfo, true); //实际capture开始,buffer轮转开始 + if (mode == CAPTURE_PREVIEW) { + streamCustomerPreview_->ReceiveFrameOn(nullptr); //创建预览线程接收递上来的buffer + } else if (mode == CAPTURE_SNAPSHOT) { + streamCustomerCapture_->ReceiveFrameOn([this](void* addr, const uint32_t size) { //创建capture线程通过StoreImage回调接收递上来的buffer + StoreImage(addr, size); + }); + } else if (mode == CAPTURE_VIDEO) { + OpenVideoFile(); + streamCustomerVideo_->ReceiveFrameOn([this](void* addr, const uint32_t size) {//创建Video线程通过StoreVideo回调接收递上来的buffer + StoreVideo(addr, size); + }); + } + return RC_OK; + } + +3,ManuList()函数会从控制台通过fgets()接口获取字符,不同的字符对应该demo所支持的功能。然后会打印出该demo所支持功能的菜单。 + + static void ManuList(const std::shared_ptr& mainDemo, + const int argc, char** argv) + { + int idx, c; + int awb = 1; + constexpr char shortOptions[] = "h:cwvaqof:"; + c = getopt_long(argc, argv, shortOptions, longOptions, &idx); + while(1) { + switch (c) { + case 'h': + c = PutMenuAndGetChr(); //打印菜单 + break; + + case 'f': + FlashLightTest(mainDemo); //手电筒功能测试 + c = PutMenuAndGetChr(); + break; + case 'o': + OfflineTest(mainDemo); //Offline功能测试 + c = PutMenuAndGetChr(); + break; + case 'c': + CaptureTest(mainDemo); //Capture功能测试 + c = PutMenuAndGetChr(); + break; + case 'w': //AWB功能测试 + if (awb) { + mainDemo->SetAwbMode(OHOS_CAMERA_AWB_MODE_INCANDESCENT); + } else { + mainDemo->SetAwbMode(OHOS_CAMERA_AWB_MODE_OFF); + } + awb = !awb; + c = PutMenuAndGetChr(); + break; + case 'a': //AE功能测试 + mainDemo->SetAeExpo(); + c = PutMenuAndGetChr(); + break; + case 'v': //Video功能测试 + VideoTest(mainDemo); + c = PutMenuAndGetChr(); + break; + case 'q': //退出demo + PreviewOff(mainDemo); + mainDemo->QuitDemo(); + exit(EXIT_SUCCESS); + + default: + CAMERA_LOGE("main test: command error please retry input command"); + c = PutMenuAndGetChr(); + break; + } + } + } + +PutMenuAndGetChr()接口打印了demo程序的菜单并调用fgets()等待从控制台输入命令,如下: + + static int PutMenuAndGetChr(void) + { + constexpr uint32_t inputCount = 50; + int c = 0; + char strs[inputCount]; + Usage(stdout); + CAMERA_LOGD("pls input command(input -q exit this app)\n"); + fgets(strs, inputCount, stdin); + + for (int i = 0; i < inputCount; i++) { + if (strs[i] != '-') { + c = strs[i]; + break; + } + } + return c; + } + +控制台输出菜单详情如下: + + "Options:\n" + "-h | --help Print this message\n" + "-o | --offline stream offline test\n" + "-c | --capture capture one picture\n" + "-w | --set WB Set white balance Cloudy\n" + "-v | --video capture Viedeo of 10s\n" + "-a | --Set AE Set Auto exposure\n" + "-f | --Set Flashlight Set flashlight ON 5s OFF\n" + "-q | --quit stop preview and quit this app\n"); + + +关于demo中其他功能会调用不同的HDI接口去实现,跟PreviewOn()接口类似,这里就不在一一讲述, +可参见[ohos_camera_demo](https://gitee.com/openharmony/drivers_peripheral/tree/master/camera/hal/init)。 + diff --git a/zh-cn/device-dev/driver/figure/logic-view-of-camera-hal-zh.png b/zh-cn/device-dev/driver/figure/logic-view-of-camera-hal-zh.png new file mode 100644 index 0000000000000000000000000000000000000000..4937a9c5bb78650249e2d4f63c57b8891552244c GIT binary patch literal 41343 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 cljL=sYcyV)hGUfp5b*D^ipIq+N`_DW2hL