海思的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,deletesignal(SIGTERM, SAMPLE_VENC_HandleSig);//shell命令kill缺省产生这个信号.s32Ret = H264_Venc();if(s32Ret==HI_SUCCESS)printf("normally\n");elseprintf("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=1PAYLOAD_TYPE_E enPayLoad=PT_H264;//264编码PIC_SIZE_E  enSize=PIC_HD720;//摄像头拍摄图片的大小,这里只用720PHI_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默认是128stVbConf.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;//只有一个摄像头设备,所以设备序号为0ISP_DEV s32IspDev=0;//同样,ISP序号也为0memset(&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;}/******************************************step 3: run isp thread note: you can jump over this step, if you do not use Hi3516A interal isp.******************************************/s32Ret = SAMPLE_COMM_ISP_Run();if (HI_SUCCESS != s32Ret){SAMPLE_PRT("%s: ISP init failed!\n", __FUNCTION__);/* disable videv */return HI_FAILURE;}s32Ret=HI_MPI_VI_SetDevAttr(ViDev, &stViDevAttr);//只有一个设备,所以设备号为0if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VI_SetDevAttr failed with %#x!\n", s32Ret);return HI_FAILURE;}ISP_WDR_MODE_S stWdrMode;s32Ret = HI_MPI_ISP_GetWDRMode(s32IspDev, &stWdrMode);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_ISP_GetWDRMode failed with %#x!\n", s32Ret);return HI_FAILURE;}VI_WDR_ATTR_S stWdrAttr;stWdrAttr.enWDRMode = stWdrMode.enWDRMode;stWdrAttr.bCompress = HI_FALSE;s32Ret = HI_MPI_VI_SetWDRAttr(ViDev, &stWdrAttr);if (s32Ret){SAMPLE_PRT("HI_MPI_VI_SetWDRAttr failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VI_EnableDev(ViDev);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VI_EnableDev failed with %#x!\n", s32Ret);return HI_FAILURE;}RECT_S stCapRect;SIZE_S stTargetSize;stCapRect.s32X = 0;stCapRect.s32Y = 0;stCapRect.u32Width = 1280;stCapRect.u32Height = 720;//AR030输出1280*720图像stTargetSize.u32Width = stCapRect.u32Width;stTargetSize.u32Height = stCapRect.u32Height;//设置通道属性VI_CHN_ATTR_S stChnAttr;memcpy(&stChnAttr.stCapRect, &stCapRect, sizeof(RECT_S));stChnAttr.enCapSel = VI_CAPSEL_BOTH;stChnAttr.stDestSize.u32Width = stTargetSize.u32Width;stChnAttr.stDestSize.u32Height = stTargetSize.u32Height;stChnAttr.enPixFormat = PIXEL_FORMAT_YUV_SEMIPLANAR_420; stChnAttr.bMirror = HI_FALSE;stChnAttr.bFlip = HI_FALSE;stChnAttr.s32SrcFrameRate = -1;stChnAttr.s32DstFrameRate = -1;stChnAttr.enCompressMode = COMPRESS_MODE_NONE;s32Ret = HI_MPI_VI_SetChnAttr(ViChn, &stChnAttr);//设置通道属性if (s32Ret != HI_SUCCESS){SAMPLE_PRT("in HI_MPI_VI_SetChnAttr failed with %#x!\n", s32Ret);SAMPLE_COMM_ISP_Stop();return HI_FAILURE;}s32Ret = HI_MPI_VI_EnableChn(ViChn);//使能通道if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VI_EnableChn failed with %#x!\n", s32Ret);SAMPLE_COMM_ISP_Stop();return HI_FAILURE;}

VI模块设置完以后就要设置VPSS模块,VPSS模块有组,通道概念,所以设置VPSS模块也要设置VPSS组、VPSS通道。

设置VPSS组过程如下:定义结构体,给结构体赋值,设置(创建的同时就设置了),启动

