海思3518E开发笔记2.3——海思mpp架构及sample程序流程分析

目录

MPP

海思提供的媒体处理软件平台(Media Process Platform,简称 MPP),可支持应用软件快速开发。

通俗点就是海思提供的这套ko、lib、api,对内存的管理。对内部进行视频编码的dsp的封装。

这一套体系就是mpp

MPP系统架构

  • 硬件层
    硬件层由 Hi35xx 芯片加上必要的外围器件构成。外围器件包括 Flash、 DDR( Double Data-Rate)、视频 Sensor 或 AD、音频 AD (模数转换器)等。
  • 操作系统层
    基于 Linux 3.4 的 OS 系统。
  • 媒体处理平台
    基于操作系统层,控制芯片完成相应的媒体处理功能。它对应用层屏蔽了硬件处理细节,并为应用层提供 API 接口完成相应功能。
  • 其他驱动
    除媒体处理平台外,海思为 Hi35xx 芯片的其他相关硬件处理单元提供了相应的驱动,
    包括 GMAC、 SDIO、 I2C、 USB、 SSP 等驱动。
  • 应用层
    基于海思媒体处理平台及其他驱动,由用户开发的应用软件系统。

海思3518E开发笔记2.3——海思mpp架构及sample程序流程分析

海思媒体处理平台架构

海思媒体处理平台的主要内部处理流程如图 1-2 所示,主要分为

  • 视频输入( VI)

  • 视频处理( VPSS)

  • 视频编码(VENC)

  • 视频解码( VDEC)

  • 视频输出(VO)

  • 视频侦测分析(VDA)

  • 音频输入(AI)

  • 音频输出(AO)

  • 音频编码( AENC)

  • 音频解码( ADEC)

  • 区域管理( REGION)等模块。
    主要的处理流程介绍如下:
    海思3518E开发笔记2.3——海思mpp架构及sample程序流程分析

  • VI 模块捕获视频图像,可对其做剪切、缩放等处理,并输出多路不同分辨率的图像数据。

  • 解码模块对编码后的视频码流进行解码,并将解析后的图像数据送 VPSS 进行图像处理或直接送 VO 显示。可对 H264/MPEG4/MPEG2 格式的视频码流进行解码。

  • VPSS 模块接收 VI 和解码模块发送过来的图像,可对图像进行去噪、图像增强、锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍。

  • 编码模块接收 VI 捕获并经 VPSS 处理后输出的图像数据,可叠加用户通过 Region模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。

  • VDA 模块接收 VI 的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析结果。

  • VO 模块接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置的输出协议输出给外围视频设备。

  • AI 模块捕获音频数据,然后 AENC 模块支持按多种音频协议对其进行编码,最后输出音频码流。

  • 用户从网络或外围存储设备获取的音频码流可直接送给 ADEC 模块, ADEC 支持解码多种不同的音频格式码流,解码后数据送给 AO 模块即可播放声音。

视频缓存池

视频缓存池主要向媒体业务提供大块物理内存管理功能,负责内存的分配和回收,充分发挥内存缓存池的作用,让物理内存资源在各个媒体处理模块中合理使用。

一组大小相同、物理地址连续的缓存块组成一个视频缓存池。

