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

前言
html

  海思平台开发MPEG视频解码须要有HIMPP解码库API相关的知识,若是不具有基础的背景知识,请移步个人博文《海思HI35xx平台软件开发快速入门之背景知识》了解海思平台开发的基本步骤和相关知识基础。遵循海思平台软件开发架构,咱们一步一步实现一个MPEG视频解码实例。先科普一下什么是MPEG视频,MPEG(Moving Picture Experts Group,动态图像专家组)是ISO成立针对运动图像和语音压缩制定国际标准的组织,MPEG标准主要有如下五个,MPEG-一、MPEG-二、MPEG-四、MPEG-7及MPEG-21等。目前应用较多的是MPEG-4,咱们熟悉的DVD就广泛使用了MPEG-4解码标准,反过来讲DVD的普及使用也促成了MPEG-4解码标准的知名度。目前MPEG4在数字电视、实时多媒体监控、低比特率下的移动多媒体通讯、网络视频流与可视游戏、网络会议、交互多媒体应用、演播电视等领域应用普遍,更多相关MPEG的知识请参阅其官网。对于音视频开发者来讲,有必要学习了解一下,如何在海思平台上实现MPEG解码。linux

知识背景网络

  海思平台音视频编解码架构遵循下图所示的数据处理流程,咱们的MPEG解码实例要实现播放MPEG样例视频,故走的是HARD DISK->VDEC->VPSS->VO->显示器的流程,这个流程必定要熟悉牢记,代码实现都是围绕这条主线来编写的。架构

实例源码ide

  实例源码很简单,先来了解一下实现MPEG解码实例的几个函数,以达到了解实例源码大概构造组成,而后再对每一个函数进行具体分析。函数

/*
**函数描述:linux标准信号捕捉函数
**函数功用:退出HIMPP调用,销毁缓冲
*/
HI_VOID SAMPLE_VDEC_HandleSig(HI_S32 signo)
{
	......
}
/*
**函数描述:用于音视频文件读写推流
**函数功用:用fread等文件操做函数读取音视频文件,并解析后推送HIMPP进行解码
*/
int SAMPLE_COMM_VDEC_JPEG_SendStream( VdecThreadParam *pArgs)
{
	......
}
/*
**函数描述:HIMPP系统初始化
**函数功用:配置HIMPP系统的各项参数以知足对目标进行编解码
*/
HI_S32 SAMPLE_VDEC_VdhMpeg4(char *filename)
{
	......
}

/*
**主函数
*/
int main(int argc, char *argv[])
{
	......
}

   main函数讲解,main函数的完成的功能主要有两,一是对信号的初始化,信号的捕捉函数用来接收来之linux的内核消息,如进程退出等;二是将main函数的参数传递给MPEG解码样例函数。如下是main函数的详细内容:工具

int main(int argc, char *argv[])
{ 
	if(argc != 2)
	{
		printf("Usage: mpeg <vedio source filename>\n");
		exit(0);
	}
    signal(SIGINT, SAMPLE_VDEC_HandleSig);
    signal(SIGTERM, SAMPLE_VDEC_HandleSig);
    SAMPLE_VDEC_VdhMpeg4(argv[1]);
    return 0;
}

   下面重点讲解SAMPLE_VDEC_VdhMpeg4(char *filename)这个函数,它是MPEG解码样例的重点函数。HIMPP系统的API函数是海思提供的SDK开发包,调用它相关的接口,在编译时必须将其提供的相应库文件进行包含编译。下面结合样例程序讲述如何使用HIMPP提供的API实现本身的业务逻辑。MPEG视频解码实例走的是HARDDISK->VDEC->VPSS->VO->显示器流程,这个过程能够细分为八大步骤,这八大步骤在其余类型的音视频编解码样例也相似,能够说这八大步骤是使用海思HIMPP API的灵魂。下面简单介绍这个八大步骤的内容:oop

Step1:初始化HIMPP SYS和通用VB缓冲,包括设置缓冲区的大小,缓冲区块的数目。须要注意的是,在设置通用VB参数以前,必须确保HIMPP系统已经退出,不然设置失败。学习