SIZE_S stSize;stSize.u32Width  = 1280;stSize.u32Height = 720;VPSS_GRP_ATTR_S stVpssGrpAttr;VPSS_NR_PARAM_U unNrParam = {{0}};VPSS_GRP VpssGrp=0;//只有一个设备,序号为0stVpssGrpAttr.u32MaxW = stSize.u32Width;stVpssGrpAttr.u32MaxH = stSize.u32Height;stVpssGrpAttr.bIeEn = HI_FALSE;stVpssGrpAttr.bNrEn = HI_TRUE;stVpssGrpAttr.bHistEn = HI_FALSE;stVpssGrpAttr.bDciEn = HI_FALSE;stVpssGrpAttr.enDieMode = VPSS_DIE_MODE_NODIE;stVpssGrpAttr.enPixFmt = PIXEL_FORMAT_YUV_SEMIPLANAR_420;s32Ret = HI_MPI_VPSS_CreateGrp(VpssGrp, &stVpssGrpAttr);//创建组的同时将组的属性设置进去if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_CreateGrp failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_GetNRParam(VpssGrp, &unNrParam);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_GetNRParam failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_SetNRParam(VpssGrp, &unNrParam);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_SetNRParam failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_StartGrp(VpssGrp);//启动VPSS组if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_StartGrp failed with %#x\n", s32Ret);return HI_FAILURE;}

VPSS还要与VI绑定,以便VI的数据直接输入VPSS,不需要我们手动从VI复制数据到VPSS,绑定时将绑定源设置为VI的哪
一个通道,目的源设置为VPSS的一个通道

SAMPLE_VI_PARAM_S stViParam;stViParam.s32ViDevCnt      = 1;stViParam.s32ViDevInterval = 1;stViParam.s32ViChnCnt      = 1;stViParam.s32ViChnInterval = 1;MPP_CHN_S stSrcChn;MPP_CHN_S stDestChn;stSrcChn.enModId  = HI_ID_VIU;stSrcChn.s32DevId = 0;stSrcChn.s32ChnId = ViChn;stDestChn.enModId  = HI_ID_VPSS;stDestChn.s32DevId = VpssGrp;stDestChn.s32ChnId = 0;s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);//将VI的0通道与VPSS的组的0通道绑定起来if (s32Ret != HI_SUCCESS){SAMPLE_PRT("failed with %#x!\n", s32Ret);return HI_FAILURE;}

前面设置了VPSS组,将VI通道与VPSS组里的0通道绑定起来,但还未设置0通道,下面设置0通道.还是老套路,定义结构体,给结构体赋值,将结构体设置进去,开启通道。

//设置VPSS通道,只有一个通道,所以VpssChn=0VPSS_CHN VpssChn=0;VPSS_CHN_ATTR_S stVpssChnAttr;VPSS_CHN_MODE_S stVpssChnMode;stVpssChnMode.enChnMode      = VPSS_CHN_MODE_USER;stVpssChnMode.bDouble        = HI_FALSE;stVpssChnMode.enPixelFormat  = PIXEL_FORMAT_YUV_SEMIPLANAR_420;stVpssChnMode.u32Width       = stSize.u32Width;stVpssChnMode.u32Height      = stSize.u32Height;stVpssChnMode.enCompressMode = COMPRESS_MODE_SEG;memset(&stVpssChnAttr, 0, sizeof(stVpssChnAttr));stVpssChnAttr.s32SrcFrameRate = -1;stVpssChnAttr.s32DstFrameRate = -1;s32Ret = HI_MPI_VPSS_SetChnAttr(VpssGrp, VpssChn, &stVpssChnAttr);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_SetChnAttr failed with %#x\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_SetChnMode(VpssGrp, VpssChn, &stVpssChnMode);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("%s failed with %#x\n", __FUNCTION__, s32Ret);return HI_FAILURE;}  s32Ret = HI_MPI_VPSS_EnableChn(VpssGrp, VpssChn);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VPSS_EnableChn failed with %#x\n", s32Ret);return HI_FAILURE;}

上面将VPSS设置完成,按照框架,下一个应该设置VENC模块,跟VPSS一样,老套路设置,然后绑定。

