hi3516 mpp SAMPLE_VENC_1080P_CLASSIC实例代码分析


原文链接: hi3516 mpp SAMPLE_VENC_1080P_CLASSIC实例代码分析

海思媒体(mmp)处理平台架构
海思媒体处理平台的主要内部处理流程如图 1-2 所示,主要分为视频输入(VI)、视频处理(VPSS)、视频编码(VENC)、视频解码(VDEC)、视频输出(VO)、视频侦测分析(VDA)、音频输入(AI)、音频输出(AO)、音频编码(AENC)、音频解码(ADEC)、区域管理(REGION)等模块。主要的处理流程介绍如下:

VI 模块捕获视频图像,可对其做剪切、缩放等处理,并输出多路不同分辨率的图像数据。解码模块对编码后的视频码流进行解码,并将解析后的图像数据送 VPSS 进行图像处理或直接送 VO 显示。可对 H264/MPEG4/MPEG2 格式的视频码流进行解码。

三、打开vi设备和通道进行捕捉
设置好sensor的型号,翻转,镜像宽动态等信息
1、mipi接口设置(打开sensor,填充sensor属性,操作sensor)
2、配置sensor和isp(执行sensor_register_callback,注册3A,给isp分配内存,设置isp属性,设置完成后进行isp初始化)
3、运行isp线程
4、配置打开vi的dev (设置设备属性翻转、镜像、宽动态、白平衡等后打开设备)

5、配置打开vi的chn(设置好宽高,翻转后,打开设备通道)

VPSS 模块接收 VI 和解码模块发送过来的图像,可对图像进行去噪、图像增强、锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍。
编码模块接收 VI 捕获并经 VPSS 处理后输出的图像数据,可叠加用户通过 Region模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。
VDA 模块接收 VI 的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析结果。
VO 模块接收 VPSS 处理后的输出图像,可进行播放控制等处理,最后按用户配置的输出协议输出给外围视频设备。
AI 模块捕获音频数据,然后 AENC 模块支持按多种音频协议对其进行编码,最后输出音频码流。

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

下面的代码我把不会用到的代码删除了,这样更简洁,为了简介,下面我只做一路输出,只选择APTINA_AR0130_DC_720P_30FPS为视频输出格式

int main(int argc, char *argv[])
{
    HI_S32 s32Ret;
    signal(SIGINT, SAMPLE_VENC_HandleSig);//ctrl+c,delete
    signal(SIGTERM, SAMPLE_VENC_HandleSig);//ctrl+\
 
    /* H.264@1080p@30fps+H.265@1080p@30fps+H.264@D1@30fps */
    s32Ret = SAMPLE_VENC_1080P_CLASSIC();
 
    if (HI_SUCCESS == s32Ret)
        printf("program exit normally!\n");
    else
        printf("program exit abnormally!\n");
    exit(s32Ret);
}

SAMPLE_VENC_HandleSig:主要做系统的回收,ISP线程的退出,ISP的停止

SAMPLE_VENC_1080P_CLASSIC:H.264@1080p@30fps+H.265@1080p@30fps+H.264@D1@30fps这几种情况的处理

由于海思的应用程序启动 MPP 业务前,必须完成 MPP 系统初始化工作。同理,应用程序退出MPP 业务后,也要完成 MPP 系统去初始化工作,释放资源。

1.初始化工作主要为:

  1. 视频缓存池的设置
  2. 各个模块之间的绑定
  3. 设置VI/VPSS 离/在线模式

可以参考:https://blog.csdn.net/qq_40732350/article/details/87970605

在H264_Venc()函数里先去初始化MPI系统,然后根据摄像头设置参数。我这里是AR030摄像头,其拍摄图片大小为720P,编码为H264,为了简单只输出一路。在初始化MPI系统时要计算视频缓存池VB_CONF_S的大小,然后填充VB_CONF_S结构体。填充完VB_CONF_S结构体后再用HI_MPI_VB_SetConf设置,最后初始化视频视频缓存池HI_MPI_VB_Init。这里注意的是基本上每个函数调用后都会判断返回值并作出错出理,后面很多函数也是这样处理的,这样利于程序排错
 

