鸿蒙媒体子系统解读-编码录像流程解读

本文作者:江苏润和软件股份有限公司 王高浩

一、鸿蒙媒体子系统简介
媒体子系统旨在为多媒体应用开发者开发者提供统一的开发接口,使得开发者可以专注于应用业务的开发,轻松使用多媒体的资源。下图分别展现媒体子系统的框架及业务流程。

图 1 媒体子系统框架图
如图1,多媒体框架支持相机、录像和播放业务功能,这些功能支持鸿蒙JS应用开发及各种使用媒体能力的KIT模块开发,系统框架包括framework层,framework对外提供应用调用的native接口及其对应的业务实现,针对相机、录像及播放业务,framework实现了音视频输入输出,音视频编解码,视频文件的打包及解复用等功能。core service层,core service利用平台提供的能力去实现对底层硬件及相关驱动使用,另外core server实现文件管理,存储管理及日志管理。

图 2 多媒体业务流程图

如图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,Recorder
foundation/multimedia/interfaces/kits 应用接口对外头文件
foundation/multimedia/services/media_lite 应用接口底层服务实现
foundation/multimedia/utils/lite 应用接口通用模块实现
foundation/multimedia/hals 硬件平台相关媒体适配接口头文件

使用
Native应用接口调用可以参考applications/sample/camera/media下demo实现。应用开发者使用多媒体接口实现录像、预览和播放音视频,使用可以参考《多媒体开发指南》。
本文解读媒体子系统里面的编码录像流程。

二、编码录像流程涉及的各模块

1、录像部分(Recorder子系统)的各功能模块

模块(类)名称 功能
Recorder 用于实现recorder功能的对外接口类,针对录像的各个方面做设置:设置源、音视频编码格式、视频尺寸、视频帧率、视频码率、音频通道数、最大录像时长、录像文件格式。。。以及一些操作:准备、开始、停止、暂停、继续、重置、释放。
RecorderImpl Recorder类的具体实现。该类有两个成员变量:sourceManager_和recorderSink_,前者负责音视频的码流的获取,后者负责录像文件的写入。对Recorder类的所有设置和操作其实都由这两个变量实现。
RecorderSink 与写录像文件功能相关。设置文件句柄、最大时长、最大字节数、创建复用器(muxer)、增加track、写文件、设置文件格式、用回调处理一般性消息和错误消息等。
RecorderVideoSource 基本上的功能就是设置buffer和获取视频码流
RecorderAudioSource 基本上的功能就是设置创建音频源和音频编码器,以及读取音频流。

2、视频编码部分的各功能模块

模块(类)名称 功能
CameraDevice 作用把CameraAbility(帧率和分辨率)里的参数传入设置摄像头,并初始化成员变量prcessorHdls_(处理器句柄)和prcessorAttrs_(处理能力属性)。并在运行之前把摄像头的输出状态设置为以下三者之一:Record、Preview、Capture,并根据输出状态设置对应的assistant。以及执行获取码流。
RecordAssistant CameraDevice类所拥有的用于录像的类,它的功能有设置视频编码器句柄、设置视频码流缓冲区、设置响应码流生成完毕消息的回调函数、以及开始执行编码。

需要注意的是音频的采集和编码功能是在Recorder子系统独立完成的,而视频的采集编码功能在CameraDevice和RecordAssistant模块中完成。

三、编码录像流程代码解读

applications\sample\camera\media\camera_sample.cpp是一个打开摄像头并且进行录像、预览、截图的例子,通过例子程序可以深入了解媒体子系统的编码录像流程。下文先对代码做尽可能的精简,再对剩余代码做简要的解释。