//定义结构体,给结构体赋值,创建通道同时将结构体设置进去SAMPLE_RC_E enRcMode= SAMPLE_RC_CBR;VENC_CHN VencChn=0;VENC_CHN_ATTR_S stVencChnAttr;VENC_ATTR_H264_S stH264Attr;VENC_ATTR_H264_CBR_S    stH264Cbr;stVencChnAttr.stVeAttr.enType =enPayLoad;stH264Attr.u32MaxPicWidth =stSize.u32Width;stH264Attr.u32MaxPicHeight = stSize.u32Height;stH264Attr.u32PicWidth = stSize.u32Width;/*the picture width*/stH264Attr.u32PicHeight = stSize.u32Height;/*the picture height*/stH264Attr.u32BufSize  = stSize.u32Width * stSize.u32Height;/*stream buffer size*/stH264Attr.u32Profile  = 0;/*0: baseline; 1:MP; 2:HP;  3:svc_t */stH264Attr.bByFrame = HI_TRUE;/*get stream mode is slice mode or frame mode?*/stH264Attr.u32BFrameNum = 0;/* 0: not support B frame; >=1: number of B frames */stH264Attr.u32RefNum = 1;/* 0: default; number of refrence frame*/memcpy(&stVencChnAttr.stVeAttr.stAttrH264e, &stH264Attr, sizeof(VENC_ATTR_H264_S));stVencChnAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;stH264Cbr.u32Gop            = (VIDEO_ENCODING_MODE_PAL== gs_enNorm)?25:30;stH264Cbr.u32StatTime       = 1; /* stream rate statics time(s) */stH264Cbr.u32SrcFrmRate      = (VIDEO_ENCODING_MODE_PAL== gs_enNorm)?25:30;/* input (vi) frame rate */stH264Cbr.fr32DstFrmRate = (VIDEO_ENCODING_MODE_PAL== gs_enNorm)?25:30;/* target frame rate */stH264Cbr.u32BitRate = 1024*2;stH264Cbr.u32FluctuateLevel = 0; /* average bit rate */memcpy(&stVencChnAttr.stRcAttr.stAttrH264Cbr, &stH264Cbr, sizeof(VENC_ATTR_H264_CBR_S));s32Ret = HI_MPI_VENC_CreateChn(VencChn, &stVencChnAttr);//创建通道同时将结构体设置进去if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VENC_CreateChn [%d] faild with %#x!\n",VencChn, s32Ret);return s32Ret;}

设置完以后,开启接收图片并将VPSS通道与VENC通道绑定起来。VPSS通道作为源通道,VENC通道作为目的通道

//将VPSS通道作为源通道与VENC通道作为目的通道绑定起来stSrcChn.enModId = HI_ID_VPSS;stSrcChn.s32DevId = VpssGrp;stSrcChn.s32ChnId = VpssChn;stDestChn.enModId = HI_ID_VENC;stDestChn.s32DevId = 0;stDestChn.s32ChnId = VencChn;s32Ret=HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);if (s32Ret != HI_SUCCESS){SAMPLE_PRT("failed with %#x!\n", s32Ret);return HI_FAILURE;}

这样,整个系统我们就设置好了,下一步要操作地是开始接收H264码流并将码流保存起来。这里用到多线程,用pthread_create创建个线程函数,在线程函数里用select多路IO复用来获取码流并保存。

gs_stPara.bThreadStart = HI_TRUE;gs_stPara.s32Cnt = s32ChnNum;pthread_create(&gs_VencPid, 0, VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);printf("please press twice ENTER to exit this sample\n");

gs_stPara是自己定义的一个结构体,里面的成员变量bThreadStart为真时表示不断读取保存H264码流,当接连有两次按下键盘时或有ctrl+c按下时,这个成员变量会为假,从而会停止VENC_GetVencStreamProc线程函数。 VENC_GetVencStreamProc线程函数可以说是获取H264码流的最关键的函数,我们稍会再说,先按正常流程说下后面代码的作用。后面的代码如下:

getchar();getchar();if (HI_TRUE == gs_stPara.bThreadStart){gs_stPara.bThreadStart = HI_FALSE;pthread_join(gs_VencPid, 0);}

getchar()函数的执行模式是阻塞式的,当需要接收字符流的时候,当前线程(main函数所在线程)就会被挂起,其后的所有代码均要等待用户输入回车表示输入完毕后,线程才会被调度执行余下的代码,即进入if让gs_stPara.bThreadStart 为假,同时调用pthread_join(gs_VencPid, 0)函数,main函数又会进入阻塞模式,等待线程函数 VENC_GetVencStreamProc结束。线程函数VENC_GetVencStreamProc结束后,CPU又进入main函数执行pthread_join(gs_VencPid, 0)后的代码,可以猜测后面的代码就是收尾工作,应该与前面的步骤动作相反,前面绑定通道,后面就要解绑,前面创建开启通道,后面就要停止销毁通道,前面初始化, 后面就要去初始化,前面按顺序设置MPI_SYS,VI,VPSS,VENC,后面就要反过来按顺序设置VENC,VPSS,VI,MPI_SYS,看代码的确如此:

