海思的SDK里其实有H264编码的sample,但因为要匹配很多东西,代码有点复杂,让初学都感到有点混乱。我根据sample自己修改了一下代码,从最简单的情况(确定摄像头类型,只选一种尺寸的图片,只用一个通道)来说明海思HI3518是怎么编码为H264的。
先把源代码下载下来,再分析程序。
下载链接:https://download.csdn.net/download/zhanshenrui/10324766
首先从main函数开始。
//./myvenc
int main(int argc, char *argv[])//main()
{
HI_S32 s32Ret;
signal(SIGINT, SAMPLE_VENC_HandleSig);//ctrl+c,delete
signal(SIGTERM, SAMPLE_VENC_HandleSig);//shell命令kill缺省产生这个信号.
s32Ret = H264_Venc();
if(s32Ret==HI_SUCCESS)
printf("normally\n");
else
printf("unnormally\n");
return -1;
}
main函数里先是用signal定义了两个信号来中断程序的运行,在板子上输入./myvenc 执行程序,按ctrl+c来中断程序,然后再往下就是H264编码函数H264_Venc()。
在H264_Venc()函数里先去初始化MPI系统,然后根据摄像头设置参数。我这里是AR030摄像头,其拍摄图片大小为720P,编码为H264,为了简单只输出一路。在初始化MPI系统时要计算视频缓存池VB_CONF_S的大小,然后填充VB_CONF_S结构体。填充完VB_CONF_S结构体后再用HI_MPI_VB_SetConf设置,最后初始化视频视频缓存池HI_MPI_VB_Init。这里注意的是基本上每个函数调用后都会判断返回值并作出错出理,后面很多函数也是这样处理的,这样利于程序排错。
//为了简单点,编码类型就选择PT_H264,图片大小就选择PIC_HD720,通道也只用1路s32ChnNum=1
PAYLOAD_TYPE_E enPayLoad=PT_H264;//264编码
PIC_SIZE_E enSize=PIC_HD720;//摄像头拍摄图片的大小,这里只用720P
HI_S32 s32ChnNum=1;//支持一路摄像
HI_S32 s32Ret=HI_FAILURE;
/******************************************
mpp system init.
******************************************/
HI_MPI_SYS_Exit();
HI_MPI_VB_Exit();
VB_CONF_S stVbConf;//缓存池参数结构体
HI_U32 u32BlkSize;//一张图片占多少字节
memset(&stVbConf,0,sizeof(VB_CONF_S));
//根据制式,图片大小,图片格式及对齐方式确定图片缓存大小
//这里用NTSC,720P,YUV420,64字节对齐
u32BlkSize=SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,enSize, PIXEL_FORMAT_YUV_SEMIPLANAR_420, SAMPLE_SYS_ALIGN_WIDTH);
printf("u32BlkSize=%d\n",u32BlkSize);
stVbConf.u32MaxPoolCnt = 128;//用默认值,3518默认是128
stVbConf.astCommPool[0].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[0].u32BlkCnt = g_u32BlkCnt;
s32Ret = HI_MPI_VB_SetConf(&stVbConf);//设置 MPP 视频缓存池属性
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_VB_SetConf failed!\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_VB_Init();//初始化 MPP 视频缓存池
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_VB_Init failed!\n");
return HI_FAILURE;
}
视频缓存池初始化后就是初始化MPI系统。海思模块化了各个功能,我们想实现哪一种产品就按这种产品的框架来实现各个模块功能,而实现模块化功能也很简单,基本套路就是定义一个结构体,给结构体赋值,然后设置就可以了,像前面缓存池模块就是先定一个VB_CONF_S结构体,然后对结体体赋值,然后调用函数设置进去,最后初始化。同理,初始化MPI系统也要先定义一个结构体再赋值然后设置最后初始化,这种思路在linux里用得很多。
//定义结构体,对结构体赋值,然后设置,最后初始化
MPP_SYS_CONF_S stSysConf = {0};
stSysConf.u32AlignWidth = SAMPLE_SYS_ALIGN_WIDTH;
s32Ret = HI_MPI_SYS_SetConf(&stSysConf);//配置系统控制参数
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_SYS_SetConf failed\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_SYS_Init();//初始化 MPP 系统
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_SYS_Init failed!\n");
return HI_FAILURE;
}
系统初始化完后还要打开硬件,摄像头用的是MIPI,应用层只需要用open打开MIPI,用ioctl设置
HI_S32 fdmipi;
combo_dev_attr_t *pstcomboDevAttr = NULL;
fdmipi = open("/dev/hi_mipi", O_RDWR);
if (fdmipi < 0)
{
printf("warning: open hi_mipi dev failed\n");
return -1;
}
pstcomboDevAttr = &MIPI_CMOS3V3_ATTR;//AR0130模组属性
if (ioctl(fdmipi, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr))
{
printf("set mipi attr failed\n");
close(fdmipi);
return HI_FAILURE;
}
close(fdmipi);
根据海思SDK里的文档介绍,H264编码过程应该是:系统初始化--VI--VPSS-VENC.系统初始化前面已完成,现在要做的就是实现输入模块VI.VI模块有通道,所以还要设置通道参数。具体解释和代码如下,不再做过多解释:
HI_S32 i,ViChn=0;//只有一路输出,所以ViChn=0
//设置输入配置参数
SAMPLE_VI_CONFIG_S stViConfig = {0};
stViConfig.enViMode = APTINA_AR0130_DC_720P_30FPS;//摄像头是AR0130模组
stViConfig.enRotate = ROTATE_NONE;//不翻转
stViConfig.enNorm = VIDEO_ENCODING_MODE_AUTO;//编码模式自动
stViConfig.enViChnSet = VI_CHN_SET_NORMAL;//普通
stViConfig.enWDRMode = WDR_MODE_NONE;//不设置宽动态
VI_DEV_ATTR_S stViDevAttr;
VI_DEV ViDev=0;//只有一个摄像头设备,所以设备序号为0
ISP_DEV s32IspDev=0;//同样,ISP序号也为0
memset(&stViDevAttr,0,sizeof(stViDevAttr));
memcpy(&stViDevAttr,&DEV_ATTR_9M034_DC_720P_BASE,sizeof(stViDevAttr));
/******************************************
step 1: mipi configure
******************************************/
/*
s32Ret = SAMPLE_COMM_VI_StartMIPI(&stViConfig);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("%s: MIPI init failed!\n", __FUNCTION__);
return HI_FAILURE;
}
*/
/******************************************
step 2: configure sensor and ISP (include WDR mode).
note: you can jump over this step, if you do not use Hi3516A interal isp.
//虽然说了不用,但还要需要,因为后面HI_MPI_ISP_GetWDRMode函数会调用ISP_CHECK_MEM_INIT
//检测内存,所以还是在这里使用
******************************************/
s32Ret = SAMPLE_COMM_ISP_Init(stViConfig.enWDRMode);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("%s: Sensor init failed!\n", __FUNCTION__);
return HI_FAILURE;