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.初始化工作主要为:
- 视频缓存池的设置
- 各个模块之间的绑定
- 设置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
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只传输,不能控制。
#两种传输方式是没有好坏之分的,关键是你看的应用场景。#
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
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冲突
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的
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的情况下是保存图像的清晰度,如果图像变化剧烈,码率就会极大增加,网速吃紧。
RTSP
————————————————
版权声明:本文为CSDN博主「QQ2651401966」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40732350/article/details/88084864