第一步:初始化视频缓冲池和系统MPP初始化

	//为了简单点,编码类型就选择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; // 缓存池vb参数结构体
	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;//(宽+压缩头 stride) * 高 * 1.5   参考MPP手册94
	stVbConf.astCommPool[0].u32BlkCnt = g_u32BlkCnt; //4个缓冲块
	
	//设置缓冲池
    HI_MPI_SYS_Exit();
    HI_MPI_VB_Exit();
	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;
	}
	
	//设置系统属性
    MPP_SYS_CONF_S stSysConf = {0};
    HI_S32 s32Ret = HI_FAILURE;
    stSysConf.u32AlignWidth = SAMPLE_SYS_ALIGN_WIDTH; //64
    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();
    if (HI_SUCCESS != s32Ret)
    {
        SAMPLE_PRT("HI_MPI_SYS_Init failed!\n");
        return HI_FAILURE;
    }

第二步:初始化视频输入(VI)模块 
ISP参考:

6 海思Hi3518E的ISP及其3A

主要做了下面的工作:

设置了mipi,摄像头用的是MIPI,应用层只需要用open打开MIPI,用ioctl设置
注册sensor和isp,和设置3A
运行isp线程
设置VI的属性,并使能VI
设置通道属性,和开启通道

    stViConfig.enViMode   = SENSOR_TYPE;  //APTINA_AR0130_DC_720P_30FPS
    stViConfig.enRotate   = ROTATE_NONE;  //旋转多少度
    stViConfig.enNorm     = VIDEO_ENCODING_MODE_AUTO;  //编码模式自动
    stViConfig.enViChnSet = VI_CHN_SET_NORMAL; //是否开启镜像
    stViConfig.enWDRMode  = WDR_MODE_NONE; //没有WDR
    /******************************************
     step 1: 配置mipi
    ******************************************/
    HI_S32 fd;
    combo_dev_attr_t *pstcomboDevAttr = NULL;
 
    /* mipi reset unrest */
    fd = open("/dev/hi_mipi", O_RDWR);
    if (fd < 0)
    {
        printf("warning: open hi_mipi dev failed\n");
        return -1;
    }
	pstcomboDevAttr = &MIPI_CMOS3V3_ATTR;
	if (ioctl(fd, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr))
    {
        printf("set mipi attr failed\n");
        close(fd);
        return HI_FAILURE;
    }
    close(fd);
	
    /*****************************************
    *step 2: 注册sensor and ISP (include WDR mode).
    *note: you can jump over this step, if you do not use Hi3516A interal isp. 
    ******************************************/
    s32Ret = SAMPLE_COMM_ISP_Init(pstViConfig->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;
    }
    /******************************************
    *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;
    }
    /******************************************************
    *step 4 : config & start vicap dev
    ******************************************************/
    s32Ret = SAMPLE_COMM_VI_StartDev(0, enViMode);
    if (HI_SUCCESS != s32Ret)
    {
        SAMPLE_PRT("%s: start vi dev[%d] failed!\n", __FUNCTION__, i);
        return HI_FAILURE;
    }
    /******************************************************
    * Step 5: config & start vicap chn (max 1) 
    ******************************************************/
    RECT_S stCapRect;
    SIZE_S stTargetSize;
    ViChn = 0;
    stCapRect.s32X = 0;
    stCapRect.s32Y = 0;
    stCapRect.u32Width = 1280;
    stCapRect.u32Height = 720;
 
    stTargetSize.u32Width = stCapRect.u32Width;
    stTargetSize.u32Height = stCapRect.u32Height;
    //通道使能
    s32Ret = SAMPLE_COMM_VI_StartChn(ViChn, &stCapRect, &stTargetSize, pstViConfig);
    if (HI_SUCCESS != s32Ret)
    {
        SAMPLE_COMM_ISP_Stop();
        return HI_FAILURE;
    }

