OpenHarmony视频录制流程介绍

媒体子系统为开发者提供了媒体相关的很多功能,本文针对其中的视频录制功能做个详细的介绍。
首先,我将通过媒体子系统提供的视频录制 test 代码作为切入点,给大家梳理一下整个录制的流程。
目录
foundation/multimedia/camera_framework├── frameworks│   ├── js│   │   └── camera_napi                            #napi实现│   │       └── src│   │           ├── input                          #camera输入│   │           ├── output                         #camera输出│   │           └── session                        #会话管理│   └── native                                     #native实现│       └── camera│           ├── build.gn│           ├── src│           │   ├── input                          #camera输入│           │   ├── output                         #camera输出│           │   └── session                        #会话管理├── interfaces                                     #接口定义│   ├── inner_api                                  #内部native实现│   │   └── native│   │       ├── camera│   │       │   └── include│   │       │       ├── input│   │       │       ├── output│   │       │       └── session│   └── kits                                       #napi接口│       └── js│           └── camera_napi│               ├── build.gn│               ├── include│               │   ├── input│               │   ├── output│               │   └── session│               └── @ohos.multimedia.camera.d.ts└── services                                       #服务端    └── camera_service        ├── binder        │   ├── base        │   ├── client                             #ipc的客户端        │   │   └── src        │   └── server                             #ipc的服务端        │       └── src        └── src   
录制的总体流程
    如下图:
native 接口使用
    在 openharmony 系统中,多媒体子系统通过 n-api 接口提供给上层 js 调用,n-api 相当于是 js 和 native 之间的桥梁。 在 openharmony 源码中,提供了 c++ 直接调用视频录制功能的例子,foundation/multimedia/camera_framework/interfaces/inner_api/native/test 目录中。