视频输入通道需要使用公共视频缓存池。所有的视频输入通道都可以从公共视频缓存池中获取视频缓存块用于保存采集的图像(如图 2-1 中所示从公共视频缓存池 A 中获取视频缓存块 Bm)。由于视频输入通道不提供创建和销毁公共视频缓存池功能,因此,在系统初始化之前,必须为视频输入通道配置公共视频缓存池。根据业务的不同,公共缓存池的数量、缓存块的大小和数量不同。 图 2-1 中所示缓存块的生存期是指经过 VPSS 通道传给后续模块的情形( 图 2-1 实线径)。如果该缓存块完全没有经过 VPSS 通道传给其他模块,则将在 VPSS 模块处理后被放回公共缓存池( 图 2-1 虚线路径)。
海思3518E开发笔记2.3——海思mpp架构及sample程序流程分析

  • (1)视频的本质是多帧图片,图片的本质是RGB或rawRGB数据,要占用一段连续内存
  • (2)视频的裁剪、缩放、修正处理等各种操作,本质上就是对内存中的数据进行运算
  • (3)视频缓存池(VB, video buffer)就是一段很大,又被合理划分和管理的内存,用来做视频数据的暂存和运算场地
  • (4)公共视频缓存池的公共2字,可以理解为全局变量,也就是各个模块都能访问的一段内存
  • (5)看似视频缓存块在各个模块之间流转,实际上并没有内存复制,而是指针在传递
  • (6)视频缓存池的内存由MPP来维护,我们在系统启动时就把整个SDRAM分成了2部分:系统部分(由linux kernel来维护管理)和mpp部分(由mpp系统来维护管理) 通常分给MPP32M
  • (7)缓存池需要几个,每个中包含几个缓存块,每个缓存块多大,都是可以由用户程序设置好参数,然后调用MPP的相应API来向MPP申请分配的。//通常块大小对应一帧图片大小

其中

typedef struct hiVB_CONF_S
{
    HI_U32 u32MaxPoolCnt;     /* max count of pools, (0,VB_MAX_POOLS]  */    
    struct hiVB_CPOOL_S
    {
        HI_U32 u32BlkSize;
        HI_U32 u32BlkCnt;
        HI_CHAR acMmzName[MAX_MMZ_NAME_LEN];
    }astCommPool[VB_MAX_COMM_POOLS];
} VB_CONF_S;

这个结构体是一些公共缓冲池的属性
由于板载DDR是64M,所以视频缓冲池的大小是有限的
结构体中的结构体就是每个缓冲池中块的大小

海思SDK中给的相关函数是

  • VB_CONF_S
    通过这个结构体告诉MPP缓冲池要几个,每个缓冲池要几个缓存块,每个缓冲块有多大
  • HI_MPI_VB_SetConf
    //VB_CONF_S只是做了个模型,这里是设置,
  • HI_MPI_VB_Init
    //真正执行了这个操作,开始分配内存了

需要按照海思文档中说明的顺序进行操作

sample

从main函数开始

int main(int argc, char *argv[])
{
    HI_S32 s32Ret;
    if ( (argc < 2) || (1 != strlen(argv[1])))//如果传参用法不对
    {
        SAMPLE_VENC_Usage(argv[0]);//打印用法函数
        return HI_FAILURE;
    }

    signal(SIGINT, SAMPLE_VENC_HandleSig);
    signal(SIGTERM, SAMPLE_VENC_HandleSig);
    
    switch (*argv[1])
    {
    	//H264 编码  1080分辨率  30帧/秒
        case '0':/* H.264@1080p@30fps+H.265@1080p@30fps+H.264@D1@30fps */
            s32Ret = SAMPLE_VENC_1080P_CLASSIC();
            break;
        case '1':/* 1*1080p mjpeg encode + 1*1080p jpeg  */
            s32Ret = SAMPLE_VENC_1080P_MJPEG_JPEG();
            break;
        case '2':/* low delay */
            s32Ret = SAMPLE_VENC_LOW_DELAY();
            break;
        case '3':/* roibg framerate */
            s32Ret = SAMPLE_VENC_ROIBG_CLASSIC();
            break;
        case '4':/* Thumbnail of 1*1080p jpeg  */
            s32Ret = SAMPLE_VENC_1080P_JPEG_Thumb();
            break;
#ifndef hi3518ev201			
		case '5':/* H.264 Svc-t */
			s32Ret = SAMPLE_VENC_SVC_H264();
			break;
#endif
        default:
            printf("the index is invaild!\n");
            SAMPLE_VENC_Usage(argv[0]);
            return HI_FAILURE;
    }
    
    if (HI_SUCCESS == s32Ret)
        printf("program exit normally!\n");
    else
        printf("program exit abnormally!\n");
    exit(s32Ret);
}