static int32_t SampleGetRecordFd()
{//创建一个后缀为mp4的文件并返回文件句柄,用于保存录像文件。
}
Recorder *SampleCreateRecorder()
{//1、规定各种音视频编码参数
//2、Recorder *recorder = new Recorder();//3、用这些音视频参数设置recorder//4、return recorder;
}
//该类可以处理相机状态变化的回调;同时该类还可以执行record、preview、capture操作。
class SampleCameraStateMng : public CameraStateCallback {public:int PrepareRecorder(){recorder_ = SampleCreateRecorder();        //创建Recorder对象
recordFd_ = SampleGetRecordFd();       //创建录像文件句柄}
void StartRecord()      //执行Record{int ret = PrepareRecorder();            //创建Recoderret = recorder_->SetOutputFile(recordFd_);ret = recorder_->Prepare();            //设置Muxerret = recorder_->Start();              //等待audioSource/videoSource里面的码流数据,写入Muxer//创建并设置FrameConfig *fc,主要是把surface设置进去ret = cam_->TriggerLoopingCapture(*fc);   //触发录像}
};
int main()
{char input;while (cin >> input) {switch (input) {case '2':CamStateMng.StartRecord();       //执行recordbreak; default:SampleHelp();break;}}return 0;
}

关于Camera架构的部分已经在之前的文章中分析过了,所以现在只考虑在Camera的框架下,编码录像流程是如何工作的。由以上的代码可知,当用户输入‘2’的时候,会执行SampleCameraStateMng类的StartRecord函数,此时就会开始录像。因此从这个函数开始分析。

图3 StartRecord函数

StartRecord函数的核心就是图3的5个函数,接下来逐一分析。

3.1、PrepareRecorder函数
StartRecord函数首先会执行PrepareRecorder函数,它的功能有两个,第一个是创建和设置Recorder对象,第二个是创建用于保存录像文件的文件句柄。第二个非常简单,因此我们只分析第一个。第一个功能的代码如下图。

图4 创建和设置Recorder对象 可以看到该代码分为2个部分,第一个部分是红框前的代码,是为了满足业务需要而设置的各种音视频采集和编码参数;第二个部分是红框内的代码,创建一个Recorder类的对象,并用之前确定的各种参数来设置这个对象。至此recorder_对象创建和设置完成。不过recorder_对象只是一个包装,真正被设置的是包含在里面的sourceManager_变量,以及sourceManager_里面的videoSource变量和audioSource变量。sourceManager_的数据结构如下图所示。

图5 sourceManager_变量的数据结构

可以看到sourceManager_变量的核心部分是videoSource和audioSource,sourceManager_变量的功能就是维护这两个音视频的编码码流。

3.2、recorder_->SetOutputFile(recordFd_)函数
有了recorder_对象后,该函数将录像文件句柄recordFd_赋值给recorder_里的recorderSink_变量的outputFd_变量。留作备用。

3.3、recorder_->Prepare()函数
如前所述,Recorder类由RecorderImpl类具体实现,而RecorderImpl类又由内部的sourceManager_和recorderSink_变量来实现具体的设置和操作。其中sourceManager_又分为video和audio两部分。因此整个Prepare函数也就分为recorderSink_、video、audio这3个部分,分别做prepare,如下图所示。

图6 Prepare函数

第一个红框处的PrepareRecorderSink由recorderSink_->Prepare()来实现,它的作用就是将收集到的写录像文件句柄、录像文件最大时长、录像文件最大文件容量设置到recorderSink_的成员变量formatMuxerHandle_中,也就是创建了一个用于写录像文件的音视频复用器(Muxer)。
第二个红框处的PrepareVideoSource函数的功能是读出sourceManager_变量里的videoSource(视频源)的一系列设置:编码格式、图像宽高、编码带宽、帧率、关键帧间隔,并把这些设置加入formatMuxerHandle_中,作为要写入的录像文件的一个视频track。
第三个红框处的PrepareAudioSource函数的功能与PrepareVideoSource函数的功能大体类似,先利用audioSource的一系列设置做音频初始化,分为音频源初始化和音频编码器初始化。然后再把这些设置加入formatMuxerHandle_中,作为要写入的录像文件的一个音频track。

3.4、recorder_->Start()函数
与前面的函数类似,该Start函数同样分成三个部分:recorderSink、videoSource、audioSource分别做start,如下图所示。

图7 Start函数

第一个红框的recorderSink_->Start();比较简单,功能是1、设置Muxer(音视频复用器)的一般性消息和错误消息的回调。2、启动该Muxer,执行写录像文件的操作。
第二个红框的StartVideoSource();函数,作用是启动了一个新的线程,该线程执行VideoSourceProcess函数,该函数如下图所示。

图8 VideoSourceProcess函数

这个函数的功能是,它在一个线程中,不停的用第一个红框里的videoSource来获取视频码流,再用第二个红框里的recorderSink把码流写入Muxer(音视频复用器)里,也就是写到录像文件中。第二个红框的内容比较清晰,所以我们只分析第一个红框里的内容,也就是AcquireBuffer函数的功能。该函数代码如下图所示。

图9 AcquireBuffer函数

可以看出AcquireBuffer函数从RecorderVideoSource的surface_里面获取码流的起始地址和长度,然后把它们设置到buffer里面。那么surface_又是从哪里得到码流的呢?但是RecorderVideoSource类里面并没有明确的和编码器绑定的部分,这是需要弄清楚的。
第三个红框的StartAudioSource();函数,也是同样的,启动了一个新的线程,该线程执行AudioSourceProcess函数。该函数的功能也类似于VideoSourceProcess函数,它在一个线程中不停的用audioSource来获取音频码流,再用recorderSink把码流写入Muxer(音视频复用器)里,也就是写到录像文件中。然而不同之处在于audioSource包含自己的拾音器、编码器、缓冲区,整过过程是完整的,无需像videoSource那样从外界获取码流。

至此,录像部分的流程(Recorder子系统)就开始工作了。总结一下该流程:
a、先创建一个Recorder类的对象,它统领整个Recorder架构,并用满足业务需要的音视频采集和编码参数去设置它。
b、设置好该Recorder对象的Muxer(音视频复用器),并用上面设置好的音视频采集和编码参数创建和设置音视频2条track,再把这2条track加入到这个Muxer里面。并且还要设置好音频的采样源和编码器,但是无需对视频部分做设置。
c、最后启动这个Recorder对象,让它里面的audioSource(音频源)和videoSource(视频源)在2个单独的线程里面不断的获取编码数据,并将数据写入Muxer,最终写入录像文件。

虽然这个流程看上去是成立的,但是还不够完整。只有音频部分是完整的,视频部分没有设置和启动编码器,也没有告诉我们videoSource的surface_是如何初始化的,也没有告诉我们surface_是从哪里获取到视频码流的,以及Recorder架构和Camera架构是如何关联的。这些问题的答案在图3的第5个红框里面,也就是cam_->TriggerLoopingCapture(*fc)函数。

3.5、cam_->TriggerLoopingCapture(*fc)函数
重新贴一下图3的代码,再增加一个红框,观察一下fc变量是如何创建和初始化的。如下图所示。

图10 与编码和获取码流相关代码

图10的第一个红框里的recorder_->GetSurface(0)才真正的创建了recorder_里的surface_,接着surface_被设置了分辨率(1920*1080)等参数后,又被放置到fc中,而第二个红框则表明fc又被CameraImpl类的cam_对象使用。因此,Recorder架构和Camera架构发生关联,既Camera架构将Recorder架构所拥有的surface_对象作为自己的码流缓冲区,产生的码流放入这个缓冲区;而Recorder架构从这个缓冲区读取视频码流,写入录像文件。分析图10的第二个红框的代码可以得到更多的架构细节。

图11 CameraImpl类和CameraDevice类的TriggerLoopingCapture函数

图11的红色箭头表明cam_->TriggerLoopingCapture(*fc)函数是由device_对象的TriggerLoopingCapture(FrameConfig &fc)函数具体实现的。第一个红框的意思是如果fc被设置为FRAME_CONFIG_RECORD(录像)模式,那就会使用recordAssistant_对象来完成TriggerLoopingCapture函数。
第二个红框则是本函数的核心,它是将Recorder架构和Camera架构连接在一起的关键。它的代码如图12。

图12 SetFrameConfig函数

图12第一个红框的意思是根据我们在fc的surface_中设置的分辨率,在可用的分辨率组合attrs中找到最接近的那个,并以此来决定需要用到的那个处理器的Id(ProcessorIdx),最终得到处理器的设备Id(deviceId)。
第二个红框的意思是根据fc(分辨率和输出码流缓冲区)、attrs(最接近的帧率和分辨率)、deviceId(处理器的设备Id)来生成一个编码器句柄codecHdl。
第三个红框的意思是将回调函数列表recordCodecCb_和编码器句柄codecHdl绑定在一起。recordCodecCb_这个回调函数列表里面只有RecordAssistant::OnVencBufferAvailble这个函数是有效的,它的作用是当编码器句柄codecHdl有了码流以后,就把这段码流拷贝到codecHdl对应的缓冲区vencSurfaces_。
第四个红框的意思则表示所谓的vencSurfaces_里面包含的就是surface_。这样Recorder架构和Camera架构就连接在了一起。

回到图11的第三个红框,它最终调用了CodecStart(vencHdls_[i]);,意思就是启动编码器开始编码。至此,图11的TriggerLoopingCapture函数完成,Camera架构开始采集和编码,并且编码数据会被放到Recorder架构的surface_里面。同时,图10的StartRecord函数也一并完成,Recorder架构也开始工作。于是整个编码录像流程在两个架构的合作下就开始工作了。

四、整个编码录像流程总结

通过上文的分析,我们已经知道了整个编码录像的大体细节,但是不太连贯,因此现在我们来总结一下整个流程。
4.1、Camera架构初始化完成,cam_、device_、recordAssistant_这些视频编码相关对象已经创建完毕。
4.2、device_执行Initialize函数,用系统提供的各种可用的帧率+分辨率的组合来初始化device_里的prcessorAttrs_和prcessorHdls_,前者表示了这个组合对处理器算力的要求,后者表示为了完成这个任务需要分配什么样的处理器。
4.3、这时用户输入‘2’,执行SampleCameraStateMng类的StartRecord函数。该函数涉及Camera架构的部分会设定device_的工作模式为FRAME_CONFIG_RECORD,也就是录像模式。并且设定把Recorder架构的surface_变量传入device_。
4.4、根据在4.2初始化的prcessorAttrs_和prcessorHdls_,以及在4.3中设置的fc,创建处理器设备句柄deviceId和编码器句柄vencHdls_。并用回调函数规定当vencHdls_产生码流时,surface_变量接收该码流。
4.5、与此同时,4.3会创建和初始化Recorder类的对象recorder,它代表整个Recorder架构。然后设置recorder里面的Muxer(音视频复用器)、audioSource(音频采集、编码、音频码流获取和传送)、videoSource(视频码流获取和传送)。
4.6、启动recorder,于是audioSource和videoSource会在两个单独的线程里面等待各自的码流,然后写入Muxer,最终保存为文件。
4.7、启动编码器,产生码流。

编码录像流程的各模块各变量之间的关系如下图所示。

图13 编码录像流程的各模块各变量之间的关系

图13表示了各模块各变量之间的关系,其中2个紫色的粗线箭头表示中间的surface被左边的surface_赋值,而右边的vencSurfaces_又被中间的surface赋值;橙色的细线箭头表示变量之间的隶属关系,箭头所在变量隶属于箭尾所在变量。

鸿蒙媒体子系统解读-编码录像流程解读相关推荐

  1. 转录组分析_肠道菌群:宏转录组测序分析流程解读

    上回给大家讲述了16S测序分析 和 宏基因组测序分析,本期的宏转录组来啦~ 你知道吗?通过16S测序分析 和 宏基因组测序分析,我们只能够知道肠道菌群做好事或坏事的潜力,而并不知道它们此时此刻正在我们 ...

  2. Misra-C编码规范全解读 - 总目录

    总目录 欢迎大家来到雪云飞星的<Misra-C 2012编码规范全解读>.Misra C 作为汽车行业乃至嵌入式行业的著名编码规范,被众多的厂商采用并遵守.其能有效的拦截潜在的编码风险,帮 ...

  3. 百度地图安卓版详细接入流程解读(获取密钥详解)

    百度地图安卓版详细接入流程解读 一.接入Android地图SDK 1.1 功能介绍: 1.2 接入百度地图前的准备 1.1.1 访问官网,并下载开发包 1.1.2 获取开发密钥 1.1.3 项目环境搭 ...

  4. 字节程序媛:大厂技术岗求职流程解读经验分享,这是一份保姆级校招攻略

    文章目录 写在前面 流程解读 简历投递 笔试(仅校招) 面试 发Offer 写在最后 写在前面 阳春三月,春暖花开.更重要的是- 一年一度的春招季他来啦!作为校招的两大关键节点之一,春招是应届生去争取 ...

  5. 亚马逊无货源店群,运作流程解读!

    亚马逊无货源店群,运作流程解读! 很多朋友想做亚马逊跨境电商,但是对整体的运作流程还不太清楚,那么我们今天就从前期的准备到后期的发货和收款,做一个全流程解析! 我们先来梳理一下亚马逊无货源模式的运作流 ...

  6. r语言 转录本结构及丰度_肠道菌群:宏转录组测序分析流程解读

    上回给大家讲述了16S测序分析 和 宏基因组测序分析,本期的宏转录组来啦~ 你知道吗?通过16S测序分析 和 宏基因组测序分析,我们只能够知道肠道菌群做好事或坏事的潜力,而并不知道它们此时此刻正在我们 ...

  7. 【Android 10 源码】MediaRecorder 录像流程:MediaRecorder 配置

    MediaRecorder 录像配置主要涉及输出文件路径.音频来源.视频来源.输出格式.音频编码格式.视频编码格式.比特率.帧率和视频尺寸等. 我们假设视频输入源来自 Camera,Camera2 A ...

  8. 百度媒体云智能编码技术实践

    随着视频行业的蓬勃发展,提升视频质量,降低带宽成本成为各平台的首要挑战目标.本文来自百度云资深工程师邢怀飞在LiveVideoStackCon 2018大会的精彩分享.在分享中其对百度云智能编码技术进 ...

  9. 鸿蒙系统新手教程,鸿蒙灭神决新手入门全流程图文攻略

    鸿蒙灭神决新手入门全流程图文攻略 2019-03-21 15:04:13来源:天天RPG编辑:野狐禅评论(0) 中后期 回归主题,如果还是打不过神器2,可以先到"中级挑战"这里完成 ...

  10. fasterrcnn论文_【论文解读】Yolo三部曲解读——Yolov1

    打个广告,复现Yolov3之后的深度原理剖析请移步下文(含代码): [算法实验]能检测COCO并鉴黄的SexyYolo(含Yolov3的深度原理剖析) Yolov1论文链接:You Only Look ...

最新文章

  1. jupyter notebook 插入图片
  2. 如何去除My97 DatePicker控件上右键弹出官网的链接 - 如何debug混淆过的代码
  3. 并发工具类(二)同步屏障CyclicBarrier
  4. Centos mysql的安装和配置
  5. Find them, Catch them POJ - 1703(种类并查集)
  6. ASP.NET MVC中同步与异步
  7. swiper 滑动出现闪白
  8. 你也被Spring的这个“线程池”坑过吗?
  9. 信号与槽是如何实现的_苹果iPhone 12信号仍弱?网友反馈打不进电话需重启解决...
  10. java ssm框架调用微信,微信小程序实现前后台交互(后台使用ssm框架)
  11. 玩了一年多电子商务,接触各种品类产品
  12. 2008-03-17 淋湿的心情
  13. 部分拆解笔记本电脑(联想y580)
  14. WinHex license添加(v19测试可用)
  15. 浩辰ICAD电气软件IDq2003i.rar
  16. 华为网络设备——利用三层交换机实现VLAN间路由配置实例
  17. R-RCN 论文理解3
  18. JDBC-----什么是JDBC
  19. Hybrid App开发之css样式使用
  20. 坐在电脑前是高一点好还是低一点好

热门文章

  1. 三阶魔方大中小魔公式_三阶魔方还原图文教程-番茄魔方
  2. 计算机最小的计量单位,计算机中最小的计量单位是
  3. 单片机学习入门一 学习概述
  4. 读文件java_java怎么读取文件?
  5. ASP.NET MVC中的下拉框数据查询
  6. Excel函数(4)日期、文本函数
  7. html 引入 svg矢量图,前端可视化——SVG矢量图技术
  8. Android航海航线地图,Alfa的航海大时代航线介绍 全航线跑商路线详解
  9. SQL Server上月同期 日期的计算
  10. ini更改文件夹图标