stSrcChn.enModId = HI_ID_VPSS;stSrcChn.s32DevId = VpssGrp;stSrcChn.s32ChnId = VpssChn;stDestChn.enModId = HI_ID_VENC;stDestChn.s32DevId = 0;stDestChn.s32ChnId = VencChn;s32Ret = HI_MPI_SYS_UnBind(&stSrcChn, &stDestChn);//先解绑VPSS和VENC的绑定,后面再解绑VPSS和VI的绑定if (s32Ret != HI_SUCCESS){SAMPLE_PRT("failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VENC_StopRecvPic(VencChn);//前面开启了接收图片,这里就要停止if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VENC_StopRecvPic vechn[%d] failed with %#x!\n",VencChn, s32Ret);return HI_FAILURE;}/******************************************Distroy Venc Channel******************************************/s32Ret = HI_MPI_VENC_DestroyChn(VencChn);//前面创建了VENC通道,这里就要销毁VENC通道,而且最先销毁,后面再销毁VI通道if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VENC_DestroyChn vechn[%d] failed with %#x!\n",VencChn, s32Ret);return HI_FAILURE;}stSrcChn.enModId = HI_ID_VIU;stSrcChn.s32DevId = ViDev;stSrcChn.s32ChnId = ViChn;stDestChn.enModId = HI_ID_VPSS;stDestChn.s32DevId = VpssGrp;stDestChn.s32ChnId = 0;s32Ret = HI_MPI_SYS_UnBind(&stSrcChn, &stDestChn);//后面再解绑VPSS和VI的绑定,在解绑VPSS和VENC的绑定之后,顺序与绑定顺序相反if (s32Ret != HI_SUCCESS){SAMPLE_PRT("failed with %#x!\n", s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_DisableChn(VpssGrp, VpssChn);//失能VPSS组和VPSS通道if (s32Ret != HI_SUCCESS){SAMPLE_PRT("%s failed with %#x\n", __FUNCTION__, s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_StopGrp(VpssGrp);//停止VPSS组if (s32Ret != HI_SUCCESS){SAMPLE_PRT("%s failed with %#x\n", __FUNCTION__, s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VPSS_DestroyGrp(VpssGrp);//销毁组if (s32Ret != HI_SUCCESS){SAMPLE_PRT("%s failed with %#x\n", __FUNCTION__, s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VI_DisableChn(ViChn);//失能通道if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VI_DisableChn failed with %#x\n",s32Ret);return HI_FAILURE;}s32Ret = HI_MPI_VI_DisableDev(ViDev);//失能VI设备即摄像头if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VI_DisableDev failed with %#x\n", s32Ret);return HI_FAILURE;}SAMPLE_COMM_ISP_Stop();//停止ISPHI_MPI_SYS_Exit();//去初始化HI_MPI_VB_Exit();//由以上几个步骤可以看出,设置与销毁顺序相反,而且是成对出现return HI_SUCCESS;

至此,H264_Venc函数分析完,现在我们来重点分析线程函数VENC_GetVencStreamProc是怎么获取码流并保存到一个文件里的。