为了保证兼容性,海思给的sample中有很多版本需要的内容

SAMPLE_VENC_1080P_CLASSIC

我们所用的主要是input为0时候,即SAMPLE_VENC_1080P_CLASSIC函数

/******************************************************************************
* function :  H.264@1080p@30fps+H.264@VGA@30fps
******************************************************************************/
HI_S32 SAMPLE_VENC_1080P_CLASSIC(HI_VOID)
{
    PAYLOAD_TYPE_E enPayLoad[3]= {PT_H264, PT_H264,PT_H264};
    PIC_SIZE_E enSize[3] = {PIC_HD1080, PIC_VGA,PIC_QVGA};
	HI_U32 u32Profile = 0;
	
    VB_CONF_S stVbConf;//视频缓存池//VB_CONF_S视频缓存池结构体
    SAMPLE_VI_CONFIG_S stViConfig = {0};
    
    VPSS_GRP VpssGrp;
    VPSS_CHN VpssChn;
    VPSS_GRP_ATTR_S stVpssGrpAttr;
    VPSS_CHN_ATTR_S stVpssChnAttr;
    VPSS_CHN_MODE_S stVpssChnMode;
    
    VENC_CHN VencChn;//编码通道
    SAMPLE_RC_E enRcMode= SAMPLE_RC_CBR;
	
    HI_S32 s32ChnNum=0;
    
    HI_S32 s32Ret = HI_SUCCESS;
    HI_U32 u32BlkSize;
    SIZE_S stSize;
    char c;

首先看上面的变量定义部分
最开始定义了一个PAYLOAD_TYPE_E的数组,对应三个通道,每一个都是H264格式
图像的尺寸对应三个通道,分别是1080、VGA、QVGA

有一点值得一提的是,枚举类型的结尾是BUTT,由于枚举是从1开始按顺序每个值增加的,所以最后写一个BUTT,可以通过BUTT进行判断

在初始化分辨率和编码格式结构体数组
还会定义一个公共视频缓存池

step 1: init sys variable

定义完变量后,第一步就是初始化sys部分的变量(mpp系统中的变量)

step 2: mpp system init.

系统初始化,为之前申请的变量分配内存

step 3: start vi dev & chn to capture

图像的采集,启动VI部分的dev(设备)和chn(通道)

step 4: start vpss and vi bind vpss

VI之后就会传给VPSS,传给VPSS之前先要把它启动起来
vi bind vpss:VI和VPSS是两个独立的模块.通过bind这操作,MPP系统提供的一个API(bind api),
通过调用bind api就可以把VI和VPSS这两个模块绑定起来。绑定起来之后有什么作用呢?
VI这边当它采集到一帧图像丢到一个VB(VIDEO BUFFER视频缓冲池)里面的缓存块里面之后,这个缓存块会自动传送到VPSS里面去。

step 5: start stream venc

VPSS处理完之后就会到VENC里面去。这一步就开始启动VENC单元,如果你要添加水印信息就在这一步去添加。
H.264编码也在这一步去研究。编码完之后呢就得到H.264的一个码流了。

step 6: stream venc process // get stream, then save it to file

我们得到H.264码流之后怎么处理呢?
-(1)可以把这个码流打包成一个MP4存储到硬盘里面去,这就是录像。
-(2)也可以分包,分成一个一个视频包通过RTSP传出去。
-(3)也可以像sample一样直接作为一个裸流直接丢到流文件里面去。

那么这种流文件必须通过像VLC这种能够解析裸流文件的播放器来观看。
所以说编码模块只负责输出一段H.264的裸流,这个裸流要怎么办是你的事。

step 7: exit process

如果不想录了之后就按两次回车跑到SAMPLE_COMM_VENC_StopGetStream();里面去
然后进行一些列的释放内存、解除绑定的操作

上一篇:课堂练习题 CodeBlock


下一篇:Java 创建 PDF 文件包的两种方法