第三步:设置VPSS
在sample.c中3个不同分辨率的图像共用一个GROUP(GROUP = 0),分别占用一个物理通道(CHANNEL = 0,1,2)。

参考:7 海思Hi3518E的视频处理子系统(VPSS)

对于函数:HI_MPI_VPSS_CreateGrp

离线模式时,可创建多个 GROUP,最大 GROUP 数为 VPSS_MAX_GRP_NUM;
在线模式时,仅支持创建 1 个 GROUP,且 GROUP 号仅能为 0。
不支持重复创建。
在线模式时,由于 VI 和 VPSS 的逻辑处理需要时序严格同步,所以 GROUP 创建中的 group 的图像属性必须和 VI 的图像设置属性一致;否则会出现 VPSS 的中断错误。具体请参见 VPSS_GRP_ATTR_S。

    /******************************************
    *start vpss and vi bind vpss
    ******************************************/
    SIZE_S stSize;
    pstSize->u32Width  = 1280;
    pstSize->u32Height = 720;
    VpssGrp = 0;
    stVpssGrpAttr.u32MaxW = stSize.u32Width;   /*MAX width of the group*/
    stVpssGrpAttr.u32MaxH = stSize.u32Height;  /*MAX height of the group*/
	stVpssGrpAttr.bIeEn = HI_FALSE;            /*图像增强 enable*/
	stVpssGrpAttr.bNrEn = HI_TRUE;             /*噪声降低 enable*/
	stVpssGrpAttr.bHistEn = HI_FALSE;          /*Hist enable*/
	stVpssGrpAttr.bDciEn = HI_FALSE;           /*动态对比改进 enable*/
	stVpssGrpAttr.enDieMode = VPSS_DIE_MODE_NODIE;/*反交错 enable*/
	stVpssGrpAttr.enPixFmt = PIXEL_FORMAT_YUV_SEMIPLANAR_420;/*像素格式*/
    
	/******************************
	*step1 : 启用 VPSS GROUP
	*******************************/
	s32Ret = HI_MPI_VPSS_CreateGrp(VpssGrp, pstVpssGrpAttr);//创建一个 VPSS GROUP
    if (s32Ret != HI_SUCCESS)
    {
        SAMPLE_PRT("HI_MPI_VPSS_CreateGrp failed with %#x!\n", s32Ret);
        return HI_FAILURE;
    }
	    /*** set vpss 3D NR(去噪) 属性 ***/
    s32Ret = HI_MPI_VPSS_GetNRParam(VpssGrp, &unNrParam);
    if (s32Ret != HI_SUCCESS)
    {
        SAMPLE_PRT("failed with %#x!\n", s32Ret);
        return HI_FAILURE;
    }
 
    s32Ret = HI_MPI_VPSS_SetNRParam(VpssGrp, &unNrParam);
    if (s32Ret != HI_SUCCESS)
    {
        SAMPLE_PRT("failed with %#x!\n", s32Ret);
        return HI_FAILURE;
    }
 
    s32Ret = HI_MPI_VPSS_StartGrp(VpssGrp);//启用 VPSS GROUP
    if (s32Ret != HI_SUCCESS)
    {
        SAMPLE_PRT("HI_MPI_VPSS_StartGrp failed with %#x\n", s32Ret);
        return HI_FAILURE;
    }
 
    /******************************
    *step2 : 绑定VI 和 VPSS
    *******************************/
    HI_S32 j, s32Ret;
    VPSS_GRP VpssGrp;
    MPP_CHN_S stSrcChn;
    MPP_CHN_S stDestChn;
    SAMPLE_VI_PARAM_S stViParam;
    VI_CHN ViChn;
 
    s32Ret = SAMPLE_COMM_VI_Mode2Param(enViMode, &stViParam);
    if (HI_SUCCESS !=s32Ret)
    {
        SAMPLE_PRT("SAMPLE_COMM_VI_Mode2Param failed!\n");
        return HI_FAILURE;
    }
    
    VpssGrp = 0;//VPSS GROUP 0
    ViChn = 0;//VI 通道0
        
	stSrcChn.enModId  = HI_ID_VIU;//模块VI的ID
	stSrcChn.s32DevId = 0;
	stSrcChn.s32ChnId = ViChn;
    
	stDestChn.enModId  = HI_ID_VPSS;//模块VPSS的ID
	stDestChn.s32DevId = VpssGrp;
	stDestChn.s32ChnId = 0;
    
	s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);//绑定VI和VPSS
	if (s32Ret != HI_SUCCESS)
	{
		SAMPLE_PRT("failed with %#x!\n", s32Ret);
		return HI_FAILURE;
	}
	
	/******************************
	*step3 : 前面设置了VPSS组,将VI通道与VPSS组里的0通道
	*绑定起来,但还未设置0通道,下面设置0通道。
	*******************************/
	VpssChn = 0;
	stVpssChnMode.enChnMode      = VPSS_CHN_MODE_USER; //设置通道为USER模式
	stVpssChnMode.bDouble        = HI_FALSE;           //字段帧传输,仅对VPSS_PRE0_CHN有效
	stVpssChnMode.enPixelFormat  = PIXEL_FORMAT_YUV_SEMIPLANAR_420;  //目标图像像素格式
	stVpssChnMode.u32Width       = stSize.u32Width;
	stVpssChnMode.u32Height      = stSize.u32Height;
	stVpssChnMode.enCompressMode = COMPRESS_MODE_SEG;  //输出的压缩模式,按照 256 bytes 为一段进行压缩。
	memset(&stVpssChnAttr, 0, sizeof(stVpssChnAttr));
	stVpssChnAttr.s32SrcFrameRate = -1;  //源通道帧率控制,源帧率与目标帧率都为-1,则不进行帧率控制。
	stVpssChnAttr.s32DstFrameRate = -1;  //目的通道帧率控制	
	
	//设置 VPSS 通道属性
	s32Ret = HI_MPI_VPSS_SetChnAttr(VpssGrp, VpssChn, pstVpssChnAttr);
	if (s32Ret != HI_SUCCESS)
	{
		SAMPLE_PRT("HI_MPI_VPSS_SetChnAttr failed with %#x\n", s32Ret);
		return HI_FAILURE;
	}
	
	//设置 VPSS 通道工作模式
	s32Ret = HI_MPI_VPSS_SetChnMode(VpssGrp, VpssChn, pstVpssChnMode);
	if (s32Ret != HI_SUCCESS)
	{
		SAMPLE_PRT("%s failed with %#x\n", __FUNCTION__, s32Ret);
		return HI_FAILURE;
	}   
	
	//启用 VPSS 通道
    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;
    }

