媒体子系统为开发者提供了媒体相关的很多功能,本文针对其中的视频录制功能做个详细的介绍。
首先,我将通过媒体子系统提供的视频录制 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网络