From 3a26582c5d50e69c50c011e11d021847acff3f0c Mon Sep 17 00:00:00 2001 From: wu Date: Sun, 19 Dec 2021 18:19:39 -0800 Subject: [PATCH] docs: add docs/zh-cn/device-dev/driver/driver-peripherals-camera-des.md Signed-off-by: wu --- zh-cn/device-dev/driver/Readme-CN.md | 3 +- .../driver/driver-peripherals-camera-des.md | 618 ++++++++++++++++++ .../figures/logic-view-of-camera-hal-zh.png | Bin 0 -> 34752 bytes 3 files changed, 620 insertions(+), 1 deletion(-) create mode 100755 zh-cn/device-dev/driver/driver-peripherals-camera-des.md create mode 100755 zh-cn/device-dev/driver/figures/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 2b3b1d2cb6..631425b4fe 100755 --- a/zh-cn/device-dev/driver/Readme-CN.md +++ b/zh-cn/device-dev/driver/Readme-CN.md @@ -37,4 +37,5 @@ - [SENSOR](driver-peripherals-sensor-des.md) - [WLAN](driver-peripherals-external-des.md) - [AUDIO](driver-peripherals-audio-des.md) - - [USB](driver-peripherals-usb-des.md) \ No newline at end of file + - [USB](driver-peripherals-usb-des.md) + - [CAMERA](driver-peripherals-camera-des.md) \ No newline at end of file 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 100755 index 0000000000..e4f722b3c3 --- /dev/null +++ b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md @@ -0,0 +1,618 @@ +# Camera + +- [概述](#section11660541593) +- [开发指导](#section161941989596) + - [接口说明](#section1551164914237) + - [开发步骤](#section19806524151819) +- [开发实例](#section1564411661810) + + +## 概述 + +OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Driver Interface)接口,对下实现相机Pipeline模型,管理相机各个硬件设备。 +该驱动框架模型内部分为三层,依次为HDI实现层、框架层和适配层,各层基本概念如下: ++ **HDI实现层**:对上实现OHOS(OpenHarmony Operation System)相机标准南向接口。 + ++ **框架层**:对接HDI实现层的控制、流的转发,实现数据通路的搭建,管理相机各个硬件设备等功能。 + ++ **适配层**:屏蔽底层芯片和OS(Operation System)差异,支持多平台适配。 + +**** +**图 1** 基于HDF驱动框架的Camera驱动模型 +![](figures/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。Pipeline的IspNode依次从BufferQueue获取指定数量buffer,然后下发到底层ISP(Image Signal Processor,图像信号处理器)硬件,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模块对各个硬件下电;如果此时在Ipp的SubPipeline中存在OfflineStream,则需要保留OfflineStream,直到执行完毕。 + +9. 动态帧率控制。在StreamOperator中起一个CollectBuffer线程,CollectBuffer线程从每一路stream的BufferQueue中获取buffer,如果某一路流的帧率需要控制(为sensor出帧帧率的1/n),可以根据需求控制每一帧的buffer打包,并决定是否collect此路流的buffer(比如sensor出帧帧率为120fps,预览流的帧率为30fps,CollectBuffer线程collect预览流的buffer时,每隔4fps collect一次)。 + +## 开发指导 + +### HDI接口说明 +旨在了解HDI接口的作用及函数参数的传递规则,详情可见[Camera驱动子系统HDI使用说明](https://gitee.com/openharmony/drivers_peripheral/blob/master/camera/README_zh.md) + + +### 开发步骤 + +下面分步骤描述了Camera驱动框架的主要接口,包括注册、检测到如何创建、捕获、销毁流、打开和关闭设备等接口(为了更清晰的展示和描述主要功能的实现部分,该章节删除了部分判错和LOG源码)。 +#### 步骤1 注册CameraHost + +定义Camera的HdfDriverEntry结构体,该结构体中定义了CameraHost初始化的方法。 + + struct HdfDriverEntry g_cameraHostDriverEntry = { + .moduleVersion = 1, + .moduleName = "camera_service", + .Bind = HdfCameraHostDriverBind, + .Init = HdfCameraHostDriverInit, + .Release = HdfCameraHostDriverRelease, + }; + HDF_INIT(g_cameraHostDriverEntry); // 将Camera的HdfDriverEntry结构体注册到HDF上 + + +#### 步骤2 CameraHost初始化 +步骤1中提到的HdfCameraHostDriverBind接口提供了CameraServiceDispatch和CameraHostStubInstance的注册。这两个接口一个是远端调用CameraHost的方法,如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; // 提供远端CameraHost调用方法 + hdfCameraService->ioservice.Open = nullptr; + hdfCameraService->ioservice.Release = nullptr; + hdfCameraService->instance = CameraHostStubInstance(); // 初始化Camera设备 + 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 +调用Get()接口从远端CameraService中获取CameraHost对象。get()方法如下: + + 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 获取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()是配置流的接口,必须在创建流之后调用,其主要作用是初始化Pipeline和创建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()接口前需要先填充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 取消捕获和释放离线流 +StreamOperatorImpl类中的CancelCapture()接口的主要作用是根据captureId取消数据流的捕获。 + + 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()来关闭CameraDevice,该接口调用deviceManager中的PowerDown()来给设备下电。 + + +## 开发实例 + +在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo,开机后会在/system/bin下生成可执行文件ohos_camera_demo,该demo可以完成camera的预览,拍照等基础功能。下面我们就以此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; + } + +初始化CameraHost函数实现如下,这里调用了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接口去获取StreamOperation对象,然后创建一个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/figures/logic-view-of-camera-hal-zh.png b/zh-cn/device-dev/driver/figures/logic-view-of-camera-hal-zh.png new file mode 100755 index 0000000000000000000000000000000000000000..772356a3efede5b1b8b45b27270ed79fcf9e9d01 GIT binary patch literal 34752 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