From 6463b1e0538b43b4a50f81edb7f8db0b1b9b7543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B7=AF=E5=BB=BA=E8=BE=89?= <1483564738@qq.com> Date: Thu, 24 Mar 2022 20:02:33 +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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 路建辉 <1483564738@qq.com> --- .../driver/driver-peripherals-camera-des.md | 1293 +++++++++-------- ...1\345\212\250\346\250\241\345\236\213.png" | Bin 0 -> 25269 bytes 2 files changed, 680 insertions(+), 613 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..b74332ad4f 100755 --- a/zh-cn/device-dev/driver/driver-peripherals-camera-des.md +++ b/zh-cn/device-dev/driver/driver-peripherals-camera-des.md @@ -1,31 +1,27 @@ -# 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实现层的控制、流的转发,实现数据通路的搭建,管理相机各个硬件设备等功能。 +### 运作机制 + +Camera模块主要包含服务、设备的初始化,数据通路的搭建,流的配置、创建、下发、捕获等,具体运作机制参考以下图文解析: -+ **适配层**:屏蔽底层芯片和OS(Operation System)差异,支持多平台适配。 +**图 1** 基于HDF驱动框架的Camera驱动模型  -**** -**图 1** 基于HDF驱动框架的Camera驱动模型 -![](figures/logic-view-of-camera-hal-zh.png) +    ![](figures/Camera模块驱动模型.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等待下一次下发。 @@ -38,635 +34,706 @@ OpenHarmony相机驱动框架模型对上实现相机HDI(Hardware Driver Inter 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设备的初始化,在开机时被调用。 +9. 动态帧率控制。在StreamOperator中起一个CollectBuffer线程,CollectBuffer线程从每一路stream的BufferQueue中获取buffer,如果某一路流的帧率需要控制(为sensor出帧帧率的1/n),可以根据需求控制每一帧的buffer打包,并决定是否collect此路流的buffer(比如sensor出帧帧率为120fps,预览流的帧率为30fps,CollectBuffer线程collect预览流的buffer时,每隔4fps collect一次)。 - ``` - 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()接口 - - 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等方法,获取流操作方法如下: - - ``` - 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); - return RC_OK; - } - ``` +### 场景介绍 -7. 配置流 +Camera模块主要用以相机预览、拍照、视频流等场景下对相机操作封装,使开发者更易操作相机硬件,提高开发效率。 - 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; - } - ``` +- icamera_device.h -8. 捕获图像 + | 功能描述 | 接口名称 | + | ---------------------------- | ------------------------------------------------------------ | + | 获取流控制器 | 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() | - 在调用Capture()接口前需要先填充CaptureInfo结构体,具体内容如下: +- icamera_device_callback.h - ``` - using CaptureInfo = struct _CaptureInfo { - std::vector streamIds_; //需要Capture的streamIds - std::shared_ptr captureSetting_; // 这里填充camera ability 可通过CameraHost 的GetCameraAbility()接口获取 - bool enableShutterCallback_; - }; - ``` + | 功能描述 | 接口名称 | + | ---------------------------------------------------------- | ------------------------------------------------------------ | + | 设备发生错误时调用,由调用者实现,用于返回错误信息给调用者 | void OnError(ErrorType type, int32_t errorCode) | + | 上报camera设备相关的metadata的回调 | void OnResult(uint64_t timestamp, const std::shared_ptr &result) | - 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; - } - ``` +- icamera_host.h -9. 取消捕获和释放离线流 + | 功能描述 | 接口名称 | + | ------------------------------ | ------------------------------------------------------------ | + | 设置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) | - StreamOperatorImpl类中的CancelCapture()接口的主要作用是根据captureId取消数据流的捕获。 +- icamera_host_callback.h - ``` - 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设备状态变化上报 | void OnCameraStatus(const std::string &cameraId, CameraStatus status) | + | 闪光灯状态变化回调 | void OnFlashlightStatus(const std::string &cameraId, FlashlightStatus status) | - StreamOperatorImpl类中的ReleaseStreams接口的主要作用是释放之前通过CreateStream()和CommitStreams()接口创建的流,并销毁Pipeline。 +- ioffline_stream_operator.h - ``` - CamRetCode StreamOperatorImpl::ReleaseStreams(const std::vector& streamIds) - { - RetCode rc = DestroyStreamPipeline(streamIds); //销毁该streamIds 的pipeline - rc = DestroyHostStreamMgr(streamIds); - rc = DestroyStreams(streamIds); //销毁该streamIds 的 Stream - return NO_ERROR; - } - ``` + | 功能描述 | 接口名称 | + | -------------- | ------------------------------------------------------------ | + | 取消捕获请求 | CamRetCode CancelCapture(int captureId) | + | 释放流 | CamRetCode ReleaseStreams(const std::vector &streamIds) | + | 释放所有离线流 | CamRetCode Release() | -10. 关闭Camera设备 +- istream_operator.h - 调用CameraDeviceImpl中的Close()来关闭CameraDevice,该接口调用deviceManager中的PowerDown()来给设备下电。 + | 功能描述 | 接口名称 | + | -------------------------------- | ------------------------------------------------------------ | + | 查询是否支持添加参数对应的流 | 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) | +- istream_operator_callback.h -## 开发实例 + | 功能描述 | 接口名称 | + | ---------------------------------------- | ------------------------------------------------------------ | + | 捕获开始回调,在捕获开始时调用 | 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) | -在/drivers/peripheral/camera/hal/init目录下有一个关于Camera的demo,开机后会在/system/bin下生成可执行文件ohos_camera_demo,该demo可以完成camera的预览,拍照等基础功能。下面我们就以此demo为例讲述怎样用HDI接口去编写预览PreviewOn()和拍照CaptureON()的用例。 +### 开发步骤 +Camera驱动的开发过程主要包含以下步骤: -1. 在main函数中构造一个Hos3516Demo对象,该对象中有对camera初始化、启停流、释放等控制的方法。下面mainDemo->InitSensors()函数为初始化CameraHost,mainDemo->InitCameraDevice()函数为初始化CameraDevice。 +1. **注册CameraHost** + 定义Camera的HdfDriverEntry结构体,该结构体中定义了CameraHost初始化的方法。 ``` - 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); // 打印菜单到控制台 + struct HdfDriverEntry g_cameraHostDriverEntry = { + .moduleVersion = 1, + .moduleName = "camera_service", + .Bind = HdfCameraHostDriverBind, + .Init = HdfCameraHostDriverInit, + .Release = HdfCameraHostDriverRelease, + }; + HDF_INIT(g_cameraHostDriverEntry); // 将Camera的HdfDriverEntry结构体注册到HDF上 + ``` + +2. **初始化Host服务** + +  步骤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. **获取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 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. **获取流** + + 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; + } + 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); + 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,开机后会在/system/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函数中构造一个Hos3516Demo对象,该对象中有对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 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()接口都会调用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 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; + } + ``` 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..640eeec89f4e417551b38cfcb82ab85bc670c464 GIT binary patch literal 25269 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
    TYYKW%*8Sf2IFAGrY{J}6B(fm` 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