OpenHarmony Camera组件架构分析以及拍照流程源码解析 原创 精华

深开鸿
发布于 2022-6-1 18:15
浏览
2收藏

作者:邵黎明

本文以OpenHarmony 3.1 Release - multimedia_camera_standard源码为基础进行分析。

概述

相机功能是现在智能设备一个非常重要的功能。OpenHarmony 相机组件支持相机业务的开发,开发者可以通过已开放的接口实现相机硬件的访问、操作和新功能开发,最常见的操作如:预览、拍照和录像等。

基本概念

拍照

此功能用于拍摄采集照片。

预览

此功能用于在开启相机后,在缓冲区内重复采集摄像帧,支持在拍照或录像前进行摄像帧预览显示。

录像

此功能用于在开始录像后和结束录像前的时间段内,在缓冲区内重复采集摄像帧,支持视频录制。

相机组件架构分析

OpenHarmony 相机组件从上层到下层依次可以分为:应用层 - 相机框架服务层 - 系统服务层(驱动框架服务)- 内核驱动。其实 OpenHarmony 的多数组件都和相机组件有同样架构层次。

代码仓库目录结构

Camera Standard 相关代码有两个仓库,相机框架层 OpenHarmony / multimedia_camera_standard 和 系统服务(驱动框架服务层) OpenHarmony / drivers_peripheral / camera

相机框架层

代码结构

/foundation/multimedia/camera_standard     # 相机组件业务代码
├── frameworks                             # 框架代码
│   ├── js                                 # 外部接口实现
│   │   └── camera_napi                    # 相机NAPI实现
│   └── native                             # 内部接口实现
│       ├── camera                         # 相机框架实现
│       └── metadata                       # 元数据实现
├── interfaces                             # 接口代码
│   ├── inner_api                          # 内部接口
│   └── kits                               # 外部接口
├── ohos.build                             # 构建脚本
└── services              
    ├── camera_service                     # 相机框架服务
    │   ├── binder    
    │   │   ├── base                       # 相机框架服务结构声明
    │   │   ├── client                     # 相机框架服务客户端
    │   │   └── server                     # 相机框架服务服务端
    │   ├── include                        # 相机服务头文件 
    │   └── src                            # 相机服务实现
    └── etc                                # 相机服务配置

相机驱动框架服务层

代码结构

/drivers/peripheral/camera
├── hal                         # camera模块的hal层代码(标准系统)
│   ├── adapter                 # camera hal平台适配层的实现
│   ├── buffer_manager          # camera hal统一的Buffer管理
│   ├── device_manager          # 提供camera hal层设备管理能力,包括设备枚举、设备能力查询等
│   ├── hdi_impl                # camera hal HDI的具体实现
│   ├── include                 # camera hal层内部的头文件
│   ├── init                    # camera hal层HDI接口使用样例实现
│   ├── pipeline_core           # camera hal层pipeline核心代码 
│   ├── test                    # camera hal层测试代码实现
│   └── utils                   # camera hal层工具类代码,目前提供的是watchdog
├── hal_c                       # 提供C实现的HAL接口
│   ├── hdi_cif                 # C实现的HDI接口适配代码
│   └── include                 # C形式的HDI接口
└── interfaces                  # camera hal对上层服务提供的驱动能力接口
    ├── hdi_ipc                 # IPC模式的HDI实现(标准系统)
    │   ├── client              # IPC 客户端
    │   └── server              # IPC 服务端接口
    ├── hdi_passthrough         # 直通模式的HDI实现
    └── include                 # camera hal对外提供的HDI定义

代码调用层级关系

OpenHarmony 应用层一般使用JS或者eTS语言进行开发,相机框架层 最外层对上提供NAPI JS接口,对下通过IPC方式调用其相机框架服务。相机框架服务通过IPC方式调用相机驱动框架服务。相机驱动框架服务调用实际的相机硬件驱动。代码调用层级关系参考下图。

OpenHarmony Camera组件架构分析以及拍照流程源码解析-鸿蒙开发者社区

关于上图的说明:

相机框架层概念和流程

框架层把相机功能抽象为会话管理(管理生命周期、参数配置、输入管理、输出管理)、设备输入(设备查询、设备控制、设备监听)和数据输出(元数据输出、流输出和状态控制),其对应的类名依次为CaptureSessionCameraInputCaptureOutput
其中数据输出CaptureOutput根据实际业务功能又细分为预览输出PreviewOutput拍照输出PhotoOutput录像输出VideoOutput。以上所有类都由相机管理类CameraManager引出。

