欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-06-16 21:04:06
媒体子系统旨在为多媒体应用开发者开发者提供统一的开发接口,使得开发者可以专注于应用业务的开发,轻松使用多媒体的资源。...

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

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


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

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

图 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();			//创建Recoder
        ret = recorder_->SetOutputFile(recordFd_);
        ret = recorder_->Prepare();			//设置Muxer
        ret = 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();		//执行record
                break; 
            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赋值;橙色的细线箭头表示变量之间的隶属关系,箭头所在变量隶属于箭尾所在变量。

本文地址:https://blog.csdn.net/weixin_47070198/article/details/110186902