本文作者:江苏润和软件股份有限公司 王高浩
一、简介 媒体子系统旨在为多媒体应用开发者提供统一的开发接口,使得开发者可以专注于应用业务的开发,轻松使用多媒体的资源。下图分别展现媒体子系统的框架及业务流程。
如图1,多媒体框架支持相机、录像和播放业务功能,这些功能支持鸿蒙JS应用开发及各种使用媒体能力的KIT模块开发,系统框架包括framework层,framework对外提供应用调用的native接口及其对应的业务实现,针对相机、录像及播放业务,framework实现了音视频输入输出,音视频编解码,视频文件的打包及解复用等功能。core service层,core service利用平台提供的能力去实现对底层硬件及相关驱动使用,另外core server实现文件管理,存储管理及日志管理。
如图2,多媒体包括camera,recorder和player,camera提供YUV、RGB、JPEG以及H264,H265数据到共享内存surface中,recorder模块将surface中h264/h265数据和音频aac数据打包成mp4文件,player模块把mp4文件解复用成音频和视频数据,分别送入对应编码器解码,然后进行播放。
目录结构
表1 轻量级多媒体子系统源代码目录结构
名称描述foundation/multimedia/frameworks内部框架实现,包括audio,camera,player.recorderfoundation/multimedia/interfaces/kits应用接口对外头文件foundation/multimedia/services/media_lite应用接口底层服务实现foundation/multimedia/utils/lite应用接口通用模块实现foundation/multimedia/hals硬件平台相关媒体适配接口头文件使用 Native应用接口调用可以参考applications/sample/camera/media下demo实现。应用开发者使用多媒体接口实现录像、预览和播放音视频,使用可以参考《多媒体开发指南》。 本文解读媒体子系统里面的Camera子系统。
二、Camera子系统解读 1、Camera的各功能模块
模块(类)名称功能CameraAbility摄像头的能力相关,主要是摄像头所产生的图像的分辨率和帧率CameraConfigImpl摄像头配置功能,主要是设置帧状态回调CameraDevice实现RecordAssistant、PreviewAssistant、CaptureAssistant、CameraDevice四个类。主要是将前三个类的对象与摄像头设备相关联:Record设置 vencHdls_、vencSurfaces_、vencAttr_这3个属性、设置产生码流后的回调、启动和停止编码器;Preview则没有这3个属性,也没有回调;Capture有2个属性,但是没有回调,以阻塞方式获取图片。CameraDevice类则是把CameraAbility里的参数传入设置摄像头,并初始化成员变量prcessorHdls_和prcessorAttrs_。并在运行之前把摄像头的输出状态设置为以下三者之一:Record、Preview、Capture,并根据输出状态设置对应的assistant。CameraImpl实现一个类CameraImpl,主要的功能把CameraDevice对象和CameraConfig对象、FrameConfig对象、EventHandler对象、CameraStateCallback对象关联在一起。并且用这些对象对CameraDevice进行设置和操纵,并用EventHandler对象发送一些回调消息。CameraKit全局唯一变量,是整个架构的入口,同时里面隐含了cameraManager_变量。CameraManagerImpl全局唯一变量,该变量维护了一个空闲摄像头池,整个摄像头架构都围绕着对这些摄像头的操作展开。CameraService全局唯一变量,该变量维护着摄像头的ability和deviceCameraStateCallback该类设置和维护用户要摄像头执行的业务功能,比如record、preview、capture。该类用回调函数响应消息的方式来执行。该类为纯虚类,需要开发者自己派生新类来实现自己的业务逻辑。2、对Camera的例子程序的分析 applications\sample\camera\media\camera_sample.cpp是一个打开摄像头并且进行录像、预览、截图的例子,通过例子程序可以深入了解camera各模块的功能和大体流程。本文先对代码做尽可能的精简,再对剩余代码做简要的解释。
static int32_t SampleGetRecordFd()
{
//创建一个后缀为mp4的文件并返回文件句柄,用于record。
}
static void SampleSaveCapture(const char *p, uint32_t size)
{
//创建一个后缀为jpg的文件,作为capture文件,并把传入的内存p保存其中。
}
Recorder *SampleCreateRecorder()
{
//1、规定各种音视频编码参数
//2、Recorder *recorder = new Recorder();
//3、用这些音视频参数设置Recorder
//4、return recorder;
}
//处理capture的回调类
class SampleFrameStateCallback : public FrameStateCallback {
void OnFrameFinished(Camera &camera, FrameConfig &fc, FrameResult &result) override
{
if (fc.GetFrameConfigType() == FRAME_CONFIG_CAPTURE) {
//从fc里面获取surface,再从surface里面得到编码后的buffer,
//再调用SampleSaveCapture()把buffer存成jpg文件。
}
}
};
//该类可以处理相机状态变化的回调;同时该类还可以执行record、preview、capture操作。
//该类是本例子的重点
class SampleCameraStateMng : public CameraStateCallback {
public:
SampleCameraStateMng(EventHandler &eventHdlr) : eventHdlr_(eventHdlr) {}
void OnCreated(Camera &c) override
{
//设置cam_,也就是摄像头实例,在其里面带入了config,
//而config里面带入了fsCb_和eventHdlr,也就是帧状态改变回调和消息句柄
}
int PrepareRecorder()
{
recorder_ = SampleCreateRecorder(); //创建音视频编码器
recordFd_ = SampleGetRecordFd(); //创建mp4文件句柄
}
//执行record
void StartRecord()
{
int ret = PrepareRecorder();
ret = recorder_->SetOutputFile(recordFd_);
ret = recorder_->Prepare();
ret = recorder_->Start(); //建立输入输出通路
//创建并设置FrameConfig *fc,主要是把surface设置进去
ret = cam_->TriggerLoopingCapture(*fc); //触发record
}
void StartPreview()
{
//基本类似于StartRecord()
}
void Capture()
{
//类似于StartPreview(),不过只做一次
}
};
int main()
{
CameraKit *camKit = CameraKit::GetInstance(); //返回CameraKit实例
list camList = camKit->GetCameraIds(); //返回空闲的camId
string camId;
//找到第一个包含了1080p分辨率的ability,并让camId = 该摄像头的camId。
EventHandler eventHdlr; // Create a thread to handle callback events
SampleCameraStateMng CamStateMng(eventHdlr); //在该类中实现用户的业务逻辑
camKit->CreateCamera(camId, CamStateMng, eventHdlr); //创建摄像头,本例子的核心
char input;
while (cin >> input) {
switch (input) {
case '1':
CamStateMng.Capture(); //执行capture
break;
case '2':
CamStateMng.StartRecord(); //执行record
break;
case '3':
CamStateMng.StartPreview(); //执行preview
break;
case 's':
CamStateMng.Stop();
break;
case 'q':
CamStateMng.Stop();
goto EXIT;
default:
SampleHelp();
break;
}
}
EXIT:
cout CreateCamera(camId, CamStateMng, eventHdlr)。如上文所述,此时本例子的所有功能都已经实现,因此CreateCamera()的作用仅仅是把他们有序的整合在同一个camera对象中,使得camera对象可以正确的执行。下面对这部分代码做梳理。
void CameraKit::CreateCamera(const string &cameraId, CameraStateCallback &callback, EventHandler &handler)
{
cameraManager_->CreateCamera(cameraId, callback, handler);
}
如前所述,CameraKit类只是个包装,实际的功能都由cameraManager_来实现,具体实现如下。
void CreateCamera(const string &cameraId, CameraStateCallback &callback, EventHandler &handler) override
{
auto p = cameraMapCache_.find(cameraId);
p->second->RegistCb(callback, handler);
cameraService_->CreateCamera(cameraId);
}
其中,cameraMapCache_是空闲摄像头池,它的定义为map cameraMapCache_;,因此p->second是一个CameraImpl类的对象。
void CameraImpl::RegistCb(CameraStateCallback &callback, EventHandler &handler)
{
handler_ = &handler;
stateCb_ = &callback;
}
所以p->second->RegistCb(callback, handler);的作用就是把callback和handler存放在CameraImpl类的变量stateCb_和handler_中。
void CameraService::CreateCamera(string cameraId)
{
cameraServiceCb_->OnCameraStatusChange(cameraId, CameraServiceCallback::CAMERA_STATUS_CREATED, device_);
}
其中,device_是在CameraService::InitCameraDevices()中创建的,跟踪代码可以发现是在CameraKit::CameraKit()这个构造器中被创建的,所以可以认为是在整个架构的初始化中创建的,当调用OnCameraStatusChange时已经是有效的。
OnCameraStatusChange的实现如下:
void OnCameraStatusChange(string &cameraId, CameraStauts status, CameraDevice *device = nullptr) override
{
auto p = cameraMapCache_.find(cameraId);
switch (status) {
//。。。
case CAMERA_STATUS_CREATED:
if (p != cameraMapCache_.end()) {
p->second->OnCreate(device);
}
break;
}
}
如前所述,p是CameraManagerImpl类管理的空闲内存池里面的某个摄像头,因此p->second->OnCreate(device)的具体实现如下。
void CameraImpl::OnCreate(CameraDevice *device)
{
device_ = device;
handler_->Post([this] { this->stateCb_->OnCreated(*this); });
}
所以它的作用第一个是给代表这个摄像头的CameraImpl类的device_赋值。由于device是唯一的,所以这也暗示了鸿蒙目前只能处理一个摄像头。第二个是发OnCreated消息给stateCb_。如前所述,stateCb_就是CamStateMng,它的OnCreated()函数已经在前面解读过,不再赘述。不过要值得注意的是OnCreated()的参数是*this,也就是CamStateMng的cam_被赋值成该CameraImpl的对象。
此时,所有的设置都已完成,调用CamStateMng对象的各个方法就可以正确运行了。 
图4 Camera创建时序图
3、camera的各模块在camera的整个架构中的相互关系 我们可以看到,大体的架构是camKit或者cameraManager_在维护一个空闲摄像头池。每个空闲摄像头被一个二元组map 所代表,string是摄像头的ID,CameraImpl是摄像头的数据结构。而该CameraImpl处于核心地位,设置和操作摄像头所需的数据结构很多都在这个类里面,同时别的类也把这个类的对象作为成员变量,以方便访问。相互之间的关系图如下。 
图5 camKit和CameraImpl的关系
图5显示了camera架构是如何通过camKit访问到CameraImpl的。接下来我们看一下CameraImpl模块又是如何可以访问到整个架构的其他各个模块的数据,以及和其他各个模块的交互,见图6。

图6 各模块成员变量的相互关系
图6有2种箭头线,实线表示指针关系,箭尾所在的变量表示要被赋值的指针,箭头所在的变量表示内存地址;虚线箭头表示参数传递关系,箭尾所在的变量表示要被传递的参数,箭头所在表示被创建出来的对象所在的类。
可以看到相互之间的赋值关系是错综复杂的,不过还可以看出基本方式是先在别的类里面创建和初始化完毕他们的成员变量,然后再把这些成员变量直接和间接的赋值到CameraImpl类的成员变量中,这是本架构最重要的类,它代表了每个摄像头实体。次一级重要的是SampleCameraStateMng类,它代表了这个摄像头实体应该如何执行用户操作和响应消息。另外还有一个是CameraDevice类,他负责设置和执行和摄像头关联的编码器,本文暂不涉及。
4、鸿蒙camera子系统架构的特点 通过以上的解读和梳理,我们可以可看出鸿蒙camera子系统架构有如下特点:
4.1. 用户接口简单,只需要用户基于纯虚类CameraStateCallback派生一个新类CamStateMng,在里面实现用户自己的操作和回调消息的处理。然后再用这个新类创建摄像头,就可以实现对摄像头的操作。而且这个摄像头实例用户基本上无需关心,只需要访问CamStateMng类就可以实现所需操作。
4.2. 架构内部每个功能模块功能单一、模块分散、耦合度低。比如CameraManagerImpl类只负责维护空闲摄像头池:为每个摄像头创建实例、设置消息句柄和处理消息的回调类;CameraService类只负责将与摄像头音视频编码相关的device_和ability_与摄像头实例整合在一起;CameraConfigImpl只负责将发送消息的句柄和帧状态回调设置到device_里,用于捕获每帧的编码数据。。。这样做的好处是要维护的功能局限在每个细小的模块中,相互之间的影响较小,方便各自维护。
4.3. 采用c++来实现模块,封装和可扩展性良好。
4.4. 最后所有需要的模块都集中到摄像头实例类CameraImpl,这样如果需要做相对底层的操作的话,用户只要获取到摄像头实例,就可以访问到所有模块,因此给用户带来了极大的灵活性。
4.5. 目前开放的Camera API 只有基本的功能,没有考虑双摄,三摄协同工作的情况,PQ调试能力也需要完善,希望鸿蒙发展越来越好。