其相机功能(预览、拍照和录像)代码流程都可以使用如下伪代码进行描述。

// 创建相机管理对象
camManagerObj = new CameraManager();

// 创建设备输入
cameraInput = camManagerObj->CreateCameraInput();

// 创建设备输出
captureOutput = camManagerObj->CreateCaptureOutput();

// 创建管理会话
captureSession = camManagerObj->CreateCaptureSession();

// 会话绑定输入
captureSession->AddInput(cameraInput);

// 会话绑定输出
captureSession->AddOutput(photoOutput);

// 使绑定生效
captureSession->CommitConfig();

// 开始预览拍照或录制
captureSession->Start();

// 停止预览或录制
captureSession->Stop();

// 释放资源
Release();

其中创建输入需要指定摄像头设备,创建输出需要配置输出到什么地方。

相机框架层拍照流程解析

上节使用伪代码阐述了相机功能的主要流程,这一节详细讲一讲拍照这一操作在框架层的实际代码实现。

拍照流程图

拍照代码流程顺序如下图所示
OpenHarmony Camera组件架构分析以及拍照流程源码解析-鸿蒙开发者社区

拍照示例代码

  1. 创建缓冲区消费者端监听器(CaptureSurfaceListener)以保存图像

    // 继承IBufferConsumerListener类
    class CaptureSurfaceListener : public IBufferConsumerListener {
    public:
        sptr<Surface> surface_; // 此为传入的拍照用的消费者Surface
    
        // 有数据产生时回调此函数
        void OnBufferAvailable() override
        {
            int32_t flushFence = 0;
            int64_t timestamp = 0;
            OHOS::Rect damage;
    
            OHOS::sptr<OHOS::SurfaceBuffer> buffer = nullptr;
            surface_->AcquireBuffer(buffer, flushFence, timestamp, damage); // 从消费者Surface 队列中获取包含数据的Buffer
            if (buffer != nullptr) {
                void *addr = buffer->GetVirAddr(); // 获取图像数据的虚拟地址
                int32_t size = buffer->GetSize();  // 获取图像数据长度
    
                // write(fd, addr, size) 保存图像数据到文件
    
                surface_->ReleaseBuffer(buffer, -1); // 释放Buffer
            }
        }
    };
    
  2. 获取相机管理器实例并获取相机对象列表

    sptr<CameraManager> camManagerObj = CameraManager::GetInstance();          // 所有的类都由CameraManager创建
    std::vector<sptr<CameraInfo>> cameraObjList = camManagerObj->GetCameras(); // 如果有多个摄像头会返回多个
    
  3. 使用相机对象创建相机输入来打开相机

    sptr<CaptureInput> cameraInput = camManagerObj->CreateCameraInput(cameraObjList[0]);  // 使用第一个摄像头创建设备输入
    
  4. 创建采集会话

    sptr<CaptureSession> captureSession = camManagerObj->CreateCaptureSession();
    
  5. 开始配置采集会话

    int32_t result = captureSession->BeginConfig(); // 初始化会话
    
  6. 将相机输入添加到采集会话

    result = captureSession->AddInput(cameraInput);
    
  7. 创建消费者Surface并注册监听器以监听缓冲区更新

    sptr<Surface> photoSurface = Surface::CreateSurfaceAsConsumer(); 
    int32_t photoWidth = 1280; // 设置所支持的图片size
    int32_t photoHeight = 960;
    photoSurface->SetDefaultWidthAndHeight(photoWidth, photoHeight);
    photoSurface->SetUserData(CameraManager::surfaceFormat, std::to_string(OHOS_CAMERA_FORMAT_JPEG)); // 当前仅支持JPEG图片格式
    sptr<CaptureSurfaceListener> capturelistener = new CaptureSurfaceListener();
    capturelistener->surface_ = photoSurface; // 给回调类传递surface
    photoSurface->RegisterConsumerListener((sptr<IBufferConsumerListener> &)capturelistener); // 注册回调函数
    
  8. 使用上面创建的 Surface 创建拍照输出

    sptr<CaptureOutput> photoOutput = camManagerObj->CreatePhotoOutput(photoSurface);
    
  9. 将拍照输出添加到采集会话

    result = captureSession->AddOutput(photoOutput);
    
  10. 将配置提交到采集会话。

    result = captureSession->CommitConfig(); // 把Input和Output做关联等
    
  11. 拍摄照片。

    result = ((sptr<PhotoOutput> &)photoOutput)->Capture(); // 调用最底层的数据接口采集图像
    
  12. 释放采集会话资源。

    captureSession->Release();
    
  13. 释放相机输入关闭相机。

    cameraInput->Release();
    