本文章主要参考了 camera_video.cpp 文件中的视频录制流程。
首先根据 camera_video.cpp 的 main 方法,了解下视频录制的主要流程代码:
int main(int argc, char **argv){    ......    // 创建cameramanager实例    sptr cammanagerobj = cameramanager::getinstance();    // 设置回调    cammanagerobj->setcallback(std::make_shared(testname));    // 获取支持的相机设备列表    std::vector cameraobjlist = cammanagerobj->getsupportedcameras();    // 创建采集会话    sptr capturesession = cammanagerobj->createcapturesession();    // 开始配置采集会话    capturesession->beginconfig();    // 创建camerainput    sptr captureinput = cammanagerobj->createcamerainput(cameraobjlist[0]);    sptr camerainput = (sptr &)captureinput;    // 开启camerainput    camerainput->open();    // 设置camerainput的error回调    camerainput->seterrorcallback(std::make_shared(testname));    // 添加camerainput实例到采集会话中    ret = capturesession->addinput(camerainput);    sptr videosurface = nullptr;    std::shared_ptr recorder = nullptr;    // 创建video的surface    videosurface = surface::createsurfaceasconsumer();    sptr videolistener = new surfacelistener(video, surfacetype::video, g_videofd, videosurface);    // 注册surface的事件监听    videosurface->registerconsumerlistener((sptr &)videolistener);    // 视频的配置    videoprofile videoprofile = videoprofile(static_cast(videoformat), videosize, videoframerates);    // 创建videooutput实例    sptr videooutput = cammanagerobj->createvideooutput(videoprofile, videosurface);    // 设置videooutput的回调    ((sptr &)videooutput)->setcallback(std::make_shared(testname));    // 添加videooutput到采集会话中    ret = capturesession->addoutput(videooutput);    // 提交会话配置    ret = capturesession->commitconfig();    // 开始录制    ret = ((sptr &)videooutput)->start();    sleep(videopauseduration);    media_debug_log(resume video recording);    // 暂停录制    ret = ((sptr &)videooutput)->resume();    media_debug_log(wait for 5 seconds before stop);    sleep(videocaptureduration);    media_debug_log(stop video recording);    // 停止录制    ret = ((sptr &)videooutput)->stop();    media_debug_log(closing the session);    // 停止采集会话    ret = capturesession->stop();    media_debug_log(releasing the session);    // 释放会话采集    capturesession->release();    // close video file    testutils::savevideofile(nullptr, 0, videosavemode::close, g_videofd);    camerainput->release();    cammanagerobj->setcallback(nullptr);    return 0;}  
以上是视频录制的整体流程,其过程主要通过 camera 模块支持的能力来实现,其中涉及几个重要的类:capturesession、camerainput、videooutput。
capturesession 是整个过程的控制者,camerainput 和 videooutput 相当于是设备的输入和输出。
调用流程
如下图:
后续主要针对上面的调用流程,梳理具体的调用流程,方便我们对了解视频录制的整理架构有一个更加深入的了解。
①创建 cameramanager 实例
通过 cameramanager::getinstance() 获取 cameramanager 的实例,后续的一些接口都是通过该实例进行调用的。
getinstance 使用了单例模式,在 openharmony 代码中这种方式很常见。
sptr &cameramanager::getinstance(){    if (cameramanager::cameramanager_ == nullptr) {        media_info_log(initializing camera manager for first time!);        cameramanager::cameramanager_ = new(std::nothrow) cameramanager();        if (cameramanager::cameramanager_ == nullptr) {            media_err_log(cameramanager::getinstance failed to new cameramanager);        }    }    return cameramanager::cameramanager_;}  
②获取支持的相机设备列表
通过调用 cameramanager 的 getsupportedcameras() 接口,获取设备支持的 cameradevice 列表。
跟踪代码可以发现 serviceproxy_->getcameras 最终会调用到 camera 服务端的对应接口。
std::vector cameramanager::getsupportedcameras(){    camera_sync_trace;    std::lock_guard lock(mutex_);    std::vector cameraids;    std::vector cameraabilitylist;    int32_t retcode = -1;    sptr cameraobj = nullptr;    int32_t index = 0;    if (cameraobjlist.size() > 0) {        cameraobjlist.clear();    }    if (serviceproxy_ == nullptr) {        media_err_log(cameramanager::getcameras serviceproxy_ is null, returning empty list!);        return cameraobjlist;    }    std::vector supportedcameras;    retcode = serviceproxy_->getcameras(cameraids, cameraabilitylist);    if (retcode == camera_ok) {        for (auto& it : cameraids) {            cameraobj = new(std::nothrow) cameradevice(it, cameraabilitylist[index++]);            if (cameraobj == nullptr) {                media_err_log(cameramanager::getcameras new cameradevice failed for id={public}%s, it.c_str());                continue;            }            supportedcameras.emplace_back(cameraobj);        }    } else {        media_err_log(cameramanager::getcameras failed!, retcode: %{public}d, retcode);    }    choosedefaultcameras(supportedcameras);    return cameraobjlist;}③创建采集会话
下面是比较重要的环节,通过调用 cameramanager 的 createcapturesession 接口创建采集会话。 cameramanager 创建采集会话,是通过 serviceproxy_->createcapturesession 方式进行调用。  
这里涉及到了 openharmony 中的 ipc 的调用,serviceproxy_ 是远端服务在本地的代理,通过这个代理可以调用到具体的服务端,这里是 hcameraservice。
sptr cameramanager::createcapturesession(){    camera_sync_trace;    sptr capturesession = nullptr;    sptr result = nullptr;    int32_t retcode = camera_ok;    if (serviceproxy_ == nullptr) {        media_err_log(cameramanager::createcapturesession serviceproxy_ is null);        return nullptr;    }    retcode = serviceproxy_->createcapturesession(capturesession);    if (retcode == camera_ok && capturesession != nullptr) {        result = new(std::nothrow) capturesession(capturesession);        if (result == nullptr) {            media_err_log(failed to new capturesession);        }    } else {        media_err_log(failed to get capture session object from hcamera service!, %{public}d, retcode);    }    return result;}代码最终来到 hcameraservice::createcapturesession 中,该方法中 new 了一个 hcapturesession 对象,并且将该对象传递给了参数 session。  
所以前面的 capturesession 对象就是这里 new 出来的 hcapturesession,前面的 cameramanager 的 createcapturesession() 方法中将 capturesession 封装成 capturesession 对象返回给应用层使用。
int32_t hcameraservice::createcapturesession(sptr &session){    camera_sync_trace;    sptr capturesession;    if (streamoperatorcallback_ == nullptr) {        streamoperatorcallback_ = new(std::nothrow) streamoperatorcallback();        if (streamoperatorcallback_ == nullptr) {            media_err_log(hcameraservice::createcapturesession streamoperatorcallback_ allocation failed);            return camera_alloc_error;        }    }    std::lock_guard lock(mutex_);    ohos::accesstokenid callertoken = ipcskeleton::getcallingtokenid();    capturesession = new(std::nothrow) hcapturesession(camerahostmanager_, streamoperatorcallback_, callertoken);    if (capturesession == nullptr) {        media_err_log(hcameraservice::createcapturesession hcapturesession allocation failed);        return camera_alloc_error;    }    session = capturesession;    return camera_ok;}  
④开始配置采集会话
调用 capturesession 的 beginconfig 进行采集会话的配置工作。这个工作最终调用到被封装的 hcapturesession 中。
int32_t hcapturesession::beginconfig(){    camera_sync_trace;    if (curstate_ == capturesessionstate::session_config_inprogress) {        media_err_log(hcapturesession::beginconfig already in config inprogress state!);        return camera_invalid_state;    }    std::lock_guard lock(sessionlock_);    prevstate_ = curstate_;    curstate_ = capturesessionstate::session_config_inprogress;    tempcameradevices_.clear();    tempstreams_.clear();    deletedstreamids_.clear();    return camera_ok;}  
⑤创建 camerainput
应用层通过 cammanagerobj->createcamerainput(cameraobjlist[0]) 的方式进行 camerainput 的创建,cameraobjlist[0] 就是前面获取支持设备的第一个。根据 cameradevice 创建对应的 camerainput 对象。
sptr cameramanager::createcamerainput(sptr &camera){    camera_sync_trace;    sptr camerainput = nullptr;    sptr deviceobj = nullptr;    if (camera != nullptr) {        deviceobj = createcameradevice(camera->getid());        if (deviceobj != nullptr) {            camerainput = new(std::nothrow) camerainput(deviceobj, camera);            if (camerainput == nullptr) {                media_err_log(failed to new camerainput returning null in createcamerainput);                return camerainput;            }        } else {            media_err_log(returning null in createcamerainput);        }    } else {        media_err_log(cameramanager: camera object is null);    }    return camerainput;}⑥开启 camerainput  
调用了 camerainput 的 open 方法,进行输入设备的启动打开。
void camerainput::open(){    int32_t retcode = deviceobj_->open();    if (retcode != camera_ok) {        media_err_log(failed to open camera input, retcode: %{public}d, retcode);    }}  
⑦添加 camerainput 实例到采集会话中
通过调用 capturesession 的 addinput 方法,将创建的 camerainput 对象添加到采集会话的输入中,这样采集会话就知道采集输入的设备。
int32_t capturesession::addinput(sptr &input){    camera_sync_trace;    if (input == nullptr) {        media_err_log(capturesession::addinput input is null);        return camera_invalid_arg;    }    input->setsession(this);    inputdevice_ = input;    return capturesession_->addinput(((sptr &)input)->getcameradevice());}  
最终调用到 hcapturesession 的 addinput 方法,该方法中核心的代码是 tempcameradevices_.emplace_back(localcameradevice),将需要添加的 cameradevice 插入到 tempcameradevices_ 容器中。
int32_t hcapturesession::addinput(sptr cameradevice){    camera_sync_trace;    sptr localcameradevice = nullptr;    if (cameradevice == nullptr) {        media_err_log(hcapturesession::addinput cameradevice is null);        return camera_invalid_arg;    }    if (curstate_ != capturesessionstate::session_config_inprogress) {        media_err_log(hcapturesession::addinput need to call beginconfig before adding input);        return camera_invalid_state;    }    if (!tempcameradevices_.empty() || (cameradevice_ != nullptr && !cameradevice_->isreleasecameradevice())) {        media_err_log(hcapturesession::addinput only one input is supported);        return camera_invalid_session_cfg;    }    localcameradevice = static_cast(cameradevice.getrefptr());    if (cameradevice_ == localcameradevice) {        cameradevice_->setreleasecameradevice(false);    } else {        tempcameradevices_.emplace_back(localcameradevice);        camera_sysevent_statistic(createmsg(capturesession::addinput));    }    sptr streamoperator;    int32_t rc = localcameradevice->getstreamoperator(streamoperatorcallback_, streamoperator);    if (rc != camera_ok) {        media_err_log(hcapturesession::getcameradevice getstreamoperator returned %{public}d, rc);        localcameradevice->close();        return rc;    }    return camera_ok;}⑧创建 video 的 surface  
通过 surface::createsurfaceasconsumer 创建 surface。
sptr surface::createsurfaceasconsumer(std::string name, bool isshared){    sptr surf = new consumersurface(name, isshared);    gserror ret = surf->init();    if (ret != gserror_ok) {        bloge(failure, reason: consumer surf init failed);        return nullptr;    }    return surf;}⑨创建 videooutput 实例  
通过调用 cameramanager 的 createvideooutput 来创建 videooutput 实例。
sptr cameramanager::createvideooutput(videoprofile &profile, sptr &surface){    camera_sync_trace;    sptr streamrepeat = nullptr;    sptr result = nullptr;    int32_t retcode = camera_ok;    camera_format_t metaformat;    metaformat = getcamerametadataformat(profile.getcameraformat());    retcode = serviceproxy_->createvideooutput(surface->getproducer(), metaformat,                                               profile.getsize().width, profile.getsize().height, streamrepeat);    if (retcode == camera_ok) {        result = new(std::nothrow) videooutput(streamrepeat);        if (result == nullptr) {            media_err_log(failed to new videooutput);        } else {            std::vector videoframerates = profile.getframerates();            if (videoframerates.size() >= 2) { // vaild frame rate range length is 2                result->setframeraterange(videoframerates[0], videoframerates[1]);            }            powermgr_sysevent_camera_config(video,                                            profile.getsize().width,                                            profile.getsize().height);        }    } else {        media_err_log(videooutpout: failed to get stream repeat object from hcamera service! %{public}d, retcode);    }    return result;}  
该方法中通过 ipc 的调用最终调用到了 hcameraservice 的:
createvideooutput(surface->getproducer(), format, streamrepeat)int32_t hcameraservice::createvideooutput(const sptr &producer, int32_t format,                                          int32_t width, int32_t height,                                          sptr &videooutput){    camera_sync_trace;    sptr streamrepeatvideo;    if ((producer == nullptr) || (width == 0) || (height == 0)) {        media_err_log(hcameraservice::createvideooutput producer is null);        return camera_invalid_arg;    }    streamrepeatvideo = new(std::nothrow) hstreamrepeat(producer, format, width, height, true);    if (streamrepeatvideo == nullptr) {        media_err_log(hcameraservice::createvideooutput hstreamrepeat allocation failed);        return camera_alloc_error;    }    powermgr_sysevent_camera_config(video, producer->getdefaultwidth(),                                    producer->getdefaultheight());    videooutput = streamrepeatvideo;    return camera_ok;}  
hcameraservice 的 createvideooutput 方法中主要创建了 hstreamrepeat,并且通过参数传递给前面的 cameramanager 使用,cameramanager 通过传递的 hstreamrepeat 对象,进行封装,创建出 videooutput 对象。
⑩添加 videooutput 到采集会话中,并且提交采集会话 该步骤类似添加 camerainput 到采集会话的过程,可以参考前面的流程。 ⑪开始录制
通过调用 videooutput 的 start 进行录制的操作。
int32_t videooutput::start(){    return static_cast(getstream().getrefptr())->start();}  
该方法中会调用到 hstreamrepeat 的 start 方法。
int32_t hstreamrepeat::start(){    camera_sync_trace;    if (streamoperator_ == nullptr) {        return camera_invalid_state;    }    if (curcaptureid_ != 0) {        media_err_log(hstreamrepeat::start, already started with captureid: %{public}d, curcaptureid_);        return camera_invalid_state;    }    int32_t ret = allocatecaptureid(curcaptureid_);    if (ret != camera_ok) {        media_err_log(hstreamrepeat::start failed to allocate a captureid);        return ret;    }    std::vector ability;    ohos::convertmetadatatovec(cameraability_, ability);    captureinfo captureinfo;    captureinfo.streamids_ = {streamid_};    captureinfo.capturesetting_ = ability;    captureinfo.enableshuttercallback_ = false;    media_info_log(hstreamrepeat::start starting with capture id: %{public}d, curcaptureid_);    camretcode rc = (camretcode)(streamoperator_->capture(curcaptureid_, captureinfo, true));    if (rc != hdi::no_error) {        releasecaptureid(curcaptureid_);        curcaptureid_ = 0;        media_err_log(hstreamrepeat::start failed with error code:%{public}d, rc);        ret = hditoserviceerror(rc);    }    return ret;}核心的代码是 streamoperator_->capture,其中最后一个参数 true,表示采集连续数据。 ⑫录制结束,保存录制文件    
总结
    本文主要对 openharmony 3.2 beta 多媒体子系统的视频录制进行介绍,首先梳理了整体的录制流程,然后对录制过程中的主要步骤进行了详细地分析。
视频录制主要分为以下几个步骤:
获取 cameramanager 实例。
创建采集会话 capturesession。
创建 camerainput 实例,并且将输入设备添加到 capturesession 中。
创建 video 录制需要的 surface。
创建 videooutput 实例,并且将输出添加到 capturesession 中。
提交采集会话的配置。
调用 videooutput 的 start 方法,进行视频的录制。
录制结束,保存录制的文件。
作者:巴延兴


2020年生物识别设备市场预计将达到282亿美元,设备出货量大幅下滑
传感器和处理器融合用于工业应用
X、B摆线针轮减速机使用及润滑知识
如何使用松下 EasyIP Setup Tool Plus软件
双变流器补偿式UPS控制研究
OpenHarmony视频录制流程介绍
S7-200 PLC的数据区
北工大校友Cheng Zhang获SIGGRAPH最佳博士论文奖!
Club Vivado 的用户们, 你在哪里?
华为nova8系列、畅享20SE部分参数曝光
2020年第四季度,苹果超越三星成为全球最大智能手机厂商
小米游戏键盘高清图赏
[图文]实验直流日光灯电路
网思科技与中国移动共推工业智能化
2012 Intel ISEF中国学生勇夺22个奖项
WT2003HX MP3语音芯片概述及功能特点
大数据到来存储需求持续增长,企业需要了解的云存储“真相”
全球公司市值榜单公布 阿里巴巴位于第七并居亚洲科技公司第一
微电网的工作原理、特点及用途
演示视频-通过Matter over Thread无缝串连Zigbee和Z-Wave网络