Step2:设置通用缓冲区的公共缓冲池属性。spa

Step3:配置解码器,包括指定解码类型,这里是MPEG解码样例,固然选PT_MP4VIDEO啦,而后指定视频大小、解码优先级等等。而后建立解码通道,并是能加收解码流。

Step4:配置VPSS参数,VPSS是对VDEC解码后的流进行处理,如裁剪、降噪等,MPEG解码实例从简单应用出发,仅仅按默认的方式配置VPSS。

Step5:配置VO参数,这一步也很关键,由于它指定了画面输出,包括常见的HDMI和VGA,主要是配置输出显示,图层属性设置、输出位置等信息。

Step6:绑定VDEC与VPSS,实现MPEG解码流程。

Step7:绑定VPSS与VO,实现MPEG解码流程。

Step8:推送视频流数据,这一步须要文件读写配合使用。

  完整源码,请参阅SAMPLE_VDEC_VdhMpeg4(char*filename)源码,为了演示各个步骤须要配置的参数信息,很多变量须要时才定义,同时整个函数避免了使用没必要要的子函数调用,尽可能使用简单顺序结构的程序设计,以达到对这八大步骤一目了然。

HI_S32 SAMPLE_VDEC_VdhMpeg4(char *filename)
{
    VB_CONF_S stVbConf, stModVbConf;
    HI_S32 i, s32Ret = HI_SUCCESS;
	VdecThreadParam pstVdecSend;
    SIZE_S stSize;
    VO_PUB_ATTR_S stVoPubAttr;
    VO_VIDEO_LAYER_ATTR_S stVoLayerAttr;	
    stSize.u32Width = HD_WIDTH;
    stSize.u32Height = HD_HEIGHT;
	
    /************************************************
    step1:  init SYS and common VB 
    *************************************************/
	MPP_SYS_CONF_S stSysConf = {0};

	memset(&stVbConf,0,sizeof(stVbConf));
	stSize.u32Width = HD_WIDTH;//1920
    stSize.u32Height = HD_HEIGHT;//1080
	stVbConf.u32MaxPoolCnt = 64;
	stVbConf.astCommPool[0].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;
	stVbConf.astCommPool[0].u32BlkCnt	 = 15;
	memset(stVbConf.astCommPool[0].acMmzName,0,sizeof(stVbConf.astCommPool[0].acMmzName));
	
	//设置VB属性以前必须先退出HIMPP系统,不然设置失败。
	HI_MPI_SYS_Exit();    
    for(i=0;i<22;i++)
    {
         HI_MPI_VB_ExitModCommPool(i);
    }
    for(i=0; i<256; i++)
    {
         HI_MPI_VB_DestroyPool(i);
    }
    HI_MPI_VB_Exit();
	
	//设置VB属性
	s32Ret = HI_MPI_VB_SetConf(&stVbConf);
	if (HI_SUCCESS != s32Ret)
	{
		hidebug("HI_MPI_VB_SetConf failed!\n");
		return HI_FAILURE;
	}
	s32Ret = HI_MPI_VB_Init();
	if (HI_SUCCESS != s32Ret)
	{
		hidebug("HI_MPI_VB_Init failed!\n");
		return HI_FAILURE;
	}
	
	//设置HIMPP系统属性,并初始化
	stSysConf.u32AlignWidth = 16;//选择系统图形对齐方式,通常分辨率是16对齐的
	/*set config of mpp system*/
	s32Ret = HI_MPI_SYS_SetConf(&stSysConf);
	if (HI_SUCCESS != s32Ret)
	{
		hidebug("HI_MPI_SYS_SetConf failed!\n");
		return HI_FAILURE;
	}
	s32Ret = HI_MPI_SYS_Init();		
	if (HI_SUCCESS != s32Ret)
	{
		hidebug("HI_MPI_SYS_Init failed!\n");
		return HI_FAILURE;
	}
    /************************************************
    step2:  init mod common VB
    *************************************************/
 	memset(&stModVbConf, 0, sizeof(VB_CONF_S));
	stModVbConf.u32MaxPoolCnt = 64;
	stModVbConf.astCommPool[0].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;;
	stModVbConf.astCommPool[0].u32BlkCnt  = 15;
	
	stModVbConf.astCommPool[1].u32BlkSize = (stSize.u32Width * stSize.u32Height* 4) >> 1;;
	stModVbConf.astCommPool[1].u32BlkCnt  = 15;

	//设置模块公共缓冲池属性
	HI_MPI_VB_ExitModCommPool(VB_UID_VDEC);
	HI_MPI_VB_SetModPoolConf(VB_UID_VDEC, &stModVbConf);
	HI_MPI_VB_InitModCommPool(VB_UID_VDEC);
	

    /************************************************
    step3:  start VDEC
    *************************************************/
	VDEC_CHN_ATTR_S stVdecChnAttr;

    stVdecChnAttr.enType       = PT_MP4VIDEO; //设置解码方式 PT_JPEG  PT_MJPEG
    stVdecChnAttr.u32BufSize   = 3 * stSize.u32Width * stSize.u32Height;
    stVdecChnAttr.u32Priority  = 5;
    stVdecChnAttr.u32PicWidth  = stSize.u32Width;
    stVdecChnAttr.u32PicHeight =stSize.u32Height;

	stVdecChnAttr.stVdecVideoAttr.enMode = VIDEO_MODE_FRAME;
    stVdecChnAttr.stVdecVideoAttr.u32RefFrameNum = 2;
	stVdecChnAttr.stVdecVideoAttr.bTemporalMvpEnable = 0;

	//设置解码通道缓冲池VB的个数,这里只需建立一个解码通道
	HI_MPI_VDEC_SetChnVBCnt(0, 10);
	//建立解码通道
	HI_MPI_VDEC_CreateChn(0, &stVdecChnAttr);
	//解码器开始接收用户推流
	HI_MPI_VDEC_StartRecvStream(0);

    /************************************************
    step4:  start VPSS
    *************************************************/
	VPSS_GRP_PARAM_S stVpssParam = {0};
	VPSS_CHN_ATTR_S stChnAttr = {0};
	VPSS_GRP_ATTR_S stVpssGrpAttr;
	
	stVpssGrpAttr.enDieMode = VPSS_DIE_MODE_NODIE;
    stVpssGrpAttr.bIeEn     = HI_FALSE;
    stVpssGrpAttr.bDciEn    = HI_TRUE;
    stVpssGrpAttr.bNrEn     = HI_FALSE;
    stVpssGrpAttr.bHistEn   = HI_FALSE;
	stVpssGrpAttr.bEsEn	  = HI_FALSE;
    stVpssGrpAttr.enPixFmt  = PIXEL_FORMAT_YUV_SEMIPLANAR_420;
    stVpssGrpAttr.u32MaxW   = 1920;//stSize.u32Width;
    stVpssGrpAttr.u32MaxH   = 1080;//stSize.u32Height;

	//建立一个VPSS GROUP  0是一个组号
	s32Ret = HI_MPI_VPSS_CreateGrp(0, &stVpssGrpAttr);
	if (s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_CreateGrp failed!\n");
		return HI_FAILURE;
	}
	//获取GROUP 0的属性 PS:实用技巧,对于未知的属性,咱们通常先获取再设置
	s32Ret = HI_MPI_VPSS_GetGrpParam(0, &stVpssParam);	   
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_GetGrpParam failed!\n");
		return HI_FAILURE;
	}
	stVpssParam.u32IeStrength = 0;
	//设置GROUP 0的属性
	s32Ret = HI_MPI_VPSS_SetGrpParam(0, &stVpssParam);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_GetGrpParam failed!\n");
		return HI_FAILURE;
	}
	/*** enable vpss chn, with frame ***/
	/* Set Vpss Chn attr */
    stChnAttr.bSpEn = HI_FALSE;
    stChnAttr.bUVInvert = HI_FALSE;
    stChnAttr.bBorderEn = HI_TRUE;
    stChnAttr.stBorder.u32Color = 0xffffff;
    stChnAttr.stBorder.u32LeftWidth = 2;
    stChnAttr.stBorder.u32RightWidth = 2;
    stChnAttr.stBorder.u32TopWidth = 2;
    stChnAttr.stBorder.u32BottomWidth = 2;
	
	//设置VPSS通道设置属性
	s32Ret = HI_MPI_VPSS_SetChnAttr(0, 0, &stChnAttr);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_SetChnAttr failed!\n");
		return HI_FAILURE;
	}
	//启动VPSS通道
	s32Ret = HI_MPI_VPSS_EnableChn(0, 0);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_EnableChn failed!\n");
		return HI_FAILURE;
	}
	//启动VPSS GROUP
	s32Ret = HI_MPI_VPSS_StartGrp(0);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VPSS_StartGrp failed!\n");
		return HI_FAILURE;
	}
	
    /************************************************
    step5:  start VO
    *************************************************/
	VO_CHN_ATTR_S stChnAttr1;

	stVoPubAttr.enIntfSync = VO_OUTPUT_1080P60;
    stVoPubAttr.enIntfType = VO_INTF_VGA | VO_INTF_HDMI;
	//配置视频输出设备公共属性
	s32Ret = HI_MPI_VO_SetPubAttr(0, &stVoPubAttr);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VO_SetPubAttr failed!\n");
		return HI_FAILURE;
	}
	//启用解码通道0
	s32Ret = HI_MPI_VO_Enable(0);
	if (s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VO_Enable failed!\n");
		return HI_FAILURE;
	}
	stVoLayerAttr.u32DispFrmRt = 60;//显示帧率
	stVoLayerAttr.stDispRect.u32Width = 1920;//显示分辨率
	stVoLayerAttr.stDispRect.u32Height = 1080;
	stVoLayerAttr.stImageSize.u32Width = stVoLayerAttr.stDispRect.u32Width;//视频层画布大小
    stVoLayerAttr.stImageSize.u32Height = stVoLayerAttr.stDispRect.u32Height;
   	stVoLayerAttr.bClusterMode = HI_FALSE;
   	stVoLayerAttr.bDoubleFrame = HI_FALSE;
    stVoLayerAttr.enPixFormat = PIXEL_FORMAT_YUV_SEMIPLANAR_420; //视屏层使用的像素格式
	//设置视频层属性
	s32Ret = HI_MPI_VO_SetVideoLayerAttr(0, &stVoLayerAttr);
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VO_SetVideoLayerAttr failed!\n");
		return HI_FAILURE;
	}
	//启动视频层
	s32Ret = HI_MPI_VO_EnableVideoLayer(0);
	if (s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_VO_EnableVideoLayer failed!\n");
		return HI_FAILURE;
	}
	/*
	if (HI_SUCCESS != SAMPLE_COMM_VO_HdmiStart(stVoPubAttr.enIntfSync))
    {
        hidebug("Start SAMPLE_COMM_VO_HdmiStart failed!\n");
    }
	*/
	stChnAttr1.stRect.s32X		= 0;//通道显示区域
	stChnAttr1.stRect.s32Y		= 0;
	stChnAttr1.stRect.u32Width	= 1920;//通道显示大小
	stChnAttr1.stRect.u32Height	= 1080;
	stChnAttr1.u32Priority		= 0;//通道优先级
	stChnAttr1.bDeflicker		= HI_FALSE;//是否开抗闪烁

	//配置视频输出通道属性
	s32Ret = HI_MPI_VO_SetChnAttr(0, 0, &stChnAttr1);
	if (s32Ret != HI_SUCCESS)
	{
		hidebug("failed with %#x!\n", s32Ret);	
	}
	//启动视频输出通道
	s32Ret = HI_MPI_VO_EnableChn(0,0);
	if (s32Ret != HI_SUCCESS)
	{
		hidebug("failed with %#x!\n", s32Ret);
		 
	}
    /************************************************
    step6:  VDEC bind VPSS
    *************************************************/
    MPP_CHN_S stSrcChn;
   	MPP_CHN_S stDestChn;

  	stSrcChn.enModId = HI_ID_VDEC;
   	stSrcChn.s32DevId = 0;
    stSrcChn.s32ChnId = 0;

    stDestChn.enModId = HI_ID_VPSS;
    stDestChn.s32DevId = 0;
    stDestChn.s32ChnId = 0;
	
	//解码器绑定VPSS
	s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);	 
	if(s32Ret != HI_SUCCESS)
	{
	   	hidebug("HI_MPI_SYS_Bind failed!\n");
		return HI_FAILURE;
	}
		
    /************************************************
    step7:  VPSS bind VO
    *************************************************/
	stSrcChn.enModId = HI_ID_VPSS;
    stSrcChn.s32DevId = 0;
    stSrcChn.s32ChnId = 0;

    stDestChn.enModId = HI_ID_VOU;
   	stDestChn.s32DevId = 0;
   	stDestChn.s32ChnId = 0;
	
	//VPSS绑定VO
	s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);	 
	if(s32Ret != HI_SUCCESS)
	{
	    hidebug("HI_MPI_SYS_Bind failed!\n");
		return HI_FAILURE;
	}

    /************************************************
    step8:  send stream to VDEC
    *************************************************/    
    sprintf(pstVdecSend.cFileName,filename);
    pstVdecSend.s32MilliSec     = 0;
    pstVdecSend.s32ChnId        = 0;
    pstVdecSend.s32IntervalTime = 1;
    pstVdecSend.u64PtsInit      = 0;
    pstVdecSend.u64PtsIncrease  = 0;
    pstVdecSend.eCtrlSinal      = VDEC_CTRL_START;
    pstVdecSend.bLoopSend       = HI_TRUE;
    pstVdecSend.bManuSend       = HI_FALSE;
    pstVdecSend.enType          = PT_MP4VIDEO;
    pstVdecSend.s32MinBufSize   = 32000000;//(stVdecChnAttr.u32PicWidth *stVdecChnAttr.u32PicHeight * 3)>>1;
	pstVdecSend.s32StreamMode = VIDEO_MODE_FRAME;
	SAMPLE_COMM_VDEC_JPEG_SendStream(&pstVdecSend);//推送视频流

    return	s32Ret;
}

  SAMPLE_COMM_VDEC_JPEG_SendStream(VdecThreadParam *pArgs)函数实现的功能是读取文件,解析文件的视频帧头(I帧),而后推送至解码器。这一函数实现的功能简单,可是涉及解析视频帧头等内容,代码量有点多,不适合粘贴出来。SAMPLE_VDEC_HandleSig(HI_S32 signo)函数主要做用就是退出程序时,对HIMPP系统资源进行释放,不然显示器上会残留视频图像,并影响下一次使用HIMPP提供的API。

HI_VOID SAMPLE_VDEC_HandleSig(HI_S32 signo)
{
	 HI_S32 i;
    if (SIGINT == signo || SIGTSTP == signo || SIGTERM == signo)
    {
		HI_MPI_SYS_Exit();
		for(i=0;i<VB_MAX_USER;i++)
		{
			HI_MPI_VB_ExitModCommPool(i);
		}
		for(i=0; i<VB_MAX_POOLS; i++)
		{
			HI_MPI_VB_DestroyPool(i);
		}	
		HI_MPI_VB_Exit();
        printf("\033[0;31mprogram exit abnormally!\033[0;39m\n");
    }
    exit(0);
}

总结

  MPEG解码实例参考了海思提供的样例及库,程序源码及相关库文件请点击这里,修改不一样的编译链工具,便可在不一样HI35XX系列平台运行,整个MPEG解码实例提供了最简单的解码实现方式,固然还能够实现快进播放、暂停播放、等经常使用的视频播放控制逻辑,这须要读者进一步摸索。