驱动框架层拍照流程源码解析

下图是驱动框架层层次结构图

OpenHarmony Camera组件架构分析以及拍照流程源码解析-鸿蒙开发者社区

1. 与CameraHost通信,获取CameraId

int32_t HCameraHostManager::GetCameras(std::vector<std::string>& cameraIds){
	...
	// 第一次时候 初始化 CameraHostInfo,CameraHostInfo中获取CameraHost
	AddCameraHost("camera_service"); 
    // 返回从底层获取的 CameraId
	cameraHost->GetCameras(cameraIds);
	return CAMERA_OK;
}

void HCameraHostManager::AddCameraHost(const std::string& svcName){
	// 创建一个 CameraHostInfo
	sptr<HCameraHostManager::CameraHostInfo> cameraHost = new HCameraHostManager::CameraHostInfo(this, svcName);
	// 初始化时获取 CameraHost的Proxy
	cameraHost->Init();
    // CameraHostInfo中维护CameraHost
	cameraHostInfos_.push_back(cameraHost);
    std::vector<std::string> cameraIds;
    ...
}

bool HCameraHostManager::CameraHostInfo::Init()
{
    // 获取驱动框架服务层的 CameraHost的Proxy代理
    cameraHostProxy_ = Camera::ICameraHost::Get(name_.c_str());
	// 通过cameraHostProxy 查询物理设备对应的cameraIds
    Camera::CamRetCode ret = cameraHostProxy_->GetCameraIds(cameraIds_);
	...
    return true;
}

2. 根据CameraId打开摄像头获取CameraDevice

// 根据CameraId 创建 CameraInput 的时候创建 CameraDevice
sptr<CameraInput> CameraManager::CreateCameraInput(sptr<CameraInfo> &camera){
	CreateCameraDevice(camera->GetID());
    ...
    cameraInput = new CameraInput(deviceObj, camera);
}

int32_t HCameraService::CreateCameraDevice(std::string cameraId, sptr<ICameraDeviceService> &device){
	sptr<HCameraDevice> cameraDevice;
	cameraDeviceCallback_ = new CameraDeviceCallback();
    // 创建 HCameraDevice
	cameraDevice = new HCameraDevice(cameraHostManager_, cameraDeviceCallback_, cameraId);
	devices_.insert(std::make_pair(cameraId, cameraDevice));
    device = cameraDevice;
    return CAMERA_OK;
}

// 把 CameraInput 加入到 CaptureSession 中
int32_t HCaptureSession::AddInput(sptr<ICameraDeviceService> cameraDevice){
    sptr<HCameraDevice> localCameraDevice = nullptr;
    localCameraDevice = static_cast<HCameraDevice*>(cameraDevice.GetRefPtr());
    // 保存在 tempCameraDevices_ 中
    tempCameraDevices_.emplace_back(localCameraDevice);
    return CAMERA_OK;
}

// CommitConfig 提交配置使其生效的时候会打开 CameraDevice
int32_t HCaptureSession::CommitConfig(){
    GetCameraDevice(device);
    ...
}

int32_t HCaptureSession::GetCameraDevice(sptr<HCameraDevice> &device) {
    sptr<HCameraDevice> camDevice;
    camDevice = tempCameraDevices_[0]; // 从中取出上一步存储的cameraDevice
    // 打开 CameraDevice
    rc = camDevice->Open();
    // 获取streamOperator,第一次会创建一个streamOperator与cameraDevice进行绑定
    // 后面的流程中会再次获取这个streamOperator
    rc = camDevice->GetStreamOperator(streamOperatorCallback_, streamOperator);
    device = camDevice;
    return rc;
}

3. 根据CameraDevice获取流操作器 StreamOperator

int32_t HCaptureSession::CommitConfig(){
    ...
    HandleCaptureOuputsConfig(device);
}

int32_t HCaptureSession::HandleCaptureOuputsConfig(sptr<HCameraDevice> &device){
    ...
	rc = CheckAndCommitStreams(device, settings, allStreamInfos, newStreamInfos);	
}

int32_t HCaptureSession::CheckAndCommitStreams(sptr<HCameraDevice> &device,
                                               std::shared_ptr<CameraMetadata> &deviceSettings,
                                               std::vector<std::shared_ptr<Camera::StreamInfo>> &allStreamInfos,
                                               std::vector<std::shared_ptr<Camera::StreamInfo>> &newStreamInfos) {
    ...
    return CreateAndCommitStreams(device, deviceSettings, newStreamInfos);
}