第四步:设置编码器
 

	VENC_CHN_ATTR_S stVencChnAttr;
	VENC_ATTR_H264_S stH264Attr;
	VIDEO_NORM_E enNorm = VIDEO_ENCODING_MODE_NTSC;
	VENC_ATTR_H264_CBR_S stH264Cbr;  //定义 H.264 编码通道 CBR 属性结构
	SAMPLE_RC_E enRcMode = SAMPLE_RC_CBR;
	VpssGrp = 0;
	VpssChn = 0;
	VencChn = 0;
 
	SIZE_S stPicSize;
	stPicSize->u32Width  = 1280;
	stPicSize->u32Height = 720;
 
	/******************************************
	*step 1:  Create Venc Channel
	******************************************/
	stVencChnAttr.stVeAttr.enType = enType;
	stH264Attr.u32MaxPicWidth = stPicSize.u32Width;  //编码图像最大宽度
	stH264Attr.u32MaxPicHeight = stPicSize.u32Height;
	stH264Attr.u32PicWidth = stPicSize.u32Width;     //编码图像宽度
	stH264Attr.u32PicHeight = stPicSize.u32Height;
	stH264Attr.u32BufSize  = stPicSize.u32Width * stPicSize.u32Height;//码流 buffer 大小
	stH264Attr.u32Profile  = u32Profile;/*视频等级0: baseline; 1:MP; 2:HP;  3:svc_t */
	stH264Attr.bByFrame = HI_TRUE;/*帧/包模式获取码流 (1:按帧获取 0:按包获取)*/
	stH264Attr.u32BFrameNum = 0;/*编码支持 B 帧的个数 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== enNorm)?25:30;//H.264 gop 值
	stH264Cbr.u32StatTime       = 1;//CBR 码率统计时间,以秒为单位
	stH264Cbr.u32SrcFrmRate      = (VIDEO_ENCODING_MODE_PAL== enNorm)?25:30;//VI 输入帧率,以 fps 为单位
	stH264Cbr.fr32DstFrmRate = (VIDEO_ENCODING_MODE_PAL== enNorm)?25:30;//编码器输出帧率,以 fps 为单位
 
	stH264Cbr.u32BitRate = 1024*2;//平均 bitrate,以 kbps 为单位
	stH264Cbr.u32FluctuateLevel = 0;  //最大码率相对平均码率的波动等级
	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;
	}
 
	/******************************************
	*step 2:  开启编码通道接收输入图像
	******************************************/
	s32Ret = HI_MPI_VENC_StartRecvPic(VencChn);
	if (HI_SUCCESS != s32Ret)
	{
		SAMPLE_PRT("HI_MPI_VENC_StartRecvPic faild with%#x!\n", s32Ret);
		return HI_FAILURE;
	}
 
	/******************************************
	*step3 :  绑定VPSS通道和编码通道
	******************************************/
	s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);
	if (HI_SUCCESS != s32Ret)
	{
		SAMPLE_PRT("Start Venc failed!\n");
		goto END_VENC_1080P_CLASSIC_5;
	}

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

    gs_stPara.bThreadStart = HI_TRUE;
    gs_stPara.s32Cnt = s32Cnt;
 
    return pthread_create(&gs_VencPid, 0, SAMPLE_COMM_VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);