HI_VOID* VENC_GetVencStreamProc(HI_VOID *p)
{SAMPLE_VENC_GETSTREAM_PARA_S *pstPara;HI_S32 s32ChnTotal;VENC_CHN VencChn;HI_S32 s32Ret;PAYLOAD_TYPE_E enPayLoadType[VENC_MAX_CHN_NUM];HI_CHAR aszFileName[VENC_MAX_CHN_NUM][64];VENC_CHN_ATTR_S stVencChnAttr;struct timeval TimeoutVal;fd_set read_fds;HI_S32 VencFd[VENC_MAX_CHN_NUM],maxfd;VENC_STREAM_S stStream;VENC_CHN_STAT_S stStat;FILE *pFile[VENC_MAX_CHN_NUM];HI_S32 i;pstPara = (SAMPLE_VENC_GETSTREAM_PARA_S*)p;s32ChnTotal = pstPara->s32Cnt;//pstPara->s32Cnt是由参数传进来的,为1for (i = 0; i < s32ChnTotal; i++){VencChn = i;s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);//这里是为了取得是什么编码类型,以便确定保存文件的后缀名//比如这里是H264编码,所以保存文件的后缀后就是.h264if(s32Ret != HI_SUCCESS){SAMPLE_PRT("HI_MPI_VENC_GetChnAttr chn[%d] failed with %#x!\n", \VencChn, s32Ret);return NULL;}enPayLoadType[i] = stVencChnAttr.stVeAttr.enType;gettime(filename);//获取时间如20171101085639为文件名sprintf(aszFileName[i], "%s_%d%s",filename, i, ".h264");//20171101085639.h264pFile[i] = fopen(aszFileName[i], "wb");if (!pFile[i]){SAMPLE_PRT("open file[%s] failed!\n", aszFileName[i]);return NULL;}VencFd[i] = HI_MPI_VENC_GetFd(i);//获取文件句柄,以便后面能用select来IO复用if (VencFd[i] < 0){SAMPLE_PRT("HI_MPI_VENC_GetFd failed with %#x!\n", VencFd[i]);return NULL;}if (maxfd <= VencFd[i]){maxfd = VencFd[i];}}while (HI_TRUE == pstPara->bThreadStart)//当main函数所在的线程接收到两个键盘字符或ctrl+c时,pstPara->bThreadStart会为假,跳出while循环//然后往下执行关闭前面打开的文件,执行完这个VENC_GetVencStreamProc线程函数,线程结束{/*IO复用4步骤1.清空文件集合FD_ZERO(&read_fds);2.将文件加入文件集合FD_SET(VencFd[i], &read_fds);3.设置超时时间并用select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal)来等待文件状态有变化唤醒线程或超时唤醒4.FD_ISSET查询文件状态是否有变化,有变化则处理*/FD_ZERO(&read_fds);for (i = 0; i < s32ChnTotal; i++){FD_SET(VencFd[i], &read_fds);}TimeoutVal.tv_sec  = 2;TimeoutVal.tv_usec = 0;s32Ret = select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);if (s32Ret < 0){SAMPLE_PRT("select failed!\n");break;}else if (s32Ret == 0){SAMPLE_PRT("get venc stream time out, exit thread\n");continue;}else{for (i = 0; i < s32ChnTotal; i++){if (FD_ISSET(VencFd[i], &read_fds)){memset(&stStream, 0, sizeof(stStream));s32Ret = HI_MPI_VENC_Query(i, &stStat);//查询是否有码流,并将码流信息填充到stStat结构体中if (HI_SUCCESS != s32Ret){SAMPLE_PRT("HI_MPI_VENC_Query chn[%d] failed with %#x!\n", i, s32Ret);break;}if(0 == stStat.u32CurPacks){SAMPLE_PRT("NOTE: Current  frame is NULL!\n");continue;}stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);//分配内存以便保存码流包数据if (NULL == stStream.pstPack){SAMPLE_PRT("malloc stream pack failed!\n");break;}stStream.u32PackCount = stStat.u32CurPacks;//printf("stStream.u32PackCount=%d\n",stStream.u32PackCount);s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);//获取码流数据并保存到stStream结构体中if (HI_SUCCESS != s32Ret){free(stStream.pstPack);//获取失败则要释放前面分配的内存,否则会造成内存溢出stStream.pstPack = NULL;SAMPLE_PRT("HI_MPI_VENC_GetStream failed with %#x!\n", s32Ret);break;}HI_S32 u32PackIndex;for (u32PackIndex= 0;u32PackIndex < stStream.u32PackCount; u32PackIndex++){fwrite(   stStream.pstPack[u32PackIndex].pu8Addr+stStream.pstPack[u32PackIndex].u32Offset,\stStream.pstPack[u32PackIndex].u32Len-  stStream.pstPack[u32PackIndex].u32Offset, \1, pFile[i]);fflush(pFile[i]);#if 1printf("stStream.u32PackCount=%d,stStream.pstPack[%d].pu8Addr=0x%08x,\stStream.pstPack[%d].u32Offset=%d,stStream.pstPack[%d].u32Len=%d\n",\stStream.u32PackCount,u32PackIndex,stStream.pstPack[u32PackIndex].pu8Addr,\u32PackIndex,stStream.pstPack[u32PackIndex].u32Offset,u32PackIndex,stStream.pstPack[u32PackIndex].u32Len);//添加打印信息,查看保存码流内容的内存是怎么样的#endif}s32Ret = HI_MPI_VENC_ReleaseStream(i, &stStream);//保存后要释放码流if (HI_SUCCESS != s32Ret){free(stStream.pstPack);//获取失败则要释放前面分配的内存,否则会造成内存溢出stStream.pstPack = NULL;break;}free(stStream.pstPack);//释放码流后,也要释放分配的内存,避免内存溢出stStream.pstPack = NULL;}}}}for (i = 0; i < s32ChnTotal; i++){fclose(pFile[i]);}return NULL;
}

