From 73b6cb462d56aeddc87baddf015943e62bf4b5a2 Mon Sep 17 00:00:00 2001 From: lujianhui <1483564738@qq.com> Date: Wed, 30 Mar 2022 09:44:35 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E5=AD=90=E7=B3=BB=E7=BB=9FCa?= =?UTF-8?q?mera=E6=A8=A1=E5=9D=97=E5=BC=80=E5=8F=91=E6=8C=87=E5=8D=97?= =?UTF-8?q?=E6=A0=87=E5=87=86=E5=8C=96=20Signed-off-by:=20lujianhui=20<148?= =?UTF-8?q?3564738@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../driver/driver-peripherals-camera-des.md | 1295 +++++++++-------- ...1\345\212\250\346\250\241\345\236\213.png" | Bin 0 -> 27028 bytes 2 files changed, 681 insertions(+), 614 deletions(-) create mode 100644 "zh-cn/device-dev/driver/figures/Camera\346\250\241\345\235\227\351\251\261\345\212\250\346\250\241\345\236\213.png" diff --git a/zh-cn/device-dev/driver/driver-peripherals-camera-des.md b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md index 148726f226..3eb096c087 100755 --- a/zh-cn/device-dev/driver/driver-peripherals-camera-des.md +++ b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md @@ -1,32 +1,28 @@ -# Camera +# Camera -- [Camera](#camera) - - [概述](#概述) - - [开发指导](#开发指导) - - [HDI接口说明](#hdi接口说明) - - [开发步骤](#开发步骤) - - [开发实例](#开发实例) +## 概述 +### 功能简介 +OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Device Interface)接口,对下实现相机Pipeline模型,管理相机各个硬件设备。 +该驱动框架模型内部分为三层,依次为HDI实现层、框架层和设备适配层,各层基本概念如下: -## 概述 ++ HDI实现层:实现OHOS(OpenHarmony Operation System)相机标准南向接口。 ++ 框架层:对接HDI实现层的控制、流的转发,实现数据通路的搭建,管理相机各个硬件设备等功能。 ++ 设备适配层:屏蔽底层芯片和OS(Operation System)差异,支持多平台适配。 -OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Driver Interface)接口,对下实现相机Pipeline模型,管理相机各个硬件设备。 -该驱动框架模型内部分为三层,依次为HDI实现层、框架层和适配层,各层基本概念如下: -+ **HDI实现层**:对上实现OHOS(OpenHarmony Operation System)相机标准南向接口。 - -+ **框架层**:对接HDI实现层的控制、流的转发,实现数据通路的搭建,管理相机各个硬件设备等功能。 +### 运作机制 -+ **适配层**:屏蔽底层芯片和OS(Operation System)差异,支持多平台适配。 +Camera模块主要包含服务、设备的初始化,数据通路的搭建,流的配置、创建、下发、捕获等,具体运作机制参考以下图文解析: -**** -**图 1** 基于HDF驱动框架的Camera驱动模型 -![](figures/logic-view-of-camera-hal-zh.png) +**图 1** 基于HDF驱动框架的Camera驱动模型  -1. 系统启动时创建CameraDeviceHost进程。进程创建后,首先枚举底层设备,创建(也可以通过配置表创建)管理设备树的DeviceManager类及其内部各个底层设备的对象,创建对应的CameraHost类实例并且将其注册到UHDF服务中,方便上层通过UHDF服务获取底层CameraDeviceHost的服务,从而操作底层设备。 +        ![](figures/Camera模块驱动模型.png) -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调用管理。 +1. 系统启动时创建camera_host进程。进程创建后,首先枚举底层设备,创建(也可以通过配置表创建)管理设备树的DeviceManager类及其内部各个底层设备的对象,创建对应的CameraHost类实例并且将其注册到UHDF服务中,方便相机服务层通过UHDF服务获取底层CameraDeviceHost的服务,从而操作硬件设备。 + +2. Service通过CameraDeviceHost服务获取CameraHost实例,CameraHost可以获取底层的Camera能力,打开手电筒、调用Open接口打开Camera创建连接、创建DeviceManager(负责底层硬件模块上电)、创建CameraDevice(向上提供设备控制接口)。创建CameraDevice时会实例化PipelineCore的各个子模块,其中StreamPipelineCore负责创建Pipeline,MetaQueueManager负责上报metaData。 + +3. Service通过CameraDevice模块配置流、创建Stream类。StreamPipelineStrategy模块通过上层下发的模式和查询配置表创建对应流的Node连接方式,StreamPipelineBuilder模块创建Node实例并且连接返回该Pipeline给StreamPipelineDispatcher。StreamPipelineDispatcher提供统一的Pipeline调用管理。 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等待下一次下发。 @@ -34,639 +30,710 @@ OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Driver Inter 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模块再调用上层回调传递给上层。 +7. Service通过EnableResult和DisableResult接口控制底层metaData的上报。如果需要底层metaData上报,pipeline会创建CameraDeviceDriverModel内部的一个Bufferqueue用来收集和传递metaData,根据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进行初始化。 +9. 动态帧率控制。在StreamOperator中起一个CollectBuffer线程,CollectBuffer线程从每一路stream的BufferQueue中获取buffer,如果某一路流的帧率需要控制(为sensor出帧帧率的1/n),可以根据需求控制每一帧的buffer打包,并决定是否collect此路流的buffer(比如sensor出帧帧率为120fps,预览流的帧率为30fps,CollectBuffer线程collect预览流的buffer时,每隔4fps collect一次)。 -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()接口 - - 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) - { - 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]; - cameraDevice->SetStatus(true); - return NO_ERROR; - } - ``` -5. GetStreamOperator()接口 +### 场景介绍 - CameraDeviceImpl定义了GetStreamOperator、UpdateSettings、SetResultMode和GetEnabledResult等方法,获取流操作方法如下: +Camera模块主要用以相机预览、拍照、视频流等场景下对相机操作封装,使开发者更易操作相机硬件,提高开发效率。 - ``` - 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. 创建流 +- icamera_device.h - 调用CreateStreams创建流前需要填充StreamInfo结构体,具体内容如下: + | 功能描述 | 接口名称 | + | ---------------------------- | ------------------------------------------------------------ | + | 获取流控制器 | CamRetCode GetStreamOperator(
const OHOS::sptr &callback,
OHOS::sptr &streamOperator) | + | 更新设备控制参数 | CamRetCode UpdateSettings(const std::shared_ptr &settingss) | + | 设置Result回调模式和回调函数 | CamRetCode SetResultMode(const ResultCallbackMode &mode) | + | 获取使能的ResultMeta | CamRetCode GetEnabledResults(std::vector &results) | + | 使能具体的ResultMeta | CamRetCode EnableResult(const std::vector &results) | + | 禁止具体的ResultMeta | CamRetCode DisableResult(const std::vector &results) | + | 关闭Camera设备 | void Close() | - ``` - 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_; - }; - ``` +- icamera_device_callback.h - CreateStreams()接口是StreamOperatorImpl类中的方法,该接口的主要作用是创建一个StreamBase对象,通过StreamBase的Init方法初始化CreateBufferPool等操作。 + | 功能描述 | 接口名称 | + | ---------------------------------------------------------- | ------------------------------------------------------------ | + | 设备发生错误时调用,由调用者实现,用于返回错误信息给调用者 | void OnError(ErrorType type, int32_t errorCode) | + | 上报camera设备相关的metadata的回调 | void OnResult(uint64_t timestamp, const std::shared_ptr &result) | - ``` - 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); - return RC_OK; - } - ``` -7. 配置流 +- icamera_host.h - CommitStreams()是配置流的接口,必须在创建流之后调用,其主要作用是初始化Pipeline和创建Pipeline。 + | 功能描述 | 接口名称 | + | ------------------------------ | ------------------------------------------------------------ | + | 设置ICameraHost回调接口 | CamRetCode SetCallback(const OHOS::sptr &callback) | + | 获取当前可用的Camera设备ID列表 | CamRetCode GetCameraIds(std::vector &cameraIds) | + | 获取Camera设备能力集合 | CamRetCode GetCameraAbility(const std::string &cameraId,
std::shared_ptr &ability) | + | 打开Camera设备 | CamRetCode OpenCamera(const std::string &cameraId,
const OHOS::sptr &callback,
OHOS::sptr &device) | + | 打开或关闭闪光灯 | CamRetCode SetFlashlight(const std::string &cameraId, bool &isEnable) | - ``` - 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; - } - ``` +- icamera_host_callback.h -8. 捕获图像 + | 功能描述 | 接口名称 | + | ---------------------- | ------------------------------------------------------------ | + | Camera设备状态变化上报 | void OnCameraStatus(const std::string &cameraId, CameraStatus status) | + | 闪光灯状态变化回调 | void OnFlashlightStatus(const std::string &cameraId, FlashlightStatus status) | - 在调用Capture()接口前需要先填充CaptureInfo结构体,具体内容如下: +- ioffline_stream_operator.h - ``` - using CaptureInfo = struct _CaptureInfo { - std::vector streamIds_; //需要Capture的streamIds - std::shared_ptr captureSetting_; // 这里填充camera ability 可通过CameraHost 的GetCameraAbility()接口获取 - bool enableShutterCallback_; - }; - ``` + | 功能描述 | 接口名称 | + | -------------- | ------------------------------------------------------------ | + | 取消捕获请求 | CamRetCode CancelCapture(int captureId) | + | 释放流 | CamRetCode ReleaseStreams(const std::vector &streamIds) | + | 释放所有离线流 | CamRetCode Release() | - StreamOperatorImpl中的Capture方法主要调用CreateCapture()接口去捕获数据流: +- istream_operator.h - ``` - 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; - } - ``` + | 功能描述 | 接口名称 | + | -------------------------------- | ------------------------------------------------------------ | + | 查询是否支持添加参数对应的流 | CamRetCode IsStreamsSupported(
OperationMode mode,
const std::shared_ptr &modeSetting,
const std::vector<std::shared_ptr<StreamInfo>> &info,
StreamSupportType &type) | + | 创建流 | CamRetCode CreateStreams(const std::vector> &streamInfos) | + | 释放流 | CamRetCode ReleaseStreams(const std::vector &streamIds) | + | 配置流 | CamRetCode CommitStreams(OperationMode mode,
const std::shared_ptr &modeSetting) | + | 获取流的属性 | CamRetCode GetStreamAttributes(
std::vector> &attributes) | + | 绑定生产者句柄和指定流 | CamRetCode AttachBufferQueue(int streamId, const OHOS::sptr &producer) | + | 解除生产者句柄和指定流的绑定关系 | CamRetCode DetachBufferQueue(int streamId) | + | 捕获图像 | CamRetCode Capture(int captureId,
const std::shared_ptr &info, bool isStreaming) | + | 取消捕获 | CamRetCode CancelCapture(int captureId) | + | 将指定流转换成离线流 | CamRetCode ChangeToOfflineStream(const std::vector &streamIds,
OHOS::sptr &callback,
OHOS::sptr &offlineOperator) | -9. 取消捕获和释放离线流 +- istream_operator_callback.h - StreamOperatorImpl类中的CancelCapture()接口的主要作用是根据captureId取消数据流的捕获。 + | 功能描述 | 接口名称 | + | ---------------------------------------- | ------------------------------------------------------------ | + | 捕获开始回调,在捕获开始时调用 | void OnCaptureStarted(int32_t captureId, const std::vector &streamIds) | + | 捕获结束回调,在捕获结束时调用 | void OnCaptureEnded(int32_t captureId,
const std::vector> &infos) | + | 捕获错误回调,在捕获过程中发生错误时调用 | void OnCaptureError(int32_t captureId,
const std::vector> &infos) | + | 帧捕获回调 | void OnFrameShutter(int32_t captureId,
const std::vector &streamIds, uint64_t timestamp) | - ``` - 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; - } - ``` +### 开发步骤 +Camera驱动的开发过程主要包含以下步骤: - StreamOperatorImpl类中的ReleaseStreams接口的主要作用是释放之前通过CreateStream()和CommitStreams()接口创建的流,并销毁Pipeline。 +1. **注册CameraHost** + 定义Camera的HdfDriverEntry结构体,该结构体中定义了CameraHost初始化的方法。 ``` - 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设备 + struct HdfDriverEntry g_cameraHostDriverEntry = { + .moduleVersion = 1, + .moduleName = "camera_service", + .Bind = HdfCameraHostDriverBind, + .Init = HdfCameraHostDriverInit, + .Release = HdfCameraHostDriverRelease, + }; + HDF_INIT(g_cameraHostDriverEntry); // 将Camera的HdfDriverEntry结构体注册到HDF上 + ``` - 调用CameraDeviceImpl中的Close()来关闭CameraDevice,该接口调用deviceManager中的PowerDown()来给设备下电。 +2. **初始化Host服务** + 步骤1中提到的HdfCameraHostDriverBind接口提供了CameraServiceDispatch和CameraHostStubInstance的注册。这两个接口一个是远端调用CameraHost的方法,如OpenCamera(),SetFlashlight()等,另外一个是Camera设备的初始化,在开机时被调用。 -## 开发实例 - -在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo,开机后会在/system/bin下生成可执行文件ohos_camera_demo,该demo可以完成camera的预览,拍照等基础功能。下面我们就以此demo为例讲述怎样用HDI接口去编写预览PreviewOn()和拍照CaptureON()的用例。 - -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(); // 初始化CameraHost - 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); // 打印菜单到控制台 + ``` + 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. **获取Host服务** + + 调用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. **打开设备** + + 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) + { + 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 failed."); + 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]; + cameraDevice->SetStatus(true); + return NO_ERROR; + } + ``` + +5. **获取流** + + CameraDeviceImpl定义了GetStreamOperator、UpdateSettings、SetResultMode和GetEnabledResult等方法,获取流操作方法如下: + + ``` + CamRetCode CameraDeviceImpl::GetStreamOperator(const OHOS::sptr &callback, + OHOS::sptr &streamOperator) + { + if (callback == nullptr) { + CAMERA_LOGW("input callback is null."); + return INVALID_ARGUMENT; + } + spCameraDeviceCallback_ = callback; + if (spStreamOperator_ == nullptr) { + // 这里new了一个spStreamOperator对象传递给调用者,以便对stream进行各种操作。 + spStreamOperator_ = new(std::nothrow) StreamOperatorImpl(spCameraDeviceCallback_, shared_from_this()); + if (spStreamOperator_ == nullptr) { + CAMERA_LOGW("create stream operator failed."); + return DEVICE_ERROR; + } + ismOperator_ = spStreamOperator_; + } + streamOperator = ismOperator_; + + spStreamOperator_->SetRequestCallback([this](){ + spCameraDeviceCallback_->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 dataSpace_; + 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); + 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设备** - return RC_OK; - } - ``` + 调用CameraDeviceImpl中的Close()来关闭CameraDevice,该接口调用deviceManager中的PowerDown()来给设备下电。 - 初始化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; - } - ``` +在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo,开机后会在/vendor/bin下生成可执行文件ohos_camera_demo,该demo可以完成Camera的预览,拍照等基础功能。下面我们就以此demo为例讲述怎样用HDI接口去编写预览PreviewOn()和拍照CaptureON()的用例,可参考[ohos_camera_demo](https://gitee.com/openharmony/drivers_peripheral/tree/master/camera/hal/init)。 - 初始化CameraDevice函数实现如下,这里调用了GetCameraIds(cameraIds_),GetCameraAbility(cameraId, ability_),OpenCamera(cameraIds_.front(), callback, demoCameraDevice_)等接口实现了demoCameraHost的获取。 +1. 在main函数中构造一个CameraDemo 对象,该对象中有对Camera初始化、启停流、释放等控制的方法。下面mainDemo->InitSensors()函数为初始化CameraHost,mainDemo->InitCameraDevice()函数为初始化CameraDevice。 - ``` - 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; - } - ``` + ``` + int main(int argc, char** argv) + { + RetCode rc = RC_OK; + auto mainDemo = std::make_shared(); + rc = mainDemo->InitSensors(); // 初始化CameraHost + 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 CameraDemo::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 CameraDemo::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()接口都会调用CreateStream()接口,只是传入的参数不同。 - - ``` - RetCode Hos3516Demo::StartVideoStream() - { - RetCode rc = RC_OK; - if (isVideoOn_ == 0) { - isVideoOn_ = 1; - rc = CreateStream(STREAM_ID_VIDEO, streamCustomerVideo_, VIDEO); // 如需启preview或者capture流更改该接口参数即可。 - } - return RC_OK; - } - ``` - - CreateStream()方法调用HDI接口去配置和创建流,首先调用HDI接口去获取StreamOperation对象,然后创建一个StreamInfo。调用CreateStreams()和CommitStreams()实际创建流并配置流。 - - ``` - RetCode Hos3516Demo::CreateStreams(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: CreateStream 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: CreateStreams 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: CreateStream CreateStreams error\n"); - return RC_ERROR; - } - - 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}; - 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; - } - ``` + ``` + 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()接口都会调用CreateStream()接口,只是传入的参数不同。 + + ``` + RetCode CameraDemo::StartVideoStream() + { + RetCode rc = RC_OK; + if (isVideoOn_ == 0) { + isVideoOn_ = 1; + rc = CreateStream(STREAM_ID_VIDEO, streamCustomerVideo_, VIDEO); // 如需启preview或者capture流更改该接口参数即可。 + } + return RC_OK; + } + ``` + + CreateStream()方法调用HDI接口去配置和创建流,首先调用HDI接口去获取StreamOperation对象,然后创建一个StreamInfo。调用CreateStreams()和CommitStreams()实际创建流并配置流。 + + ``` + RetCode CameraDemo::CreateStreams(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: CreateStream 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: CreateStreams 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: CreateStream CreateStreams error\n"); + return RC_ERROR; + } + + 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}; + streamOperator_->ReleaseStreams(streamIds); + return RC_ERROR; + } + return RC_OK; + } + ``` + + CaptureON()接口调用streamOperator的Capture()方法获取Camera数据并轮转buffer,拉起一个线程接收相应类型的数据。 + + ``` + RetCode CameraDemo::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)。 + ``` + 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"); + ``` + diff --git "a/zh-cn/device-dev/driver/figures/Camera\346\250\241\345\235\227\351\251\261\345\212\250\346\250\241\345\236\213.png" "b/zh-cn/device-dev/driver/figures/Camera\346\250\241\345\235\227\351\251\261\345\212\250\346\250\241\345\236\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..1a4fa27e98e5bb4568be1399b57da3b987ee4fbf GIT binary patch literal 27028 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