在介绍视频缓存池前我们先了解下海思sdk整体软件框架:
海思媒体处理平台的主要内部处理流程如图 1-2 所示,主要分为视频输入(VI)、视频
处理(VPSS)、视频编码(VENC)、视频解码(VDEC)、视频输出(VO)、视频侦测分
析(VDA)、音频输入(AI)、音频输出(AO)、音频编码(AENC)、音频解码(ADEC)、
区域管理(REGION)等模块。主要的处理流程介绍如下:
VI 模块捕获视频图像,可对其做剪切、缩放、镜像等处理,并输出多路不同分辨
率的图像数据。
解码模块对编码后的视频码流进行解码,并将解析后的图像数据送 VPSS 进行图
像处理或直接送 VO 显示。可对 H.264/H.265/VC1/MPEG4/MPEG2/AVS 格式的视
频码流进行解码。
VPSS 模块接收 VI 和解码模块发送过来的图像,可对图像进行去噪、图像增强、
锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓
拍。
编码模块接收 VI 捕获并经 VPSS 处理后输出的图像数据,可叠加用户通过 Region
模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。
VDA 模块接收 VI 的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析
结果。
VO 模块接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置
的输出协议输出给外围视频设备。
AI 模块捕获音频数据,然后 AENC 模块支持按多种音频协议对其进行编码,最后
输出音频码流。
用户从网络或外围存储设备获取的音频码流可直接送给 ADEC 模块,ADEC 支持
解码多种不同的音频格式码流,解码后数据送给 AO 模块即可播放声音。
视频缓存池主要向媒体业务提供大块物理内存管理功能,负责内存的分配和回收,充
分发挥内存缓存池的作用,让物理内存资源在各个媒体处理模块中合理使用。
一组大小相同、物理地址连续的缓存块组成一个视频缓存池。
视频输入通道需要使用公共视频缓存池。所有的视频输入通道都可以从公共视频缓存
池中获取视频缓存块用于保存采集的图像(如图 2-1 中所示从公共视频缓存池 A 中获
取视频缓存块 Bm)。
由于视频输入通道不提供创建和销毁公共视频缓存池功能,因此,
在系统初始化之前,必须为视频输入通道配置公共视频缓存池。
根据业务的不同,公共缓存池的数量、缓存块的大小和数量不同。图 2-1 中所示缓存块的
生存期是指经过 VPSS 的通道以 USERMODE 方式传给后续模块的情形(图 2-1 实线路径)
。如果该缓存块完全没有经过 VPSS 的通道透传给其他模块,则将在 VPSS 模块处理后被放
回公共缓存池(图 2-1 虚线路径)。
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;
缓存池数据结构如上,大致就是分类型的池子里装了对应的多块缓存块。
VI -> VPSS -> VENC
VI -> VPSS -> VDA
VI -> VPSS -> VO
缓存块Bm在VI->VPSS的过程中使用,由于VPSS可能会进行锐化,或者放大图形的操作,那原来的Bm将不适用,这时 VPSS -> VENC 重新获取Bi缓存块来存储经过VPSS处理后的视频。Bj,Bk同理,经过最后一步的处理后再进行回收。这个是公共缓存池的操作。
使用流程API
/清空VB/
HI_MPI_VB_DestroyPool
HI_MPI_VB_Exit
/设置新VB/
HI_MPI_VB_SetConf
HI_MPI_VB_Init
如果,讲分配的大块内存以不同内存池存储并建立内存块间的链表。
由于内存池都为物理内存地址,在应用层使用时需要转换为mpp映射的虚拟内存地址。
以上介绍的是公用缓存池下面来说下私有缓存池,如下应用场景解码YUV420 Semi-Planar格式的视频那存储这个raw yuv420 sp的缓存池将是公共内存池。经过解码模块VDEC后出来的视频是raw RGB之类的原始视频数据,这时候用的是common缓存池(解码后的一帧视频肯定比yuv的大原先的vb肯定装不下)。
VDH->VPSS->VO->WBC->H264
/* g_s32VBSource: 0 to module common vb, 1 to private vb, 2 to user vb
And don’t forget to set the value of VBSource file “load3535” */
VDH->VPSS的过程使用了 g_s32VBSource = 0;那这时候使用的是通用VB接口为
HI_MPI_VB_SetModPoolConf
HI_MPI_VB_InitModCommPool
如果g_s32VBSource = 2用的是用户VB,用HI_MPI_VB_CreatePool接口返回用户VB的id,然后使用该ID传入HI_S32 HI_MPI_VDEC_AttachVbPool(VDEC_CHN VdChn, VDEC_CHN_POOL_S *pstPool);绑定对应的解码通道这时就达到使用用户VB的功能了。
上述为解码帧存分配方式,具体如下:
− 解码 ModuleVB 池:创建解码通道时不分配图像 Buffer,而是由用户调用相应
的 MPI 接口创建专属于解码模块的 ModuleVB 池,该 VB 池只允许 VDEC 获取
VB 块,其它模块只能使用不能获取。
− 解码 PrivateVB 池:创建解码通道时由 VDEC 创建私有 VB 池作为该通道的图
像 Buffer,用户可以在创建通道前调用接口 HI_MPI_VDEC_SetChnVBCnt 来设
置通道私有 VB 池的 VB 个数,默认个数为 5。
− 解码 UserVB 池:创建解码通道时不分配图像 Buffer,而是由用户调用接口
HI_MPI_VB_CreatePool 创建一个视频缓存 VB 池,再通过调用接口
HI_MPI_VDEC_AttachVbPool 把某个解码通道绑定到固定的视频缓存 VB 池
中。
三种解码帧存分配方式可通过 ko 加载时设置模块参数 VBSource 来选择。
VBSource=0 表示使用解码 ModuleVB 池方式;VBSource =1 表示使用解码
PrivateVB 池方式;VBSource=2 表示使用解码 UserVB 池方式。当解码帧存使用
ModuleVB 池或者 UserVB 池方式时,可以不用销毁解码通道直接销毁 VB 池,但
是销毁解码 VB 池前用户必须保证没有任何模块正在使用这个 VB 池里的任何一
块 VB(可通过复位解码通道,以及复位解码直接或间接绑定的后级模块实现,如
VDEC 绑定 VPSS,VPSS 绑定 VO,则就要同时复位 VDEC、VPSS 和 VO;如果
用户是从 VDEC 里获取图像上去,也必须保证全部图像释放回 VDEC。),否则会
出现程序异常的情况
VIDEO_NORM_E gs_enNorm = VIDEO_ENCODING_MODE_PAL; //PAL(德国制@25帧),中国主要也是使用这种。
VIDEO_NORM_E gs_enNorm = VIDEO_ENCODING_MODE_NTSC; //NTSC(美国制@30帧),(来自网络)更换N制的镜头后,VI VO的属性都要设置成N制的
case PIC_QCIF:
pstSize->u32Width = D1_WIDTH / 4;
pstSize->u32Height = (VIDEO_ENCODING_MODE_PAL==enNorm)?144:120;
break;
case PIC_CIF:
pstSize->u32Width = D1_WIDTH / 2;
pstSize->u32Height = (VIDEO_ENCODING_MODE_PAL==enNorm)?288:240;
break;
case PIC_D1:
pstSize->u32Width = D1_WIDTH;
pstSize->u32Height = (VIDEO_ENCODING_MODE_PAL==enNorm)?576:480;
break;
case PIC_960H:
pstSize->u32Width = 960;
pstSize->u32Height = (VIDEO_ENCODING_MODE_PAL==enNorm)?576:480;
break;
case PIC_2CIF:
pstSize->u32Width = D1_WIDTH / 2;
pstSize->u32Height = (VIDEO_ENCODING_MODE_PAL==enNorm)?576:480;
break;
case PIC_QVGA: /* 320 * 240 */
pstSize->u32Width = 320;
pstSize->u32Height = 240;
break;
case PIC_VGA: /* 640 * 480 */
pstSize->u32Width = 640;
pstSize->u32Height = 480;
break;
case PIC_XGA: /* 1024 * 768 */
pstSize->u32Width = 1024;
pstSize->u32Height = 768;
break;
case PIC_SXGA: /* 1400 * 1050 */
pstSize->u32Width = 1400;
pstSize->u32Height = 1050;
break;
case PIC_UXGA: /* 1600 * 1200 */
pstSize->u32Width = 1600;
pstSize->u32Height = 1200;
break;
case PIC_QXGA: /* 2048 * 1536 */
pstSize->u32Width = 2048;
pstSize->u32Height = 1536;
break;
case PIC_WVGA: /* 854 * 480 */
pstSize->u32Width = 854;
pstSize->u32Height = 480;
break;
case PIC_WSXGA: /* 1680 * 1050 */
pstSize->u32Width = 1680;
pstSize->u32Height = 1050;
break;
case PIC_WUXGA: /* 1920 * 1200 */
pstSize->u32Width = 1920;
pstSize->u32Height = 1200;
break;
case PIC_WQXGA: /* 2560 * 1600 */
pstSize->u32Width = 2560;
pstSize->u32Height = 1600;
break;
case PIC_HD720: /* 1280 * 720 */
pstSize->u32Width = 1280;
pstSize->u32Height = 720;
break;
case PIC_HD1080: /* 1920 * 1080 */
pstSize->u32Width = 1920;
pstSize->u32Height = 1080;
break;
case PIC_UHD4K: /* 1920 * 1080 */
pstSize->u32Width = 3840;
pstSize->u32Height = 2160;
break;
假设我们的图片格式大小类型为PIC_HD1080那stSize为1920 * 1080 (pstSize->u32Width = 1920;pstSize->u32Height = 1080;)
u32Width = CEILING_2_POWER(stSize.u32Width, u32AlignWidth);
u32Height = CEILING_2_POWER(stSize.u32Height,u32AlignWidth);
CEILING_2_POWER(x,a) ceiling x to multiple of a(a must be power of 2)
CEILING_2_POWER(x,a) ( ((x) + ((a) - 1) ) & ( ~((a) - 1) ) )
意思就是u32Width (stSize.u32Width = 1080 的最近的上限值)必须是u32AlignWidth = 16 的整数倍 那就是1088 ,1088/16 = 68.即是16的整数倍做对齐操作。
依次计算出宽高。
if (PIXEL_FORMAT_YUV_SEMIPLANAR_422 == enPixFmt)
{
u32BlkSize = u32Width * u32Height * 2;
}
else
{
u32BlkSize = u32Width * u32Height * 3 / 2;
}
我们的是PIXEL_FORMAT_YUV_SEMIPLANAR_420 所以我们的缓存块大小是u32BlkSize = u32Width * u32Height * 3 / 2;个字节
这个格式的数据量跟YUV420 Planar的一样,但是U、V是交叉存放的,一个像素点对应一个Y,一个2X2的小方块对应一个U和V。
width * hight =Y(总和)
U = Y / 4
V = Y / 4
所以YUV420 数据在内存中的长度是 width * hight * 3 / 2,实际上Y U V 各占一个字节(知道怎么算了吧~)。
stVbConf.astCommPool[0].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[0].u32BlkCnt = 20;
u32BlkCnt 需要根据具体的帧率来设定。
然后传入上述使用过程的api就完成申请了。这样内部mpp就可以使用到你已经配置好的缓存池了。
视频像素格式为PIXEL_FORMAT_YUV_SEMIPLANAR_420
分辨率为PIC_HD1080(1920*1080)
VB_CONF_S stVbConf;
SIZE_S stSize;
HI_U32 u32AlignWidth = 16;//16字节对齐
HI_U32 u32BlkSize = 0;
HI_U32 u32Width = 0;
HI_U32 u32Height = 0;
MPP_SYS_CONF_S stSysConf = {0};
HI_S32 s32Ret = HI_FAILURE;
HI_S32 i;
stSize.u32Width = 1920;
stSize.u32Height = 1080;
u32Width = CEILING_2_POWER(stSize.u32Width, u32AlignWidth);
u32Height = CEILING_2_POWER(stSize.u32Height,u32AlignWidth);
u32BlkSize = u32Width * u32Height * 3 / 2;
stVbConf.astCommPool[0].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[0].u32BlkCnt = 20;
HI_MPI_SYS_Exit();
for(i=0;i
以上为整个海思缓存池的个人理解。如有错误请帮忙指出。谢谢。