线程函数里先通过HI_MPI_VENC_GetFd取得文件句柄,然后在while循环里不断清空读文件集合FD_ZERO(&read_fds);将文件句柄加入读文件集合FD_SET(VencFd[i], &read_fds);用select来休眠线程,有数据或超时又唤醒线程select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);用FD_ISSET(VencFd[i], &read_fds)来查询是哪个文件句柄有数据以便处理,然后用HI_MPI_VENC_Query查询码流统计信息以便分配内存,HI_MPI_VENC_GetStream来取得码流包数据,fwrite将码流包数据保存到文件中,码流包处理后要HI_MPI_VENC_ReleaseStream释放掉码流,释放掉申请的内存。

综合整个程序,在创建线程函数前都是对系统设置,按照海思SDK里的文档步骤设置就行,基本上就是定义结构体,给结构体赋值,然后设置进系统。到后面创建线程后就有两个线程函数了,一个main函数所在线程,一个VENC_GetVencStreamProc函数所在线程。main函数所在线程创建VENC_GetVencStreamProc线程后就调用getchar();函数进入休眠状态,只有当键盘有输入时getchar()函数有返回值时才会唤醒main函数所在线程,所以CPU就切换到VENC_GetVencStreamProc线程,VENC_GetVencStreamProc线程在调用select函数时,如果读文件集合read_fds有要读的内容则会往下执行,如果没有VENC_GetVencStreamProc线程也会休眠,CPU会切换到别的线程,当read_fds有要读的内容内容时或超时VENC_GetVencStreamProc线程会唤醒,往下执行去进行读码流的处理过程。

程序写完后,在sample文件夹里创建个子文件夹如myvenc,然后将这个文件保存到myvenc里,从sample\venc里复制makefile到sample\myvenc里,这个makefile都不要我们改些什么东西,只要位置放对了位置(跟源代码文件在同一个目录里)就直接可以用,然后make clean,make编译,如果没问题的话会生成myvenc可执行文件,将这个可执行文件拷贝到HI3518开发板(可通过SD卡从PC拷贝到开发板,也可挂载根文件系统直接复制过去,方法多样),然后执行./myvenc就会运行程序,过一会就会在myvenc同级目录下生成一个以时间为名的,后缀名为h264的文件,这个文件就是H264码流保存的文件,将这文件从Hi3518里复制到PC机上,用VLC播放器就可以播放出摄像头拍摄的内容。

编译运行程序后生成了h264文件,这个文件及程序运行时打印的输出内容有助于我们分析h264,下一篇我们将分析h264以便后期怎么将h264数据打包到rtp中进行rtsp传送数据。

这个程序比较简单,只是为了让大家了解海思芯片是怎么进行多媒体开发,所以很多功能没有写成函数,感觉内容很多,但避免了sample里复杂的函数调用,有助于将了解各个数据结构及海思SDK各个函数的用法。程序从前往后看,流程一目了然,各个数据结构、函数对应哪个模块很清楚,对入门很有好处。我将代码上传到github,欢迎朋友们下载,一起讨论学习。

最后看一下程序运行效果,用VLC打开H264文件,可以看到摄像头拍摄界面:

项目源代码及工具:Hi3518编码H264