int32_t HCaptureSession::CreateAndCommitStreams(sptr<HCameraDevice> &device,
                                                std::shared_ptr<CameraMetadata> &deviceSettings,
                                                std::vector<std::shared_ptr<Camera::StreamInfo>> &streamInfos)
{
    ...
    streamOperator = device->GetStreamOperator(); // 获取StreamOperator 
    ...
	streamOperator->CreateStreams(streamInfos); // 通过配置信息 创建流
}

4. 通过StreamOperator创建Stream

获取StreamOperator后会接着CreateStreams,这一步主要分析创建Stream之前的参数配置。

int32_t HCaptureSession::CommitConfig(){
    HandleCaptureOuputsConfig(device);
}

int32_t HCaptureSession::HandleCaptureOuputsConfig(sptr<HCameraDevice> &device) {
	curStreamInfo = std::make_shared<Camera::StreamInfo>();
    // 配置流的信息
    curStreamCapture->SetStreamInfo(curStreamInfo);
}

// 配置流的信息
void HStreamCapture::SetStreamInfo(std::shared_ptr<Camera::StreamInfo> streamInfoPhoto)
{
    int32_t pixelFormat;
#ifdef RK_CAMERA
        pixelFormat = PIXEL_FMT_RGBA_8888;
#else
        pixelFormat = PIXEL_FMT_YCRCB_420_SP;
#endif

    streamInfoPhoto->streamId_ = photoStreamId_;
    streamInfoPhoto->width_ = producer_->GetDefaultWidth();		// 分辨率
    streamInfoPhoto->height_ = producer_->GetDefaultHeight();
    streamInfoPhoto->format_ = pixelFormat;	// 像素格式
    streamInfoPhoto->datasapce_ = CAMERA_PHOTO_COLOR_SPACE;
    streamInfoPhoto->intent_ = Camera::STILL_CAPTURE;
    streamInfoPhoto->tunneledMode_ = true;
    streamInfoPhoto->bufferQueue_ = producer_;
    streamInfoPhoto->encodeType_ = Camera::ENCODE_TYPE_JPEG; // 图片编码格式
}

// 最后走到这个函数创建流
int32_t HCaptureSession::CreateAndCommitStreams(sptr<HCameraDevice> &device,
                                                std::shared_ptr<CameraMetadata> &deviceSettings,
                                                std::vector<std::shared_ptr<Camera::StreamInfo>> &streamInfos)
{
    streamOperator = device->GetStreamOperator(); // 获取StreamOperator 
    ...
	streamOperator->CreateStreams(streamInfos); // 创建流
}

CamRetCode StreamOperator::CreateStreams(const std::vector<std::shared_ptr<StreamInfo>>& streamInfos){
    ...
    StreamConfiguration scg;
    // 把streamInfos转换为scg格式
    
    // 创建 StreamBase
    std::shared_ptr<IStream> stream = StreamFactory::Instance().CreateShared(
            IStream::g_availableStreamType[it->intent_], it->streamId_, it->intent_, pipelineCore_, messenger_);
    // 配置 StreamBase
    RetCode rc = stream->ConfigStream(scg);
}

5. 通过StreamOperator提交Stream,并设置数据流回调函数

int32_t HCaptureSession::CreateAndCommitStreams(sptr<HCameraDevice> &device,
                                                std::shared_ptr<CameraMetadata> &deviceSettings,
                                                std::vector<std::shared_ptr<Camera::StreamInfo>> &streamInfos)
{
    streamOperator = device->GetStreamOperator(); // 获取StreamOperator 
    ...
	streamOperator->CreateStreams(streamInfos); // 创建流
	...
	hdiRc = streamOperator->CommitStreams(Camera::NORMAL, deviceSettings); 
}

CamRetCode StreamOperator::CommitStreams(OperationMode mode,
                                         const std::shared_ptr<CameraStandard::CameraMetadata>& modeSetting)
{
    ...
    // 调用 StreamBase类的CommitStream函数
    stream->CommitStream();
    ...
}

// 提交Stream,并设置数据流的回调函数
RetCode StreamBase::CommitStream()
{
	...
    hostStreamMgr_ = pipelineCore_->GetHostStreamMgr();
    // 创建HostStream,并设置数据流的回调函数HandleResult() 
    RetCode rc = hostStreamMgr_->CreateHostStream(info, [this](std::shared_ptr<IBuffer> buffer) {
        HandleResult(buffer);
        return;
    });
}

6. 拍一张照片

