From 21a3177d5c6a445d07401778c6367f2bc422adf4 Mon Sep 17 00:00:00 2001 From: wusongqing Date: Sun, 30 Jan 2022 10:45:13 +0800 Subject: [PATCH] updated docs Signed-off-by: wusongqing --- .../driver/driver-peripherals-camera-des.md | 259 +++++++++--------- .../camera-driver-model-architecture.png | Bin 0 -> 34752 bytes 2 files changed, 127 insertions(+), 132 deletions(-) create mode 100644 en/device-dev/driver/figures/camera-driver-model-architecture.png diff --git a/en/device-dev/driver/driver-peripherals-camera-des.md b/en/device-dev/driver/driver-peripherals-camera-des.md index 24cf34f94b..148726f226 100644 --- a/en/device-dev/driver/driver-peripherals-camera-des.md +++ b/en/device-dev/driver/driver-peripherals-camera-des.md @@ -1,60 +1,57 @@ -# Camera +# Camera -- [Introduction](#section353860995163604) -- [Development Guidelines](#section1291233039163604) - - [Available APIs](#section1192025723163604) - - [How to Develop](#section1407626053163604) +- [Camera](#camera) + - [概述](#概述) + - [开发指导](#开发指导) + - [HDI接口说明](#hdi接口说明) + - [开发步骤](#开发步骤) + - [开发实例](#开发实例) -- [Development Example](#section605457993163604) -## Introduction +## 概述 -The OpenHarmony camera driver model implements the camera hardware driver interface \(HDI\) and the camera pipeline model to manage camera devices. +OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Driver Interface)接口,对下实现相机Pipeline模型,管理相机各个硬件设备。 +该驱动框架模型内部分为三层,依次为HDI实现层、框架层和适配层,各层基本概念如下: ++ **HDI实现层**:对上实现OHOS(OpenHarmony Operation System)相机标准南向接口。 + ++ **框架层**:对接HDI实现层的控制、流的转发,实现数据通路的搭建,管理相机各个硬件设备等功能。 -The camera driver model consists of the following layers: ++ **适配层**:屏蔽底层芯片和OS(Operation System)差异,支持多平台适配。 -- HDI implementation layer: implements standard southbound interfaces of OpenHarmony cameras. +**** +**图 1** 基于HDF驱动框架的Camera驱动模型 +![](figures/logic-view-of-camera-hal-zh.png) -- Framework layer: connects to the HDI implementation layer for control instruction and stream transfer, establishes data channels, and manages camera devices. +1. 系统启动时创建CameraDeviceHost进程。进程创建后,首先枚举底层设备,创建(也可以通过配置表创建)管理设备树的DeviceManager类及其内部各个底层设备的对象,创建对应的CameraHost类实例并且将其注册到UHDF服务中,方便上层通过UHDF服务获取底层CameraDeviceHost的服务,从而操作底层设备。 -- Adaptation layer: shields the differences between bottom-layer chips and OSs for multi-platform adaptation. +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等待下一次下发。 -**Figure 1** Architecture of the camera driver model -![](figures/architecture-of-the-camera-driver-model.png "architecture-of-the-camera-driver-model") +5. Service通过Capture接口下发拍照命令。ChangeToOfflineStream接口查询拍照buffer位置,如果ISP已经出图,并且图像数据已经送到IPP node,可以将普通拍照流转换为离线流,否则直接走关闭流程。ChangeToOfflineStream接口通过传递StreamInfo使离线流获取到普通流的流信息,并且通过配置表确认离线流的具体Node连接方式,创建离线流的Node连接(如果已创建则通过CloseCamera释放非离线流所需的Node),等待buffer从底层Pipeline回传到上层再释放持有的Pipeline相关资源。 -1. The CameraDeviceHost process is created during system startup. The process enumerates underlying devices, creates a **DeviceManager** instance that manages the device tree, an object for each underlying device, and a **CameraHost** instance, and registers the **CameraHost** instance with the UHDF service. Through this service, the upper layer can obtain the CameraDeviceHost process at the bottom layer to operate the underlying devices. Note that the **DeviceManager** instance can also be created by using the configuration table. +6. Service通过CameraDevice的UpdateSettings接口向下发送CaptureSetting参数,CameraDeviceDriverModel通过StreamPipelineDispatcher模块向各个Node转发,StartStreamingCapture和Capture接口携带的CaptureSetting通过StreamPipelineDispatcher模块向该流所属的Node转发。 -2. The Camera Service obtains the **CameraHost** instance through the CameraDeviceHost service. The **CameraHost** instance can be used to obtain the bottom-layer camera capabilities, turn on the flashlight, call the **Open\(\)** interface to start the camera and create a connection, create a **DeviceManager** instance \(powering on the bottom-layer hardware modules\), and create a **CameraDevice** instance \(providing the device control interface for the upper layer\). When the **CameraDevice** instance is created, each submodule of PipelineCore is instantiated. Among the submodules, StreamPiplineCore is responsible for creating pipelines, and MetaQueueManager is responsible for reporting metadata. +7. Service通过EnableResult和DisableResult接口控制底层meta的上报。如果需要底层meta上报,pipeline会创建CameraDeviceDriverModel内部的一个Bufferqueue用来收集和传递meta,根据StreamPipelineStrategy模块查询配置表并通过StreamPipelineBuilder创建和连接Node,MetaQueueManager下发buffer至底层,底层相关Node填充数据,MetaQueueManager模块再调用上层回调传递给上层。 -3. The Camera Service configures the stream and creates a **Stream** instance through the **CameraDevice** instance at the bottom layer. The StreamPipelineStrategy module creates the node connection mode of the corresponding stream by using the mode issued by the upper layer and querying the configuration table. The StreamPipelineBuilder module creates a node and returns the pipeline to the StreamPipelineDispatcher module through the connection. The StreamPipelineDispatcher module provides unified pipeline invoking management. +8. Service调用CameraDevice的Close接口,CameraDevice调用对应的DeviceManager模块对各个硬件下电;如果此时在Ipp的SubPipeline中存在OfflineStream,则需要保留OfflineStream,直到执行完毕。 -4. The Camera Service controls the stream operations through the **Stream** instance. The **AttachBufferQueue\(\)** interface is used to deliver the buffer queue requested from the display module to the bottom layer. The CameraDeviceDriverModel manages the buffer. After the **Capture\(\)** interface is called to deliver commands, the bottom layer transfers the buffer to the upper layer. The Image Signal Processor \(ISP\) node obtains a specified number of buffers from the buffer queue and delivers the buffers to the bottom-layer ISP hardware. After filling the buffers, the ISP hardware transfers the buffers to the CameraDeviceDriverModel. The CameraDeviceDriverModel fills the created pipeline with the received buffers by using a loop thread. Each node processes the pipeline data and transfers the data to the upper layer by using a callback. At the same time, the buffers are freed for reuse. +9. 动态帧率控制。在StreamOperator中起一个CollectBuffer线程,CollectBuffer线程从每一路stream的BufferQueue中获取buffer,如果某一路流的帧率需要控制(为sensor出帧帧率的1/n),可以根据需求控制每一帧的buffer打包,并决定是否collect此路流的buffer(比如sensor出帧帧率为120fps,预览流的帧率为30fps,CollectBuffer线程collect预览流的buffer时,每隔4fps collect一次)。 -5. The Camera Service delivers the photographing command through the **Capture\(\)** interface. The **ChangeToOfflineStream\(\)** interface is used to query the position of the photographing buffer. If the ISP hardware has output an image and sent the image data to the IPP node, the common photographing streams can be converted into offline streams. Otherwise, the close process is executed. The **ChangeToOfflineStream\(\)** interface transfers **StreamInfo** to enable the offline stream to obtain the stream information of the common stream, confirms the node connection mode of the offline stream based on the configuration table, and creates the node connection of the offline stream. If the node connection has been created, the interface releases the node required by the non-offline stream through **CloseCamera\(\)**. It then waits for the buffer to return from the bottom-layer pipeline to the upper layer and then releases the pipeline resources. +## 开发指导 -6. The Camera Service sends the **CaptureSetting** parameter to the CameraDeviceDriverModel through the **UpdateSettings\(\)** interface of the **CameraDevice** instance. The CameraDeviceDriverModel forwards the parameter to each node through the StreamPipelineDispatcher module. The **CaptureSetting** parameter carried in the **StartStreamingCapture\(\)** and **Capture\(\)** interfaces is forwarded to the node to which the stream belongs through the StreamPipelineDispatcher module. +### HDI接口说明 +旨在了解HDI接口的作用及函数参数的传递规则,详情可见[Camera驱动子系统HDI使用说明](https://gitee.com/openharmony/drivers_peripheral/blob/master/camera/README_zh.md)。 -7. The Camera Service controls underlying metadata reporting through the **EnableResult\(\)** and **DisableResult\(\)** interfaces. If the bottom-layer metadata needs to be reported, the pipeline creates a buffer queue in the CameraDeviceDriverModel to collect and transfer metadata, queries the configuration table based on the StreamPipelineStrategy module, and creates and connects to the specified node through the StreamPipelineBuilder module. The MetaQueueManager module delivers the buffer to the bottom layer, and the bottom-layer node fills in data. The MetaQueueManager module then invokes the upper-layer callback to transfer the data to the upper layer. -8. The Camera Service calls the **Close\(\)** interface of the **CameraDevice** class, and the **CameraDevice** instance calls the corresponding DeviceManager module to power off each hardware. If an offline stream exists in the subpipeline of the IPP node, the offline stream must be reserved until the execution is complete. +### 开发步骤 -9. To implement dynamic frame control, a CollectBuffer thread is started in the StreamOperator. The CollectBuffer thread obtains a buffer from the buffer queue of each stream. If the frame rate of a stream needs to be controlled \(1/n of the sensor output frame rate\), the CollectBuffer thread can control the buffer packaging of each frame as required, and determine whether to collect the buffer of the stream. For example, if the output frame rate of the sensor is 120 fps and the preview stream frame rate is 30 fps, the CollectBuffer thread collects the buffer of the preview stream every 4 fps. +下面分步骤描述了Camera驱动框架的主要接口,包括注册、检测;创建、捕获和销毁流;打开和关闭设备等接口(为了更清晰的展示和描述主要功能的实现部分,该章节删除了部分判错和LOG源码)。 +1. 注册CameraHost - -## Development Guidelines - -### Available APIs - -For details about the HDI functionalities and the function passing rules, see "Available APIs" in [Camera](https://gitee.com/openharmony/drivers_peripheral/blob/master/camera/README_zh.md). - -### How to Develop - -The following describes the main APIs of the camera driver model, including the APIs for registering and detecting cameras, creating, capturing, and destroying streams, and enabling and disabling devices. \(To clearly describe the implementation of main functionalities, some error judgment and log source code are not described here.\) - -1. Register a **CameraHost**. - - Define the **HdfDriverEntry** structure to define the method for initializing a **CameraHost**. + 定义Camera的HdfDriverEntry结构体,该结构体中定义了CameraHost初始化的方法。 ``` struct HdfDriverEntry g_cameraHostDriverEntry = { @@ -64,12 +61,12 @@ The following describes the main APIs of the camera driver model, including the .Init = HdfCameraHostDriverInit, .Release = HdfCameraHostDriverRelease, }; - HDF_INIT(g_cameraHostDriverEntry); // Register the HdfDriverEntry structure with the HDF. + HDF_INIT(g_cameraHostDriverEntry); // 将Camera的HdfDriverEntry结构体注册到HDF上 ``` -2. Initialize the **CameraHost**. +2. CameraHost初始化 - **HdfCameraHostDriverBind** defined in the **HdfDriverEntry** structure provides the registration of **CameraServiceDispatch\(\)** and **CameraHostStubInstance\(\)**. **CameraServiceDispatch\(\)** is used to remotely call a method of the **CameraHost**, such as **OpenCamera\(\)** and **SetFlashlight\(\)**. **CameraHostStubInstance\(\)** is used to initialize the camera device, which is called during system startup. + 步骤1中提到的HdfCameraHostDriverBind接口提供了CameraServiceDispatch和CameraHostStubInstance的注册。这两个接口一个是远端调用CameraHost的方法,如OpenCamera(),SetFlashlight()等,另外一个是Camera设备的初始化,在开机时被调用。 ``` int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject) @@ -84,16 +81,16 @@ The following describes the main APIs of the camera driver model, including the HDF_LOGE("HdfCameraHostDriverBind OsalMemAlloc HdfCameraService failed!"); return HDF_FAILURE; } - hdfCameraService->ioservice.Dispatch = CameraServiceDispatch; // Used to call methods of the CameraHost. + hdfCameraService->ioservice.Dispatch = CameraServiceDispatch; // 提供远端CameraHost调用方法 hdfCameraService->ioservice.Open = nullptr; hdfCameraService->ioservice.Release = nullptr; - hdfCameraService->instance = CameraHostStubInstance(); // Initialize the camera device. + hdfCameraService->instance = CameraHostStubInstance(); // 初始化Camera设备 deviceObject->service = &hdfCameraService->ioservice; return HDF_SUCCESS; } ``` - The following functions are the implementation of the methods of the **CameraHost**: + 下面的函数是远端CameraHost调用的方法: ``` int32_t CameraHostStub::CameraHostServiceStubOnRemoteRequest(int cmdId, MessageParcel &data, @@ -124,11 +121,11 @@ The following describes the main APIs of the camera driver model, including the } ``` - **CameraHostStubInstance\(\)** finally calls **CameraHostImpl::Init\(\)** to obtain the physical camera and initialize the DeviceManager and PipelineCore modules. + CameraHostStubInstance()接口最终调用CameraHostImpl::Init()方法,该方法会获取物理Camera,并对DeviceManager和PipelineCore进行初始化。 -3. Obtain the **CamerHost**. +3. 获取CamerHost - Call the **Get\(\)** interface to obtain the **CameraHost** from the **CameraService**. The **Get\(\)** interface is as follows: + 调用Get()接口从远端CameraService中获取CameraHost对象。get()方法如下: ``` sptr ICameraHost::Get(const char *serviceName) @@ -140,9 +137,9 @@ The following describes the main APIs of the camera driver model, including the HDF_LOGE("%s: IServiceManager failed!", __func__); break; } - auto remote = servMgr->GetService(serviceName); // Obtain the CameraHost based on serviceName. + auto remote = servMgr->GetService(serviceName); // 根据serviceName名称获取CameraHost if (remote != nullptr) { - sptr hostSptr = iface_cast(remote); // Return the CameraHostProxy object that contains methods such as OpenCamera() to the caller. + sptr hostSptr = iface_cast(remote); // 将CameraHostProxy对象返回给调用者,该对象中包含OpenCamera()等方法。 return hostSptr; } HDF_LOGE("%s: GetService failed! serviceName = %s", __func__, serviceName); @@ -152,11 +149,10 @@ The following describes the main APIs of the camera driver model, including the } ``` -4. Implement the **OpenCamera\(\)** interface. - - The **CameraHostProxy** class provides five interfaces: **SetCallback\(\)**, **GetCameraIds\(\)**, **GetCameraAbility\(\)**, **OpenCamera\(\)**, and **SetFlashlight\(\)**. The following describes **OpenCamera\(\)**. +4. OpenCamera()接口 - The **OpenCamera\(\)** interface calls the remote **CameraHostStubOpenCamera\(\)** interface through the CMD\_CAMERA\_HOST\_OPEN\_CAMERA to obtain an **ICameraDevice** object. + CameraHostProxy对象中有五个方法,分别是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) @@ -179,7 +175,7 @@ The following describes the main APIs of the camera driver model, including the } ``` - **Remote\(\)-\>SendRequest** calls **CameraHostServiceStubOnRemoteRequest\(\)**, enters the **CameraHostStubOpenCamera\(\)** interface based on **cmdId**, and finally calls **CameraHostImpl::OpenCamera\(\)** to obtain a **CameraDevice** and power on the camera hardware. + Remote()->SendRequest调用上文提到的CameraHostServiceStubOnRemoteRequest(),根据cmdId进入CameraHostStubOpenCamera()接口,最终调用CameraHostImpl::OpenCamera(),该接口获取了CameraDevice并对硬件进行上电等操作。 ``` CamRetCode CameraHostImpl::OpenCamera(const std::string &cameraId, const OHOS::sptr &callback, OHOS::sptr &device) @@ -204,7 +200,7 @@ The following describes the main APIs of the camera driver model, including the CAMERA_LOGE("get physic cameraId failed."); return DEVICE_ERROR; } - if (CameraPowerUp(cameraId, phyCameraIds) != RC_OK) { // Power on the camera hardware. + if (CameraPowerUp(cameraId, phyCameraIds) != RC_OK) { // 对Camera硬件上电 CAMERA_LOGE("camera powerup failed."); CameraPowerDown(phyCameraIds); return DEVICE_ERROR; @@ -214,15 +210,15 @@ The following describes the main APIs of the camera driver model, including the if (sptrDevice == deviceBackup_.end()) { deviceBackup_[cameraId] = cameraDevice.get(); } - device = deviceBackup_[cameraId]; + device = deviceBackup_[cameraId]; cameraDevice->SetStatus(true); return NO_ERROR; } ``` -5. Implement the **GetStreamOperator\(\)** interface. +5. GetStreamOperator()接口 - **CameraDeviceImpl** defines interfaces such as **GetStreamOperator\(\)**, **UpdateSettings\(\)**, **SetResultMode\(\)**, and **GetEnabledResult\(\)**. The following is an example of implementing the **GetStreamOperator\(\)** interface: + CameraDeviceImpl定义了GetStreamOperator、UpdateSettings、SetResultMode和GetEnabledResult等方法,获取流操作方法如下: ``` CamRetCode CameraDeviceImpl::GetStreamOperator(const OHOS::sptr &callback, @@ -234,7 +230,7 @@ The following describes the main APIs of the camera driver model, including the } spCameraDeciceCallback_ = callback; if (spStreamOperator_ == nullptr) { - // Here, an spStreamOperator object is created and passed to the caller for stream operations. + // 这里new了一个spStreamOperator对象传递给调用者,以便对stream进行各种操作。 spStreamOperator_ = new(std::nothrow) StreamOperatorImpl(spCameraDeciceCallback_, shared_from_this()); if (spStreamOperator_ == nullptr) { CAMERA_LOGW("create stream operator failed."); @@ -250,26 +246,26 @@ The following describes the main APIs of the camera driver model, including the } ``` -6. Create a stream. +6. 创建流 - Fill in the **StreamInfo** structure before creating a stream by calling **CreateStreams\(\)**. + 调用CreateStreams创建流前需要填充StreamInfo结构体,具体内容如下: ``` using StreamInfo = struct _StreamInfo { int streamId_; - int width_; // Stream width - int height_; // Stream height - int format_; // Stream format, for example, PIXEL_FMT_YCRCB_420_SP + int width_; // 数据流宽 + int height_; // 数据流高 + int format_; // 数据流格式,如PIXEL_FMT_YCRCB_420_SP int datasapce_; - StreamIntent intent_; // StreamIntent, for example, PREVIEW + StreamIntent intent_; // StreamIntent 如PREVIEW bool tunneledMode_; - OHOS::sptr bufferQueue_; // The stream buffer queue can be created by using the streamCustomer->CreateProducer() interface. + OHOS::sptr bufferQueue_; // 数据流bufferQueue可用streamCustomer->CreateProducer()接口创建 int minFrameDuration_; EncodeType encodeType_; }; ``` - The **CreateStreams\(\)** interface in the **StreamOperatorImpl** class is used to create a **StreamBase** instance, which can then be used to initialize operations such as **CreateBufferPool\(\)** by using the **init\(\)** method. + CreateStreams()接口是StreamOperatorImpl类中的方法,该接口的主要作用是创建一个StreamBase对象,通过StreamBase的Init方法初始化CreateBufferPool等操作。 ``` RetCode StreamOperatorImpl::CreateStream(const std::shared_ptr& streamInfo) @@ -287,15 +283,15 @@ The following describes the main APIs of the camera driver model, including the CAMERA_LOGE("do not support stream type. [type = %{public}d]", streamInfo->intent_); return RC_ERROR; } - std::shared_ptr stream = StreamFactory::Instance().CreateShared(itr->second); // Create a StreamBase instance. + std::shared_ptr stream = StreamFactory::Instance().CreateShared(itr->second); // 创建StreamBase实例 RetCode rc = stream->Init(streamInfo); return RC_OK; } ``` -7. Configure the stream. +7. 配置流 - Use the **CommitStreams\(\)** method to configure the stream, including PipelineCore initialization and creation. It must be called after the stream is created. + CommitStreams()是配置流的接口,必须在创建流之后调用,其主要作用是初始化Pipeline和创建Pipeline。 ``` CamRetCode StreamOperatorImpl::CommitStreams(OperationMode mode, const std::shared_ptr& modeSetting) @@ -308,43 +304,43 @@ The following describes the main APIs of the camera driver model, including the std::shared_ptr PipelineCore = std::static_pointer_cast(cameraDevice)->GetPipelineCore(); if (PipelineCore == nullptr) { - CAMERA_LOGE("Failed to obtain PipelineCore."); + CAMERA_LOGE("get pipeline core failed."); return CAMERA_CLOSED; } streamPipeCore_ = PipelineCore->GetStreamPipelineCore(); if (streamPipeCore_ == nullptr) { - CAMERA_LOGE("Failed to obtain the stream PipelineCore."); + CAMERA_LOGE("get stream pipeline core failed."); return DEVICE_ERROR; } - RetCode rc = streamPipeCore_->Init(); // Initialize the PipelineCore. + RetCode rc = streamPipeCore_->Init(); // 对pipelinecore的初始化 if (rc != RC_OK) { - CAMERA_LOGE("Failed to initialize the stream PipelineCore."); + CAMERA_LOGE("stream pipeline core init failed."); return DEVICE_ERROR; } - rc = streamPipeCore_->CreatePipeline(mode); // Create a pipeline. + rc = streamPipeCore_->CreatePipeline(mode); // 创建一个pipeline if (rc != RC_OK) { - CAMERA_LOGE("Failed to create pipeline."); + CAMERA_LOGE("create pipeline failed."); return INVALID_ARGUMENT; } return NO_ERROR; } ``` -8. Capture images. +8. 捕获图像 - Fill in the **CaptureInfo** structure before calling the **Capture\(\)** method. + 在调用Capture()接口前需要先填充CaptureInfo结构体,具体内容如下: ``` using CaptureInfo = struct _CaptureInfo { - std::vector streamIds_; // IDs of streams to be captured - std::shared_ptr captureSetting_; // Camera ability can be obtained through the GetCameraAbility() interface of CameraHost. + std::vector streamIds_; //需要Capture的streamIds + std::shared_ptr captureSetting_; // 这里填充camera ability 可通过CameraHost 的GetCameraAbility()接口获取 bool enableShutterCallback_; }; ``` - Use the **Capture\(\)** interface in **StreamOperatorImpl** to call the **CreateCapture\(\)** interface to capture streams. + StreamOperatorImpl中的Capture方法主要调用CreateCapture()接口去捕获数据流: ``` CamRetCode StreamOperatorImpl::Capture(int captureId, const std::shared_ptr& captureInfo, bool isStreaming) @@ -374,74 +370,74 @@ The following describes the main APIs of the camera driver model, including the } ``` -9. Cancel the capture and release the offline stream. +9. 取消捕获和释放离线流 - Use the **CancelCapture\(\)** interface in the **StreamOperatorImpl** class to cancel the stream capture based on **captureId**. + StreamOperatorImpl类中的CancelCapture()接口的主要作用是根据captureId取消数据流的捕获。 ``` CamRetCode StreamOperatorImpl::CancelCapture(int captureId) { - auto itr = camerCaptureMap_.find(captureId); // Search for the CameraCapture object in the Map based on the captureId. - RetCode rc = itr->second->Cancel(); // Call the Cancel() interface in CameraCapture to cancel the stream capture. + auto itr = camerCaptureMap_.find(captureId); //根据captureId 在Map中查找对应的CameraCapture对象 + RetCode rc = itr->second->Cancel(); //调用CameraCapture中Cancel方法结束数据捕获 std::unique_lock lock(captureMutex_); - camerCaptureMap_.erase(itr); // Erase the CameraCapture object. + camerCaptureMap_.erase(itr); //擦除该CameraCapture对象 return NO_ERROR; } ``` - Use the **ReleaseStreams\(\)** interface in the **StreamOperatorImpl** class t release the streams created by using **CreateStream\(\)** and **CommitStreams\(\)** and destroy the pipeline. + StreamOperatorImpl类中的ReleaseStreams接口的主要作用是释放之前通过CreateStream()和CommitStreams()接口创建的流,并销毁Pipeline。 ``` CamRetCode StreamOperatorImpl::ReleaseStreams(const std::vector& streamIds) { - RetCode rc = DestroyStreamPipeline(streamIds); // Destroy the pipeline based on streamIds. + RetCode rc = DestroyStreamPipeline(streamIds); //销毁该streamIds 的pipeline rc = DestroyHostStreamMgr(streamIds); - rc = DestroyStreams(streamIds); // Destroy the stream specified by streamIds. + rc = DestroyStreams(streamIds); //销毁该streamIds 的 Stream return NO_ERROR; } ``` -10. Close the camera device. +10. 关闭Camera设备 - Use the **Close\(\)** interface in the **CameraDeviceImpl** class to close the camera device. This interface calls **PowerDown\(\)** in the **DeviceManager** to power off the device. + 调用CameraDeviceImpl中的Close()来关闭CameraDevice,该接口调用deviceManager中的PowerDown()来给设备下电。 -## Development Example +## 开发实例 -There is a camera demo in the **/drivers/peripheral/camera/hal/init** directory. After system startup, the executable file **ohos\_camera\_demo** is generated in the **/system/bin** directory. This demo can implement basic camera capabilities such as preview and photographing. The following uses the demo as an example to describe how to use the HDI to implement the **PreviewOn\(\)** and **CaptureON\(\)** interfaces. +在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo,开机后会在/system/bin下生成可执行文件ohos_camera_demo,该demo可以完成camera的预览,拍照等基础功能。下面我们就以此demo为例讲述怎样用HDI接口去编写预览PreviewOn()和拍照CaptureON()的用例。 -1. Construct a Hos3516Demo object in the **main** function. This object contains methods for initializing the camera and starting, stopping, and releasing streams. The **mainDemo-\>InitSensors\(\)** function is used to initialize the **CameraHost**, and the **mainDemo-\>InitCameraDevice\(\)** function is used to initialize the **CameraDevice**. +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(); // Initialize the CameraHost. + rc = mainDemo->InitSensors(); // 初始化CameraHost if (rc == RC_ERROR) { CAMERA_LOGE("main test: mainDemo->InitSensors() error\n"); return -1; } - - rc = mainDemo->InitCameraDevice(); // Initialize the CameraDevice. + + rc = mainDemo->InitCameraDevice(); // 初始化CameraDevice if (rc == RC_ERROR) { CAMERA_LOGE("main test: mainDemo->InitCameraDevice() error\n"); return -1; } - rc = PreviewOn(0, mainDemo); // Configure and enable streams. + rc = PreviewOn(0, mainDemo); // 配流和启流 if (rc != RC_OK) { CAMERA_LOGE("main test: PreviewOn() error demo exit"); return -1; } - ManuList(mainDemo, argc, argv); // Print the menu to the console. + ManuList(mainDemo, argc, argv); // 打印菜单到控制台 return RC_OK; } ``` - The function used to initialize the **CameraHost** is implemented as follows, where the HDI **ICameraHost::Get\(\)** is called to obtain the **demoCameraHost** and set the callback: + 初始化CameraHost函数实现如下,这里调用了HDI接口ICameraHost::Get()去获取demoCameraHost,并对其设置回调函数。 ``` RetCode Hos3516Demo::InitSensors() @@ -458,7 +454,7 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` - The implementation of the function for initializing the **CameraDevice** is as follows, where the **GetCameraIds\(cameraIds\_\)**, **GetCameraAbility\(cameraId, ability\_\)**, and **OpenCamera\(cameraIds\_.front\(\), callback, demoCameraDevice\_\)** interfaces are called to obtain the **demoCameraHost**. + 初始化CameraDevice函数实现如下,这里调用了GetCameraIds(cameraIds_),GetCameraAbility(cameraId, ability_),OpenCamera(cameraIds_.front(), callback, demoCameraDevice_)等接口实现了demoCameraHost的获取。 ``` RetCode Hos3516Demo::InitCameraDevice() @@ -473,24 +469,24 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` -2. Implement the **PreviewOn\(\)** interface to configure streams, enable preview streams, and start stream capture. After this interface is called, the camera preview channel starts running. Two streams are enabled: preview stream and capture or video stream. Only the preview stream will be captured. +2. PreviewOn()接口包含配置流、开启预览流和启动Capture动作。该接口执行完成后Camera预览通路已经开始运转并开启了两路流,一路流是preview,另外一路流是capture或者video,两路流中仅对preview流进行capture动作。 ``` static RetCode PreviewOn(int mode, const std::shared_ptr& mainDemo) { - rc = mainDemo->StartPreviewStream(); // Configure the preview stream. + rc = mainDemo->StartPreviewStream(); // 配置preview流 if (mode == 0) { - rc = mainDemo->StartCaptureStream(); // Configure the capture stream. + rc = mainDemo->StartCaptureStream(); // 配置capture流 } else { - rc = mainDemo->StartVideoStream(); // Configure the video stream. + rc = mainDemo->StartVideoStream(); // 配置video流 } - rc = mainDemo->CaptureON(STREAM_ID_PREVIEW, CAPTURE_ID_PREVIEW, CAPTURE_PREVIEW); // Capture the preview stream. + rc = mainDemo->CaptureON(STREAM_ID_PREVIEW, CAPTURE_ID_PREVIEW, CAPTURE_PREVIEW); // 将preview流capture return RC_OK; } ``` - The **StartCaptureStream\(\)**, **StartVideoStream\(\)**, and **StartPreviewStream\(\)** interfaces call the **CreateStream\(\)** interface with different input parameters. + StartCaptureStream()、StartVideoStream()和StartPreviewStream()接口都会调用CreateStream()接口,只是传入的参数不同。 ``` RetCode Hos3516Demo::StartVideoStream() @@ -498,22 +494,22 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director RetCode rc = RC_OK; if (isVideoOn_ == 0) { isVideoOn_ = 1; - rc = CreateStream(STREAM_ID_VIDEO, streamCustomerVideo_, VIDEO); // To enable the preview stream or capture stream, change the input parameters. + rc = CreateStream(STREAM_ID_VIDEO, streamCustomerVideo_, VIDEO); // 如需启preview或者capture流更改该接口参数即可。 } return RC_OK; } ``` - The **CreateStream\(\)** interface calls the HDI to configure and create a stream. Specifically, the interface first calls the HDI to obtain a **StreamOperation** object and then creates a **StreamInfo** object. Call **CreateStreams\(\)** and **CommitStreams\(\)** to create and configure a stream. + CreateStream()方法调用HDI接口去配置和创建流,首先调用HDI接口去获取StreamOperation对象,然后创建一个StreamInfo。调用CreateStreams()和CommitStreams()实际创建流并配置流。 ``` RetCode Hos3516Demo::CreateStreams(const int streamIdSecond, StreamIntent intent) { std::vector> streamInfos; std::vector>().swap(streamInfos); - GetStreamOpt(); // Obtain a StreamOperator object. + GetStreamOpt(); // 获取StreamOperator对象 std::shared_ptr previewStreamInfo = std::make_shared(); - SetStreamInfo(previewStreamInfo, streamCustomerPreview_, STREAM_ID_PREVIEW, PREVIEW); // Fill in the StreamInfo. + SetStreamInfo(previewStreamInfo, streamCustomerPreview_, STREAM_ID_PREVIEW, PREVIEW); // 填充StreamInfo if (previewStreamInfo->bufferQueue_ == nullptr) { CAMERA_LOGE("demo test: CreateStream CreateProducer(); is nullptr\n"); return RC_ERROR; @@ -533,13 +529,13 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } streamInfos.push_back(secondStreamInfo); - rc = streamOperator_->CreateStreams(streamInfos); // Create a stream. + rc = streamOperator_->CreateStreams(streamInfos); // 创建流 if (rc != Camera::NO_ERROR) { CAMERA_LOGE("demo test: CreateStream CreateStreams error\n"); return RC_ERROR; } - rc = streamOperator_->CommitStreams(Camera::NORMAL, ability_); // Commit the stream. + rc = streamOperator_->CommitStreams(Camera::NORMAL, ability_); if (rc != Camera::NO_ERROR) { CAMERA_LOGE("demo test: CreateStream CommitStreams error\n"); std::vector streamIds = {STREAM_ID_PREVIEW, streamIdSecond}; @@ -550,26 +546,26 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` - The **CaptureON\(\)** interface calls the **Capture\(\)** method of **StreamOperator** to obtain camera data, rotate the buffer, and start a thread to receive data of the corresponding type. + CaptureON()接口调用streamOperator的Capture()方法获取camera数据并轮转buffer,拉起一个线程接收相应类型的数据。 ``` RetCode Hos3516Demo::CaptureON(const int streamId, const int captureId, CaptureMode mode) { - The std::shared_ptr captureInfo = std::make_shared(); // Create and fill in CaptureInfo. + std::shared_ptr captureInfo = std::make_shared(); // 创建并填充CaptureInfo captureInfo->streamIds_ = {streamId}; captureInfo->captureSetting_ = ability_; captureInfo->enableShutterCallback_ = false; - The int rc = streamOperator_->Capture(captureId, captureInfo, true); // The stream capture starts, and buffer recycling starts. + int rc = streamOperator_->Capture(captureId, captureInfo, true); // 实际capture开始,buffer轮转开始 if (mode == CAPTURE_PREVIEW) { - streamCustomerPreview_->ReceiveFrameOn(nullptr); // Create a preview thread to receive the passed buffers. + streamCustomerPreview_->ReceiveFrameOn(nullptr); // 创建预览线程接收递上来的buffer } else if (mode == CAPTURE_SNAPSHOT) { - The streamCustomerCapture_->ReceiveFrameOn([this](void* addr, const uint32_t size) { // Create a capture thread to receive the passed buffers through the StoreImage callback. + 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) {// Create a video thread to receive the passed buffer by calling the StoreVideo callback. + streamCustomerVideo_->ReceiveFrameOn([this](void* addr, const uint32_t size) {// 创建Video线程通过StoreVideo回调接收递上来的buffer StoreVideo(addr, size); }); } @@ -577,7 +573,7 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` -3. Implement the **ManuList\(\)** function to obtain characters from the console through the **fgets\(\)** interface. Different characters correspond to different capabilities provided by the demo, and the functionality menu is printed. +3. ManuList()函数从控制台通过fgets()接口获取字符,不同字符所对应demo支持的功能不同,并打印出该demo所支持功能的菜单。 ``` static void ManuList(const std::shared_ptr& mainDemo, @@ -590,22 +586,22 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director while(1) { switch (c) { case 'h': - c = PutMenuAndGetChr(); // Print the menu. + c = PutMenuAndGetChr(); // 打印菜单 break; case 'f': - FlashLightTest(mainDemo); // Test the flashlight capability. + FlashLightTest(mainDemo); // 手电筒功能测试 c = PutMenuAndGetChr(); break; case 'o': - OfflineTest(mainDemo); // Test the offline capability. + OfflineTest(mainDemo); // Offline功能测试 c = PutMenuAndGetChr(); break; case 'c': - CaptureTest(mainDemo); // Test the capture capability. + CaptureTest(mainDemo); // Capture功能测试 c = PutMenuAndGetChr(); break; - case 'w': // Test the AWB capability. + case 'w': // AWB功能测试 if (awb) { mainDemo->SetAwbMode(OHOS_CAMERA_AWB_MODE_INCANDESCENT); } else { @@ -614,15 +610,15 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director awb = !awb; c = PutMenuAndGetChr(); break; - case 'a': // Test the AE capability. + case 'a': // AE功能测试 mainDemo->SetAeExpo(); c = PutMenuAndGetChr(); break; - case 'v': // Test the video capability. + case 'v': // Video功能测试 VideoTest(mainDemo); c = PutMenuAndGetChr(); break; - case 'q': // Exit the demo. + case 'q': // 退出demo PreviewOff(mainDemo); mainDemo->QuitDemo(); exit(EXIT_SUCCESS); @@ -636,9 +632,9 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` - The **PutMenuAndGetChr\(\)** interface prints the menu of the demo and calls **fgets\(\)** to wait for commands from the console. + PutMenuAndGetChr()接口打印了demo程序的菜单,并调用fgets()等待从控制台输入命令,内容如下: - ``` + ``` static int PutMenuAndGetChr(void) { constexpr uint32_t inputCount = 50; @@ -658,7 +654,7 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director } ``` - The console outputs the menu details as follows: + 控制台输出菜单详情如下: ``` "Options:\n" @@ -672,6 +668,5 @@ There is a camera demo in the **/drivers/peripheral/camera/hal/init** director "-q | --quit stop preview and quit this app\n"); ``` - Other capabilities in the demo are implemented by calling different HDIs, which are similar to **PreviewOn\(\)**. For details, see [ohos\_camera\_demo](https://gitee.com/openharmony/drivers_peripheral/tree/master/camera/hal/init). - + demo中其他功能会调用不同的HDI接口去实现,与PreviewOn()接口类似,这里不再赘述,具体详情可以参见[ohos_camera_demo](https://gitee.com/openharmony/drivers_peripheral/tree/master/camera/hal/init)。 diff --git a/en/device-dev/driver/figures/camera-driver-model-architecture.png b/en/device-dev/driver/figures/camera-driver-model-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..772356a3efede5b1b8b45b27270ed79fcf9e9d01 GIT binary patch literal 34752 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