1.1 海思3518 H264编码相关推荐

  1. C# WinForm 调用海思 H264 解码库进行解码

    最近做视频监控监控项目,学习了一下如何在 C# WinForm 下进行 H264 解码.下面贴一下代码,让大家了解一下如何使用海思的 H264 解码库进行解码,以方便其他有需要的人使用. 1.首先根据 ...

  2. 海思3518E开发笔记2.5——海思VI(video input)模块详解

    目录 海思video input模块架构介绍 海思video input模块功能介绍 结构体说明 函数调用关系 流程分析 step 1: mipi configure step 2: configur ...

  3. 海思Hi3519A 进行4k60 h264编码帧率不足的问题

    <!---title:海思Hi3519A 进行4k60 h264编码帧率不足的问题--> <!---keywords:海思,hi3519A, 4k60, h264--> 原始引 ...

  4. 海思HI35xx平台软件开发快速入门之H264解码实例

    前言 H264视频编码技术诞生于2003年,至今已有十余载,技术相当成熟,它的优势在于有高的视频的压缩率,利用帧间和帧内预测(Estimation).变换(Transform)和反变换.量化(Quan ...

  5. 海思SDK学习(7)海思媒体处理软件平台MMP(6)视频编码VENC

    视频编码 1.概述 VENC模块,即视频编码模块.本模块支持多路实时编码,且每路编码独立,编码协议和编码profile 可以不同.本模块支持视频编码同时,调度Region 模块对编码图像内容进行叠加和 ...

  6. 建立live555海思编码推流服务

    因项目需要,这一周弄了一下live555.需求:海思编码-->RTSP server,使用VLC可以访问,类似于网络摄像机的需求.看了一下,live555的架构太复杂了,半桶水的C++水平还真的 ...

  7. Python解码H.264(二)——将海思处理器编码的H.264裸码流转换为jpg序列

    上一篇我们历尽千辛万苦完成了PyAV的安装,这一篇我们来小试牛刀. 思路:通过PyAV来把海思Hi3516编码处理器编码的h.264裸码流中的每一帧都转换为一张jpeg编码的图片,然后将这些jpg图像 ...

  8. 的有效 海思编码_【最佳案例展示】2020年CUVA“超高清视频创新产品与解决方案”全球首款8K@120解码芯片海思Hi3796CV300...

     最佳奖:上海海思技术有限公司<全球首款8K@120解码芯片-海思Hi3796CV300> 案例概述 8K从摄像.制作.编码到传输.解码.显示,全链条的产业化正在加快成熟, 国家超高清/8 ...

  9. 海思3559万能平台搭建:获取数据帧修改后编码

    前言   有了这么长的铺垫和反复的啃sample,现在开始搭建自己的平台就底气多了,倒也不至于万能平台哈哈,只是在完成配置文件的功能后,可以不用改代码重新编译,就可以实现多场景多平台多功能下的使用了 ...

最新文章

  1. 【码书】一本经典且内容全面算法书籍,学算法必备
  2. LNMP--Nginx的日志切割
  3. JavaBean技术
  4. 如何使用 Docker 部署一个基于 Play Framework 的 Scala Web 应用?
  5. 一文彻底明白linux中的selinux到底是什么
  6. Oracle 10g新特性——正则表达式(转)
  7. mvp+dagger2_Android MVP + Dagger2 +改造+ RxJava
  8. 蓝牙学习笔记(九)——BLE超过20字节数据包传输(MTU)
  9. 存储单位 KB MB bit
  10. Java变量的默认值和初始化
  11. VM14无法将网络更改为桥接状态:没有未桥接的主机网络适配器
  12. 13.节点操作上,下
  13. 萌翻!超女纪敏佳产子并幸福晒照 男宝七斤一两超可爱
  14. Python爬虫进阶必备 | X天下与XX二手房密码加密分析
  15. python可以爬取wind数据库吗_如何利用Python来爬取近百万条数据?数据库会炸吧?...
  16. [天池最新比赛] 云上进化”2022全球AI生物智药大赛” #¥150000
  17. 哥:我要嫁给你!(让人巨感动)
  18. 不错嘛(lsp专用)
  19. 中文科技论文写作技巧总结
  20. Android Instant App调研报告

热门文章

  1. 【洋桃电子】STM32入门100步-03
  2. 计算机工业革命是,计算机工业革命是第几次科技革命
  3. android漏洞检测工具,安卓“超级拒绝服务漏洞”分析及自动检测工具
  4. C语言程序之经典习题:请编程序将“China”译成密码,密码规律是:用原来的字母后面第4个字母代替原来的字母。
  5. ROS Qt5 librviz人机交互界面开发四(添加rviz显示界面)
  6. 深入Java Servlets网络编程 西安电子科技大学出版社
  7. echarts3D地图塌陷问题
  8. 中国十大美女最多学校
  9. 【苹果】Apple Store 更换ID教程
  10. 使用PointNets在雷达数据中进行二维汽车检测