前言
海思平台开发MPEG视频解码需要有HIMPP解码库API相关的知识,如果不具备基础的背景知识,请移步我的博文《海思HI35xx平台软件开发快速入门之背景知识》了解海思平台开发的基本步骤和相关知识基础。遵循海思平台软件开发架构,我们一步一步实现一个MPEG视频解码实例。先科普一下什么是MPEG视频,MPEG(Moving Picture Experts Group,动态图像专家组)是ISO成立针对运动图像和语音压缩制定国际标准的组织,MPEG标准主要有以下五个,MPEG-1、MPEG-2、MPEG-4、MPEG-7及MPEG-21等。目前应用较多的是MPEG-4,我们熟悉的DVD就普遍使用了MPEG-4解码标准,反过来说DVD的普及使用也促成了MPEG-4解码标准的知名度。目前MPEG4在数字电视、实时多媒体监控、低比特率下的移动多媒体通信、网络视频流与可视游戏、网络会议、交互多媒体应用、演播电视等领域应用广泛,更多相关MPEG的知识请参阅其官网。对于音视频开发者来说,有必要学习了解一下,如何在海思平台上实现MPEG解码。
知识背景
海思平台音视频编解码架构遵循下图所示的数据处理流程,我们的MPEG解码实例要实现播放MPEG样例视频,故走的是HARD DISK->VDEC->VPSS->VO->显示器的流程,这个流程一定要熟悉牢记,代码实现都是围绕这条主线来编写的。
实例源码
实例源码很简单,先来了解一下实现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的灵魂。下面简单介绍这个八大步骤的内容:
Step1:初始化HIMPP SYS和通用VB缓冲,包括设置缓冲区的大小,缓冲区块的数目。需要注意的是,在设置通用VB参数之前,必须确保HIMPP系统已经退出,否则设置失败。
Step2:设置通用缓冲区的公共缓冲池属性。
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解码实例提供了最简单的解码实现方式,当然还可以实现快进播放、暂停播放、等常用的视频播放控制逻辑,这需要读者进一步摸索。