线程处理程序:
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是由参数传进来的,为1
	for (i = 0; i < s32ChnTotal; i++)
	{
		VencChn = i;
		s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);
 
		//这里是为了取得是什么编码类型,以便确定保存文件的后缀名
		//比如这里是H264编码,所以保存文件的后缀后就是.h264
		if(s32Ret != HI_SUCCESS)
		{
			SAMPLE_PRT("HI_MPI_VENC_GetChnAttr chn[%d] failed with %#x!\n", \
			VencChn, s32Ret);
			return NULL;
		}
 
		enPayLoadType[i] = stVencChnAttr.stVeAttr.enType;
		sprintf(aszFileName[i], "stream_chn0%d%s",filename, i, ".h264");//20171101085639.h264		
 
		pFile[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];
		}
	}
	
	//当main函数所在的线程接收到两个键盘字符或ctrl+c时,pstPara->bThreadStart会为假,跳出while循环
	//然后往下执行关闭前面打开的文件,执行完这个VENC_GetVencStreamProc线程函数,线程结束
	while (HI_TRUE == pstPara->bThreadStart)
	{
	/*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));
					//查询是否有码流,并将码流信息填充到stStat结构体中
					s32Ret = HI_MPI_VENC_Query(i, &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);
					//获取码流数据并保存到stStream结构体中
					s32Ret = HI_MPI_VENC_GetStream(i, &stStream, HI_TRUE);
					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 0
printf("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释放掉码流,释放掉申请的内存。
 

第六步:退出程序
通过设置 gs_stPara.bThreadStart 的值来终止线程,最后就是一些收尾工作,就没列出代码。

    printf("please press twice ENTER to exit this sample\n");
    getchar();
    getchar();
 
    if (HI_TRUE == gs_stPara.bThreadStart)
    {
        gs_stPara.bThreadStart = HI_FALSE;
        pthread_join(gs_VencPid, 0);
    }

函数调用图谱:

main

SAMPLE_VENC_1080P_CLASSIC
    SAMPLE_COMM_VI_GetSizeBySensor(step1)//init sys variable
    SAMPLE_COMM_SYS_CalcPicVbBlkSize    
        SAMPLE_COMM_SYS_GetPicSize
    SAMPLE_COMM_SYS_Init(step2)//mpp system init.
        HI_MPI_SYS_Exit();
        HI_MPI_VB_Exit();
        HI_MPI_VB_SetConf
        HI_MPI_VB_Init
        HI_MPI_SYS_SetConf
        HI_MPI_SYS_Init
    SAMPLE_COMM_VI_StartVi(step3)//start vi dev & chn to capture
        IsSensorInput
        SAMPLE_COMM_VI_StartIspAndVi
            SAMPLE_COMM_VI_StartMIPI(1)//mipi configure
                SAMPLE_COMM_VI_SetMipiAttr
                    fd = open("/dev/hi_mipi", O_RDWR);
                    ioctl(fd, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr)
            SAMPLE_COMM_ISP_Init(2)
                sensor_register_callback
                HI_MPI_AE_Register
                HI_MPI_AWB_Register
                HI_MPI_AF_Register
                HI_MPI_ISP_MemInit
                HI_MPI_ISP_SetWDRMode
                HI_MPI_ISP_SetPubAttr
                HI_MPI_ISP_Init
            SAMPLE_COMM_ISP_Run(3)
                pthread_create(&gs_IspPid, &attr, (void* (*)(void*))Test_ISP_Run, NULL)
                    Test_ISP_Run
                        HI_MPI_ISP_Run
            SAMPLE_COMM_VI_StartDev(4)
                HI_MPI_VI_SetDevAttr
                HI_MPI_ISP_GetWDRMode
                HI_MPI_VI_SetWDRAttr
                HI_MPI_VI_EnableDev
            SAMPLE_COMM_VI_StartChn(5)
                HI_MPI_VI_SetChnAttr
                HI_MPI_VI_SetRotate
                HI_MPI_VI_EnableChn                 
    SAMPLE_COMM_SYS_GetPicSize(step4)//start vpss and vi bind vpss
    SAMPLE_COMM_VPSS_StartGroup
        HI_MPI_VPSS_CreateGrp
        HI_MPI_VPSS_GetNRParam
        HI_MPI_VPSS_SetNRParam
        HI_MPI_VPSS_StartGrp
    SAMPLE_COMM_VI_BindVpss
        SAMPLE_COMM_VI_Mode2Param
        HI_MPI_SYS_Bind
    SAMPLE_COMM_VPSS_EnableChn
        HI_MPI_VPSS_SetChnAttr
        HI_MPI_VPSS_SetChnMode
        HI_MPI_VPSS_EnableChn
    SAMPLE_COMM_VENC_Start(step5)//start stream venc
        SAMPLE_COMM_SYS_GetPicSize
        HI_MPI_VENC_CreateChn
        HI_MPI_VENC_StartRecvPic
    SAMPLE_COMM_VENC_BindVpss
        HI_MPI_SYS_Bind
    SAMPLE_COMM_VENC_StartGetStream(step6)//stream venc process -- get stream, then save it to file.
        pthread_create(&gs_VencPid, 0, SAMPLE_COMM_VENC_GetVencStreamProc, (HI_VOID*)&gs_stPara);
    SAMPLE_COMM_VENC_StopGetStream(step7)//exit process

可以参考:

https://blog.csdn.net/zhanshenrui/article/details/79082157

https://blog.csdn.net/wytzsjzly/article/details/82500277

https://blog.csdn.net/taotongning/article/details/82427955

 

  1. ORTP
    #open RTP (RTP的一个开源实现)
    #视频在网络上的传输主要有两种:
    #(1).基于下载:http or ftp 要播放的话,先从服务器上下载到本地,比如视频网站播放视频,

        下载的速度可以赶得上播放的速度那就是实时的,网速慢就在那里缓冲,网速快缓冲比播放提前。

        基于下载的这种模式一般是为了保证视频的质量。

    #(2).基于实时:RTP/RTSP/RTCP 主要用于视频监控的相关领域。还有直播

        这种应用一般都是为了保证时间上的同步的应用场景。如果网速不够,牺牲的是画面质量。网速快的时候

        看到的是清晰实时的画面,网速慢的时候看到的是模糊实时的画面。

    #RTP(Real-time Transport Protocol)  可以用来传输语音、视频流等
    #RTSP(Real Time Streaming Protocol)专门用来传输视频流的
    #RTCP(RTP Control Protocol)用来控制用的,传输方与接收方的一个协调,RTCP是RTP的一个补充,因为RTP只传输,不能控制。
    #两种传输方式是没有好坏之分的,关键是你看的应用场景。

    #

  2. h.264的编码原理
    #图像的冗余信息:空间冗余,时间冗余,
    #视频编码的关键点:压缩比、算法的复杂度、还原度;求得一个平衡,压缩分为硬压缩,软压缩;3518E就是用了
    #一个硬件单元DSP来压缩,属于硬压缩。
    #H.264的2大组成部分:VCL和NAL  VCL关心的是视频的压缩,NAL关心的是这些被压缩后的视频流如何被网络传输到对方解码
    #h.264编码相关的一些概念
    #(1) 宏块 MB(macroblock) 表示的是一幅图像的一小块区域, 压缩都是以宏块为单位(不是以像素为单位),因为一个宏块里面的像素是有相似性的
    #(2) 片 slice 帧的一部分
    #(3) 帧 frame 有时候帧只有一个slice,有时候又有多个slice
    #像素组成宏块,宏块组成片,片组成帧,多个帧加起来组成一个序列,多个序列组成了一个码流
    #(4) I帧(非参考帧,只和自己有关,可以理解为图像的第一帧,之前没有参考,做不了时间冗余,只能做帧内压缩,及空间压缩)
    #(5) B帧(参考帧,相当于图像后面的帧,做了空间和时间冗余,压缩的时候前后帧都做了参考,可以这么理解,第二帧与第一帧非常相似,
    #不用记录内容,记录差异就行,这样所占用的空间就小了,还原的时候前后帧都要参考,再把差异修正了就行,谁像就多参考些,
    #因为在编码的时候前面后面的帧都已经出来了)
    #(6) P帧(参考帧,只参考了前一帧,算法复杂度没那么高)
    #I帧必须得有

    #帧率 fps

  3. NAL单元
    #NAL关系的是VCL的输出的纯视频流如何被表达和封包以利于网络传输
    #NAL部分出来的就是H.264的码流,这部分码流包括纯视频流和封包信息,封包的作用是利于网络传输和解码
    #SODB :String Of Date Bits VCL的输出的纯视频流
    #RBSP: Raw Byte Sequence Payload   在SODB基础上加上了封包(头尾信息)
    #NALU: Network Abstraction Layer Units  h.264里面就是一个一个的NALU
    #关系:SODB + RBSP trailing bits(头尾信息) = RBSP
    #NAL header(1 byte) + RBSP = NALU
    #做编码器的人关心的是VCL部分,做视频传输和解码播放的人关心的是NAL部分
    #雷神作品:SpecialVH264.exe
    #国外工具:Elecard StreamEye Tools
    #二级制工具:winhex
    #网络抓包工具:wireshark
    #播放器:vlc
    #海思平台编码出来的h.264码流都是一个个序列:包含1sps+1pps+1sei+1I帧+若干p帧
    #相关概念
    #序列 sequence ,每个sequence都有一个I帧,本sequence的I帧坏了顶多是丢弃本sequence,不会影响其他sequence.
    #一秒钟一个sequence,每一秒钟的第一个帧都是I帧,往下就有 帧率-1个P帧,每秒钟的sequence数等于帧率。
    #分隔符00 00 00 01在h.264的码流里面是有特殊含义的,表示有一个新的开始,分隔符不是有效数据,相当于房子的墙
    #00 00 00 01后的第一个数据是SPS,长度为14个字节,向后数14个字节后,又遇到分隔符00 00 00 01,分隔符后面是PPS
    #PPS长度为4个字节,然后是分隔符00 00 00 01,接下来是SEI,长度为5个字节,然后是分隔符00 00 00 01,接下来是IDR_SLICE(I帧),
    #然后是分隔符00 00 00 01,接下来就是P帧,依次类推。

    #如果码流数据有00 00 00 ,那么要转变成 00 00 03 00 避免和分隔符00 00 00 01冲突

  4. h.264中的profile和level
    #profile是对视频压缩等级或档次的描述,profile越高,就说明采用了越高级的压缩特性,越高级的压缩算法。压缩结果就越好,压缩算法的实现对硬件要求就比较高。
    #level是对视频本身特性的描述(码率、分辨率、fps)。Level越高,视频的码率、分辨率、fps越高。
    #在同一个profile里面,level是可以不一样的,比如大家都用的是最基础的profile(Base line Profile),最后得到的码率、分辨率、帧率也可以不一样。
    #level指的是图像本身的一些参数,profile指的是图像压缩算法的一些参数。
    #h.264 profile分为三个档次,分别为baseline profile(低配,硬件性能要求低)、main profile(主流)、high profile(高配,但是硬件性能要求比较高)。
    #1280*720@30f对应的level是3.1

    #程序中可以配置profile的

  5. sequence
    #一段h.264码流其实就是由多个sequence组成的
    #一个sequence持续一秒
    #每个sequence均有固定结构:1sps+1pps+1sei+1I帧+若干P帧
    #p帧的个数等于fps-1
    #sps和pps和sei描述该sequence的图像信息,这些信息有两个功能:网络传输和解码
    #I帧是关键,丢了I帧整个sequence就废了,每个sequence有且只有1个I帧(这只针对海思平台)
    #I帧越大则P帧可以越小,反之I帧越小则I帧会越大
    #I比较大说明I帧包括的信息比较详细,说明I帧的压缩比例没那么高
    #I帧越详细,P帧就越好参考,p帧就会越小。反之,I帧越小,说明I帧压缩的越狠,I帧本身就
    #不是很详细,那么I帧就得内容多一些,否则你I帧也小,P帧也小,图像肯定就不清晰。
    #I帧的大小取决于图像本身内容(图像比较丰富,可压缩的空间就比较小,反之,图像比较单一,可压缩的空间就比较大,I帧压缩与时间没有关系)和压缩算法
    #的空间压缩部分
    #P帧的大小取决于图像的变化剧烈程度,如果这一帧的相对于上一帧变化非常的小,那么这帧P帧就可以很小,反之,如果这一帧的相对于上一帧变化非常的大,
    #那么这帧P帧就很大。
    #视频码率就是数据传输时单位时间传送的数据位数,一般我们用单位是kbps。
    #CBR和VBR下P帧的大小策略会不同,CBR时P帧大小基本恒定,VBR时变化比较剧烈。
    #CRR下就是牺牲图像的清晰度,图像变化剧烈,清晰度就会下降,图像稳定,清晰度就会上升。

    #VBR的情况下是保存图像的清晰度,如果图像变化剧烈,码率就会极大增加,网速吃紧。

  6. RTSP
    ————————————————
    版权声明:本文为CSDN博主「QQ2651401966」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_40732350/article/details/88084864

`