CommitConfig中环节中,会给HStreamCapture 传递一个StreamOperator。

// CommitConfig 提交配置
int32_t HCaptureSession::CommitConfig(){
    ...
    HandleCaptureOuputsConfig(device);
}

// 设置 streamOperator 与 HStreamCapture 的绑定关系
int32_t HCaptureSession::HandleCaptureOuputsConfig(sptr<HCameraDevice> &device){
    ...
    streamOperator = device->GetStreamOperator();
    curStreamCapture->LinkInput(streamOperator, settings, streamId); 
    ...
}

// 保存streamOperator
int32_t HStreamCapture::LinkInput(sptr<Camera::IStreamOperator> streamOperator,
    std::shared_ptr<CameraMetadata> cameraAbility, int32_t streamId){
    streamOperator_ = streamOperator; 
    photoStreamId_ = streamId;
    cameraAbility_ = cameraAbility;
    return CAMERA_OK;
}


操作streamOperator进行拍照。

int32_t HStreamCapture::Capture(const std::shared_ptr<CameraMetadata> &captureSettings){
    // 拍照会调用 streamOperator 的捕获图像函数
    rc = streamOperator_->Capture(CurCaptureId, captureInfoPhoto, false);
}

7. 采集到的图像数据回调流程

系统通过 V4L2 或者 mpp 采集到的图像数据,通过 SourceNode 等一系列流程,最终到达 CommitStream() 环节中设置的回调函数 StreamBase::HandleResult() 的位置,以下是数据流转到Surface Buffer的过程。

void StreamBase::HandleResult(std::shared_ptr<IBuffer>& buffer){
	...
    request->AttachBuffer(buffer);
    request->OnResult(streamId_);
    ...
}

RetCode CaptureRequest::OnResult(const int32_t id){
    ...
    return stream->OnFrame(shared_from_this());
}

RetCode StreamBase::OnFrame(const std::shared_ptr<CaptureRequest>& request){
    ...
    ReceiveBuffer(buffer);
    ...
}

RetCode StreamBase::ReceiveBuffer(std::shared_ptr<IBuffer>& buffer){
    ...
    bufferPool_->ReturnBuffer(buffer);
    tunnel_->PutBuffer(buffer);
    ...
}

RetCode StreamTunnel::PutBuffer(const std::shared_ptr<IBuffer>& buffer){
    ...
    // 数据最终流入到 Surface 的Buffer中
    bufferQueue_->FlushBuffer(sb, fence, flushConfig_);
}

数据流入Surface 的BufferQueue中,最终在 FlushBuffer 函数中会调用最上层设置的 surface的数据监听 listener。

GSError BufferQueue::FlushBuffer(int32_t sequence, const BufferExtraData &bedata,
    int32_t fence, const BufferFlushConfig &config)
{
	...
    // 这个回调函数中可以获取BufferQueue中媒体数据并保存成文件
	listener_->OnBufferAvailable(); 
	...
}

// 下面是设置surface消费者回调函数的流程
GSError ConsumerSurface::RegisterConsumerListener(sptr<IBufferConsumerListener>& listener)
{
    return consumer_->RegisterConsumerListener(listener);
}

GSError BufferQueueConsumer::RegisterConsumerListener(sptr<IBufferConsumerListener>& listener)
{
    return bufferQueue_->RegisterConsumerListener(listener);
}

GSError BufferQueue::RegisterConsumerListener(sptr<IBufferConsumerListener> &listener)
{
    listener_ = listener;
    return GSERROR_OK;
}

总结

本文主要描述了OpenHarmony 多媒体子系统Camera组件从应用层到驱动层的架构,按照拍照的流程,分析了与之相关的两个仓库的主要流程的函数代码。

预览、录像与拍照的区别为前两者捕获的是连续的图像(按固定的帧率间隔,定时捕获1帧数据),拍照仅捕获一帧图像。

在相机框架层中,拍照使用的是HStreamCapture进行单次捕获图像,预览和录像使用HStreamRepeat类进行连续捕获。这两个类最终都是调用驱动层的StreamOperator::Capture()函数,此函数根据参数区分是单次捕获还是连续捕获。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-6-1 18:25:16修改
4
收藏 2
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢老师详细的讲解

回复
2022-6-1 18:35:15
码农小明
码农小明

不错不错,自己顶^_^

回复
2022-6-1 20:31:39
LoinDIci
LoinDIci

有camera设备驱动的skeleton驱动示例吗,如ISP、image sensor需要实现哪些接口,这部分接口的基本功能等

回复
2022-7-25 10:47:48
回复
    相关推荐