裸机->C高级->uboot和系统移植->linux应用编程和网络编程、驱动
===========================================================================================================
1.
/* We just coyp this value of payload type from RTP/RTSP definition */
typedef enum
{
PT_PCMU = 0,
....... ....
PT_H265 = 265,
PT_MAX = 266,
/* add by hisilicon */
PT_AMR = 1001,
PT_MJPEG = 1002,
PT_AMRWB = 1003,
PT_BUTT
} PAYLOAD_TYPE_E;
#海思源码中,枚举类型都是以大写字母加下划线构成,并以E结尾,结构体是以S结尾
#这里面PT就是PAYLOAD_TYPE,PAYLOAD简单理解为载体或者载荷,可以这么理解,假设你在传输一个视频,你传输的这个视频是个什么类型的
#这就是这个视频的PAYLOAD_TYPE,你把RTSP视频传输看成是用饺子皮把饺子馅包起来运走,这个PAYLOAD_TYPE就是问你这个饺子是什么馅的。
#通过RTSP不仅能传H.264类型的,还可以传其他类型的,都列在枚举里面
#RTP/RTSP本来就是用来传输音视频的一些东西,音视频的编码种类特别多,那你传输的是怎样一种编码,你要用PAYLOAD_TYPE告诉别人
#PT_BUTT是结尾标识符,不能用
===========================================================================================================
2.
PAYLOAD_TYPE_E enPayLoad[3]= {PT_H264, PT_H264,PT_H264};
#这个变量enPayLoad以en打头就告诉你这个是enum类型的
#从这个数组有三个变量来看,要编3路视频
#三路都是H.264的,这里只是一个初始化,后面再去填充
===========================================================================================================
3.
VPSS_GRP_ATTR_S stVpssGrpAttr;
#以st打头的就是结构体变量,前的VPSS_GRP_ATTR_S就是一个结构体,全部大写,下划线分开,S结尾
===========================================================================================================
4.
typedef enum hiPIC_SIZE_E
{
PIC_QCIF = 0, //176*144
PIC_CIF, //352*288
PIC_2CIF,
PIC_HD1,
PIC_D1, //704*576
PIC_960H,
PIC_QVGA, /* 320 * 240 */
PIC_VGA, /* 640 * 480 */
PIC_XGA, /* 1024 * 768 */
PIC_SXGA, /* 1400 * 1050 */
PIC_UXGA, /* 1600 * 1200 */
PIC_QXGA, /* 2048 * 1536 */
PIC_WVGA, /* 854 * 480 */
PIC_WSXGA, /* 1680 * 1050 */
PIC_WUXGA, /* 1920 * 1200 */
PIC_WQXGA, /* 2560 * 1600 */
PIC_HD720, /* 1280 * 720 */
PIC_HD1080, /* 1920 * 1080 */
PIC_2304x1296, /* 3M:2304 * 1296 */
PIC_2592x1520, /* 4M:2592 * 1520 */
PIC_5M, /* 2592 * 1944 */
PIC_UHD4K, /* 3840 * 2160 */
PIC_12M, /* 4000 * 3000 */
PIC_BUTT
} PIC_SIZE_E;
#图片分辨率的
=============================================================================================================
5.
VB_CONF_S;定义了模型,点菜
HI_MPI_VB_SetConf; 把模型告诉了VB,点好菜交给服务员
HI_MPI_VB_Init;VB真正去执行分配了,服务员把点好的菜单交给后厨开始做菜
#顺序不能搞错
#可以有多个缓冲池,每个缓冲池又分多个缓存块
=============================================================================================================
6.
step 6: stream venc process -- get stream, then save it to file.
#可以把这个码流打包成一个MP4存储到你的硬盘里面去,这就是录像。
#也可以分包,分成一个一个的视频包通过RTSP传出去。
#也可以作为一个裸流直接丢到流文件里面。这个裸流文件必须通过像VLC这些可以解析流文件的播放器才可以观看。
#裸流是开发过程中的半成品,没有文件头,就得花时间去检测一下这幅图是多大,正常情况不会给别人提供裸流的
#正常的视频去分辨率都是直接从文件中提取的
=============================================================================================================
7.
step 1: init sys variable 指的是初始化MMP这个系统的变量
=============================================================================================================
8.
typedef struct hiVB_CONF_S
{
HI_U32 u32MaxPoolCnt; /* max count of pools, (0,VB_MAX_POOLS] */
struct hiVB_CPOOL_S
{
HI_U32 u32BlkSize; /*what the size of each block. */
HI_U32 u32BlkCnt; /*How many blocks of each pool*/
HI_CHAR acMmzName[MAX_MMZ_NAME_LEN];
}astCommPool[VB_MAX_COMM_POOLS];
} VB_CONF_S;
#u32BlkSize一帧图像的大小决定了一个缓冲块的大小
#u32BlkCnt 缓冲块的数量按道理来说越多越好,要适量分配,避免浪费。内存大你就多给点避免不够用的情况出现,内存不多就要合理安排
#step 1: init sys variable这一步是根据我们的实际来核算各路缓存池的BlkSize,BlkCnt,不是随便给的,
#在这一步整个MPP系统还没有启动
=============================================================================================================
9.
SAMPLE_COMM_VI_GetSizeBySensor(&enSize[0]);
#填充变量,命令表明函数的位置在sample/common目录下,意思是通过sensor来算得到的图像大小
#我们这里的SENSOR是720p的,这个函数出来后就变成720p了
#刚开始初始化时PIC_SIZE_E enSize[3] = {PIC_HD1080, PIC_VGA,PIC_QVGA};给的是这三路码流,经过我们初始化后的到一路720p了
=============================================================================================================
10.
SAMPLE_VI_MODE_E enMode = SENSOR_TYPE;
#SENSOR_TYPE是在sample目录下面Makefile.param配置的
################# select sensor type for your sample ####################
#SENSOR_TYPE ?= APTINA_9M034_DC_720P_30FPS
SENSOR_TYPE ?= SONY_IMX222_DC_1080P_30FPS
#SENSOR_TYPE ?= SONY_IMX222_DC_720P_30FPS
#SENSOR_TYPE ?= APTINA_AR0130_DC_720P_30FPS
#SENSOR_TYPE ?= PANASONIC_MN34222_MIPI_1080P_30FPS
#SENSOR_TYPE ?= APTINA_AR0230_HISPI_1080P_30FPS
#SENSOR_TYPE ?= OMNIVISION_OV9712_DC_720P_30FPS
#SENSOR_TYPE ?= OMNIVISION_OV9732_DC_720P_30FPS
#SENSOR_TYPE ?= OMNIVISION_OV9750_MIPI_720P_30FPS
#SENSOR_TYPE ?= OMNIVISION_OV9752_MIPI_720P_30FPS
#SENSOR_TYPE ?= OMNIVISION_OV2718_MIPI_1080P_25FPS
##########################################################################
===========================================================================================================
11.
if (PIC_HD1080 == enSize[0])
{
enSize[1] = PIC_VGA;
s32ChnNum = 2;
}
else if (PIC_HD720 == enSize[0])
{
enSize[1] = PIC_VGA;
enSize[2] = PIC_QVGA;
s32ChnNum = 3;
}
else
{
printf("not support this sensor\n");
return HI_FAILURE;
}
#最后我们得到的是720p,三路码流,enSize[0]=PIC_HD720 enSize[1] = PIC_VGA enSize[2] = PIC_QVGA
#PIC_HD720是主码流,PIC_VGA,PIC_QVGA是子码流,子码流的意思是通过对主码流裁剪,缩放等操作后得到的。
#子码流一般是通过手机来观看,手机屏幕毕竟小,没必要HD720,而且还流畅,节省带宽。
============================================================================================================
12.
stVbConf.u32MaxPoolCnt = 128;
#视频缓存池的个数是我们自己根据资源估算设置的,128是一个经验值
#不是实际设置了128个,而是设置了一个上限
============================================================================================================
13.
/*video buffer*/
if(s32ChnNum >= 1)
{
u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,\
enSize[0], SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH);
stVbConf.astCommPool[0].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[0].u32BlkCnt = g_u32BlkCnt;
} \\返回一帧图像所要的内存大小即u32VbSize,这个值就是block的大小-----第一路码流的公共缓存池
if(s32ChnNum >= 2)
{
u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,\
enSize[1], SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH);
stVbConf.astCommPool[1].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[1].u32BlkCnt =g_u32BlkCnt;
} \\---第二路码流的公共缓存池
if(s32ChnNum >= 3)
{
u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,\
enSize[2], SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH);
stVbConf.astCommPool[2].u32BlkSize = u32BlkSize;
stVbConf.astCommPool[2].u32BlkCnt = g_u32BlkCnt;
} \\---第三路码流的公共缓存池
#各路缓存池的BlkSize,BlkCnt是不一样的
#一个缓冲池其实就对应一路码流,
#为什么要这么分呢,是因为不同的码流分辨率不一样,耗费的内存的块block也不一样,这是为了避免浪费内存空间
#enSize[0]目前为PIC_HD720,
#SAMPLE_PIXEL_FORMAT为像素格式,比如RGB888,RGB565就不一样,RGB888 24b占三个字节,RGB565 16b占两个字节
#SAMPLE_SYS_ALIGN_WIDTH对齐
=============================================================================================================
14.
((PIXEL_FORMAT_YUV_SEMIPLANAR_422 == enPixFmt)?2:1.5));
#这里指的是平均一个像素占几个字节
#YUV422 4+2+2=8字节,4个像素一共占了8个字节,那一个像素平均占多少个字节8/4=2字节
#YUV420 4+2=6字节,4个像素一共占了6个字节,那一个像素平均占多少个字节6/4=1.5字节
=============================================================================================================
15.
u32VbSize += u32HeaderSize;
#最后还要加上头信息
=============================================================================================================
16.
VB_PIC_HEADER_SIZE(stSize.u32Width, stSize.u32Height, enPixFmt, u32HeaderSize);
u32VbSize += u32HeaderSize;
return u32VbSize;
#返回u32VbSize就知道考虑了所有的的余量之后一帧图像要多大内存
=============================================================================================================
17.
/******************************************
step 2: mpp system init.
******************************************/
s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("system init failed with %d!\n", s32Ret);
goto END_VENC_1080P_CLASSIC_0;
}
#s32Ret = SAMPLE_COMM_SYS_Init(&stVbConf);把参数&stVbConf传进来,stVbConf是一个结构体
#函数内部用了两个API
#这两个API分别为 s32Ret = HI_MPI_VB_SetConf(pstVbConf); s32Ret = HI_MPI_VB_Init();
=============================================================================================================
18.
/******************************************************************************
* function : vb init & MPI system init
******************************************************************************/
HI_S32 SAMPLE_COMM_SYS_Init(VB_CONF_S *pstVbConf)
{
MPP_SYS_CONF_S stSysConf = {0};
HI_S32 s32Ret = HI_FAILURE;
HI_MPI_SYS_Exit();
HI_MPI_VB_Exit();
if (NULL == pstVbConf)
{
SAMPLE_PRT("input parameter is null, it is invaild!\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_VB_SetConf(pstVbConf);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_VB_SetConf failed!\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_VB_Init();
#HI_MPI_SYS_Exit(); 和HI_MPI_VB_Exit(); 打扫场地用的,注意调用顺序,释放是SYS在前,VB在后
#建立的时候是先VB后SYS
#其实VB的参数要操点心,pstVbConf自己去算好,余下的都是模式化,
================================================================================================================
19.
//SAMPLE_PRT("w:%d, u32AlignWidth:%d\n", CEILING_2_POWER(stSize.u32Width,u32AlignWidth), u32AlignWidth);
u32VbSize = (CEILING_2_POWER(stSize.u32Width, u32AlignWidth) * \
CEILING_2_POWER(stSize.u32Height,u32AlignWidth) * \
((PIXEL_FORMAT_YUV_SEMIPLANAR_422 == enPixFmt)?2:1.5));
#CEILING_2_POWER(stSize.u32Width, u32AlignWidth) 这个宏的意思是stSize.u32Width向u32AlignWidth对齐
#怎么对齐,stSize.u32Width向上取整,u32AlignWidth是64,stSize.u32Width为1280,往上数,一直数到够第一个够64整除的数,意思就是往上留点余量
================================================================================================================
20.
PIXEL_FORMAT_YUV_SEMIPLANAR_420
#是怎么定成YUV420的
#其实是写APP的人用宏定义直接定义出来的,他怎么知道把它定成YUV420?
#define VB_PIC_HEADER_SIZE(Width, Height, Type, size)\
do{\
if (PIXEL_FORMAT_YUV_SEMIPLANAR_422 == Type || PIXEL_FORMAT_RGB_BAYER == Type )\
{\
size = VB_HEADER_STRIDE * (Height) * 2;\
}\
else if(PIXEL_FORMAT_YUV_SEMIPLANAR_420 == Type)\
{\
size = (VB_HEADER_STRIDE * (Height) * 3) >> 1;\
}\
else if(PIXEL_FORMAT_YUV_400 == Type)\
{\
size = VB_HEADER_STRIDE * (Height);\
}\
}while(0)
#PIXEL_FORMAT_RGB_BAYER指的是rawRGB,后面转成RGA,再到YUV,为什么不直接用RGB呢?
#是因为YUV这种方式表达颜色更加科学,所以最后不管你sensor这边出来是什么格式,一律转成YUV格式
#你想变成YUV422还是YUV420都可以,自己来定
#很明显YUV422的颜色分量会多一些,将来出来的色彩的还原度会高一些,但有比较浪费内存,所以自己来权衡颜色分量和内存占用情况
#行业内YUV420用得比较多
=================================================================================================================
21.
/******************************************
step 3: start vi dev & chn to capture
******************************************/
stViConfig.enViMode = SENSOR_TYPE;
stViConfig.enRotate = ROTATE_NONE;
stViConfig.enNorm = VIDEO_ENCODING_MODE_AUTO;
stViConfig.enViChnSet = VI_CHN_SET_NORMAL;
stViConfig.enWDRMode = WDR_MODE_NONE;
s32Ret = SAMPLE_COMM_VI_StartVi(&stViConfig);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("start vi failed!\n");
goto END_VENC_1080P_CLASSIC_1;
}
#MPP里面的函数我们追不进去
#HI_MPI_VI_SetDevAttr MPI代表MPP interface
#常用senso接口,MIPI,LVDS,DC(并口)
#stViConfig.enRotate = ROTATE_NONE;图像出来要旋转的话在这里设置
#stViConfig.enNorm = VIDEO_ENCODING_MODE_AUTO;图像制式的标准有PAL和NTSC两种,对于这种数字接口的sensor来说,不重要。
#stViConfig.enViChnSet = VI_CHN_SET_NORMAL; 图像镜像,翻转在这里设置
#stViConfig.enWDRMode = WDR_MODE_NONE;宽动态,这种技术需要sensor硬件支持。动态范围:在一幅图像中,能看到最亮与最暗的比例
#动态范围的模式有如下几种
typedef enum hiWDR_MODE_E
{
WDR_MODE_NONE = 0,
WDR_MODE_BUILT_IN,
WDR_MODE_2To1_LINE,
WDR_MODE_2To1_FRAME,
WDR_MODE_2To1_FRAME_FULL_RATE,
WDR_MODE_3To1_LINE,
WDR_MODE_3To1_FRAME,
WDR_MODE_3To1_FRAME_FULL_RATE,
WDR_MODE_4To1_LINE,
WDR_MODE_4To1_FRAME,
WDR_MODE_4To1_FRAME_FULL_RATE,
WDR_MODE_BUTT,
} WDR_MODE_E;
#sensor运行需要驱动
HI_S32 SAMPLE_COMM_VI_SetMipiAttr(SAMPLE_VI_CONFIG_S* pstViConfig)
{
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;
}
printf("=============SAMPLE_COMM_VI_SetMipiAttr enWDRMode: %d\n", pstViConfig->enWDRMode);
if ( pstViConfig->enViMode == APTINA_AR0230_HISPI_1080P_30FPS )
{
pstcomboDevAttr = &HISPI_4lane_SENSOR_AR0230_12BIT_ATTR;
}
if ( pstViConfig->enViMode == PANASONIC_MN34222_MIPI_1080P_30FPS )
{
pstcomboDevAttr = &MIPI_2lane_SENSOR_MN34222_12BIT_NOWDR_ATTR;
}
if ( (pstViConfig->enViMode == OMNIVISION_OV9752_MIPI_720P_30FPS)
|| (pstViConfig->enViMode == OMNIVISION_OV9750_MIPI_720P_30FPS) )
{
pstcomboDevAttr = &MIPI_2lane_SENSOR_OV9752_12BIT_NOWDR_ATTR;
}
if ( pstViConfig->enViMode == OMNIVISION_OV2718_MIPI_1080P_25FPS )
{
pstcomboDevAttr = &MIPI_4lane_SENSOR_OV2718_12BIT_NOWDR_ATTR;
}
if ( (pstViConfig->enViMode == APTINA_9M034_DC_720P_30FPS)
|| (pstViConfig->enViMode == APTINA_AR0130_DC_720P_30FPS)
|| (pstViConfig->enViMode == SONY_IMX222_DC_1080P_30FPS)
|| (pstViConfig->enViMode == SONY_IMX222_DC_720P_30FPS)
|| (pstViConfig->enViMode == OMNIVISION_OV9712_DC_720P_30FPS)
|| (pstViConfig->enViMode == OMNIVISION_OV9732_DC_720P_30FPS) )
{
pstcomboDevAttr = &MIPI_CMOS3V3_ATTR;
}
if (NULL == pstcomboDevAttr)
{
printf("Func %s() Line[%d], unsupported enViMode: %d\n", __FUNCTION__, __LINE__, pstViConfig->enViMode);
close(fd);
return HI_FAILURE;
}
if (ioctl(fd, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr))
{
printf("set mipi attr failed\n");
close(fd);
return HI_FAILURE;
}
close(fd);
return HI_SUCCESS;
#sensor驱动装载完后会生成/dev/hi_mipi这样的一个设备文件,打开,打开后准备好相应的参数,不同的sensor填充的参数是不一样的
#填充完以后通过一个ioctl(fd, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr),HI_MIPI_SET_DEV_ATTR是3518E给sensor做属性设置的命令,传参的标准都是海思定义好的一个结构体,
#但是这个结构体在不用的sensor里面是不一样的,
#ioctl是驱动对应用开放的接口
#SAMPLE_COMM_VI_SetMipiAttr功能就是在应用层对sensor做一个初始化
combo_dev_attr_t HISPI_4lane_SENSOR_AR0230_12BIT_ATTR =
{
/* input mode */
.input_mode = INPUT_MODE_HISPI,
{
.lvds_attr =
{
.img_size = {1920, 1080},
HI_WDR_MODE_NONE,
LVDS_SYNC_MODE_SOL,
RAW_DATA_12BIT,
LVDS_ENDIAN_LITTLE,
LVDS_ENDIAN_LITTLE,
.lane_id = {0, 1, 2, 3, -1, -1, -1, -1},
.sync_code =
{
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}},
{{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005},
{0x003, 0x007, 0x001, 0x005}}
}
}
}
};
===============================================================================================================================
22.
/******************************************
step 2: configure 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;
}
#ISP image signal process 是一种技术,就是做一下数学运算,3518e内部的一个硬件单元
#这个ISP硬件单元就是专门用来做ISP的,这个模块在MPP里面被封装成了API
#SAMPLE_COMM_ISP_Init功能就是把这个ISP的线程运行起来
#ISP里面处理比如3A
#也可以专门加一个ISP芯片,不用3518e里面这个ISP模块单元,不启动就行,默认就是关闭的
/******************************************************************************
* funciton : ISP init
******************************************************************************/
HI_S32 SAMPLE_COMM_ISP_Init(WDR_MODE_E enWDRMode)
{
ISP_DEV IspDev = 0;
HI_S32 s32Ret;
ISP_PUB_ATTR_S stPubAttr;
ALG_LIB_S stLib;
#if 0
/* 0. set cmos iniparser file path */
s32Ret = sensor_set_inifile_path("configs/");
if (s32Ret != HI_SUCCESS)
{
printf("%s: set cmos iniparser file path failed with %#x!\n", \
__FUNCTION__, s32Ret);
return s32Ret;
}
#endif
/* 1. sensor register callback */
s32Ret = sensor_register_callback();
if (s32Ret != HI_SUCCESS)
{
printf("%s: sensor_register_callback failed with %#x!\n", \
__FUNCTION__, s32Ret);
return s32Ret;
}
/* 2. register hisi ae lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AE_LIB_NAME);
s32Ret = HI_MPI_AE_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AE_Register failed!\n", __FUNCTION__);
return s32Ret;
}
/* 3. register hisi awb lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AWB_LIB_NAME);
s32Ret = HI_MPI_AWB_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AWB_Register failed!\n", __FUNCTION__);
return s32Ret;
}
/* 4. register hisi af lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AF_LIB_NAME);
s32Ret = HI_MPI_AF_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AF_Register failed!\n", __FUNCTION__);
return s32Ret;
}
/* 5. isp mem init */
s32Ret = HI_MPI_ISP_MemInit(IspDev);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_ISP_Init failed!\n", __FUNCTION__);
return s32Ret;
}
/* 6. isp set WDR mode */
ISP_WDR_MODE_S stWdrMode;
stWdrMode.enWDRMode = enWDRMode;
s32Ret = HI_MPI_ISP_SetWDRMode(0, &stWdrMode);
if (HI_SUCCESS != s32Ret)
{
printf("start ISP WDR failed!\n");
return s32Ret;
}
/* 7. isp set pub attributes */
/* note : different sensor, different ISP_PUB_ATTR_S define.
if the sensor you used is different, you can change
ISP_PUB_ATTR_S definition */
switch(SENSOR_TYPE)
{
case APTINA_9M034_DC_720P_30FPS:
case APTINA_AR0130_DC_720P_30FPS:
stPubAttr.enBayer = BAYER_GRBG;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
break;
case SONY_IMX222_DC_1080P_30FPS:
stPubAttr.enBayer = BAYER_RGGB;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 200;
stPubAttr.stWndRect.s32Y = 20;
stPubAttr.stWndRect.u32Width = 1920;
stPubAttr.stWndRect.u32Height = 1080;
break;
case SONY_IMX222_DC_720P_30FPS:
stPubAttr.enBayer = BAYER_RGGB;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 200;
stPubAttr.stWndRect.s32Y = 20;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
break;
case APTINA_AR0230_HISPI_1080P_30FPS:
stPubAttr.enBayer = BAYER_GRBG;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1920;
stPubAttr.stWndRect.u32Height = 1080;
break;
case PANASONIC_MN34222_MIPI_1080P_30FPS:
stPubAttr.enBayer = BAYER_GRBG;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1920;
stPubAttr.stWndRect.u32Height = 1080;
break;
case OMNIVISION_OV9712_DC_720P_30FPS:
stPubAttr.enBayer = BAYER_BGGR;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
break;
case OMNIVISION_OV9732_DC_720P_30FPS:
stPubAttr.enBayer = BAYER_BGGR;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
break;
case OMNIVISION_OV9750_MIPI_720P_30FPS:
case OMNIVISION_OV9752_MIPI_720P_30FPS:
stPubAttr.enBayer = BAYER_BGGR;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
break;
case OMNIVISION_OV2718_MIPI_1080P_25FPS:
stPubAttr.enBayer = BAYER_BGGR;
stPubAttr.f32FrameRate = 25;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1920;
stPubAttr.stWndRect.u32Height = 1080;
break;
default:
stPubAttr.enBayer = BAYER_GRBG;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1920;
stPubAttr.stWndRect.u32Height = 1080;
break;
}
s32Ret = HI_MPI_ISP_SetPubAttr(IspDev, &stPubAttr);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_ISP_SetPubAttr failed with %#x!\n", __FUNCTION__, s32Ret);
return s32Ret;
}
/* 8. isp init */
s32Ret = HI_MPI_ISP_Init(IspDev);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_ISP_Init failed!\n", __FUNCTION__);
return s32Ret;
}
gbIspInited = HI_TRUE;
return HI_SUCCESS;
}
#sensor_register_callback() IQ调试相关
#这个函数在sensor驱动里面Z:\Hi3518E_V200R001C01SPC030\Hi3518E V200R001C01SPC030\
#01.software\board\Hi3518E_SDK_V1.0.3.0\package\mpp\component\isp\sensor\ar0130目录的ar0130_cmos.c里面
/* 2. register hisi ae lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AE_LIB_NAME);
s32Ret = HI_MPI_AE_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AE_Register failed!\n", __FUNCTION__);
return s32Ret;
}
#自动曝光
/* 3. register hisi awb lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AWB_LIB_NAME);
s32Ret = HI_MPI_AWB_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AWB_Register failed!\n", __FUNCTION__);
return s32Ret;
}
#自动白平衡
/* 4. register hisi af lib */
stLib.s32Id = 0;
strcpy(stLib.acLibName, HI_AF_LIB_NAME);
s32Ret = HI_MPI_AF_Register(IspDev, &stLib);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_AF_Register failed!\n", __FUNCTION__);
return s32Ret;
}
#自动对焦
#注册一下3A单元
/* 5. isp mem init */
s32Ret = HI_MPI_ISP_MemInit(IspDev);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_ISP_Init failed!\n", __FUNCTION__);
return s32Ret;
}
#给ISP单元分配内存,传这个参数IspDev就可以了,内部自动会去分配内存
/* 6. isp set WDR mode */
ISP_WDR_MODE_S stWdrMode;
stWdrMode.enWDRMode = enWDRMode;
s32Ret = HI_MPI_ISP_SetWDRMode(0, &stWdrMode);
if (HI_SUCCESS != s32Ret)
{
printf("start ISP WDR failed!\n");
return s32Ret;
}
#设置宽动态
switch(SENSOR_TYPE)
{
case APTINA_9M034_DC_720P_30FPS:
case APTINA_AR0130_DC_720P_30FPS:
stPubAttr.enBayer = BAYER_GRBG;
stPubAttr.f32FrameRate = 30;
stPubAttr.stWndRect.s32X = 0;
stPubAttr.stWndRect.s32Y = 0;
stPubAttr.stWndRect.u32Width = 1280;
stPubAttr.stWndRect.u32Height = 720;
#stPubAttr.enBayer = BAYER_GRBG; RGB原始信号的排列序列,查sensor的datasheet
# stPubAttr.stWndRect.s32X = 0;
# stPubAttr.stWndRect.s32Y = 0; 图像区域的起始点(0,0)
#ISP初始化以后ISP就已经准备好了
/* 8. isp init */
s32Ret = HI_MPI_ISP_Init(IspDev);
if (s32Ret != HI_SUCCESS)
{
printf("%s: HI_MPI_ISP_Init failed!\n", __FUNCTION__);
return s32Ret;
}
#3518E内部ISP单元是隶属于VI模块的
#VI模块就包括3大部分,第一部分是与sensor对接的部分,第二部分是就是ISP,第三部分就是VI dev和channel部分。
#VI dev是采集图像的硬件单元,channel是与后端连接的多个出口,也是一个硬件单元。每一个出口就是一个channel。
#填充完后就设置进去HI_MPI_VI_SetDevAttr
s32Ret = HI_MPI_VI_SetDevAttr(ViDev, &stViDevAttr);
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("HI_MPI_VI_SetDevAttr failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
#获取WDR模式HI_MPI_ISP_GetWDRMode
if ( (SAMPLE_VI_MODE_BT1120_1080P != enViMode)
&&(SAMPLE_VI_MODE_BT1120_720P != enViMode) )
{
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;
}
#使能dev
s32Ret = HI_MPI_VI_EnableDev(ViDev);
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("HI_MPI_VI_EnableDev failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
#dev循环了一次,因为u32DevNum=1
/******************************************************
step 4 : config & start vicap dev
******************************************************/
for (i = 0; i < u32DevNum; i++)
{
ViDev = i;
s32Ret = SAMPLE_COMM_VI_StartDev(ViDev, enViMode);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("%s: start vi dev[%d] failed!\n", __FUNCTION__, i);
return HI_FAILURE;
}
}
VPSS(Video Process Sub-System)
#支持对一幅图像进行统一预处理,如去噪,去隔行等。
#什么是去隔行,就是把隔行转成逐行,因为以前的技术有隔行扫描,不过现在的大多数的sensor都是逐行扫描的了。
#然后再对各通道分别进行缩放,锐化等处理,最后输出多种不同分辨率的图像。
#从这句话可以看出,VI到VPSS是当通道输入,多通道输出是从VPSSz这里出去的。扩展通道是从VPSS这里出来的。
#具体的功能包括FRC(Frame Rate Control)、Crop、NR(Noise Reduce)、LDC(Lens Distortion Correction)
#Rotate、Cover/Overlay、Scale、Mirror/Flip、FishEye
#通过调用SYS模块的绑定接口这句话的意思,SYS模块就是MPP,绑定接口就是s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);
#stSrcChn源头通道是VI,目的通道stDestChn是VPSS
#group是VPSS硬件在软件上的映射,如果只有一个group那就是VPSS硬件的1:1的映射,不用复用了
#group里面分了好多个channel;
#VI里面的channel和group里面的channel是不同的东西
#是VI这里的channel去绑定VPSS的gruop的
/*** enSize[0] **/
if(s32ChnNum >= 1)
{
VpssGrp = 0;
VpssChn = 0;
VencChn = 0;
s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[0],\
gs_enNorm, enSize[0], enRcMode,u32Profile);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("Start Venc failed!\n");
goto END_VENC_1080P_CLASSIC_5;
}
s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("Start Venc failed!\n");
goto END_VENC_1080P_CLASSIC_5;
}
}
#Profile指的是H.264编码的清晰度,分为baseline mainprofile hightprofile
/******************************************
step 1: Create Venc Channel
******************************************/
stVencChnAttr.stVeAttr.enType = enType;
switch(enType)
{
case PT_H264:
{
stH264Attr.u32MaxPicWidth = stPicSize.u32Width;
stH264Attr.u32MaxPicHeight = stPicSize.u32Height;
stH264Attr.u32PicWidth = stPicSize.u32Width;/*the picture width*/
stH264Attr.u32PicHeight = stPicSize.u32Height;/*the picture height*/
stH264Attr.u32BufSize = stPicSize.u32Width * stPicSize.u32Height;/*stream buffer size*/
stH264Attr.u32Profile = u32Profile;/*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));
#stH264Attr.u32MaxPicWidth,stH264Attr.u32MaxPicHeight编码通道的最大宽和高,如果给到通道里面的图像比这个大就会被压缩,如果小就丢弃
#所有图像的宽高stH264Attr.u32PicWidth,stH264Attr.u32PicHeight就应该和最大宽和高设置一样大,这样是最好的,不用处理直接编码
#stH264Attr.u32BufSize过程中的一个buffer,以像素为单位
#svc_t是h.264的一个补充标准
================================================================================================================
pstPara = (SAMPLE_VENC_GETSTREAM_PARA_S*)p;
s32ChnTotal = pstPara->s32Cnt;
#s32Cnt告诉你有几路,我们目前有三路
/******************************************
step 1: check & prepare save-file & venc-fd
******************************************/
if (s32ChnTotal >= VENC_MAX_CHN_NUM)
{
SAMPLE_PRT("input count invaild\n");
return NULL;
}
#总的通道数大于最大通道提醒输入无效
for (i = 0; i < s32ChnTotal; i++)
{
/* decide the stream file name, and open file to save stream */
VencChn = i;
s32Ret = HI_MPI_VENC_GetChnAttr(VencChn, &stVencChnAttr);
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;
s32Ret = SAMPLE_COMM_VENC_GetFilePostfix(enPayLoadType[i], szFilePostfix);
if(s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("SAMPLE_COMM_VENC_GetFilePostfix [%d] failed with %#x!\n", \
stVencChnAttr.stVeAttr.enType, s32Ret);
return NULL;
}
sprintf(aszFileName[i], "stream_chn%d%s", i, szFilePostfix);
pFile[i] = fopen(aszFileName[i], "wb");
if (!pFile[i])
{
SAMPLE_PRT("open file[%s] failed!\n",
aszFileName[i]);
return NULL;
}
#去get流就是跟VENC这边对接的,
#获取编码输出通道的一些信息HI_MPI_VENC_GetChnAttr
# enPayLoadType[i] = stVencChnAttr.stVeAttr.enType;这个拿出来就是H.264的类型
#SAMPLE_COMM_VENC_GetFilePostfix获取编码后文件名的后缀。比如.h264
#aszFileName[i]编码后文件名
#最后这个流就是写到一个文件里面去了pFile[i] = fopen(aszFileName[i], "wb");一个裸流,裸流就是没有文件头,没有文件格式
#一般的播放器是播放不了,VLC是面向开发的,支持裸流播放。wb为二级制的形式写入。
VencFd[i] = HI_MPI_VENC_GetFd(i);
#取流文件的访问接口,毕竟编码后的文件是封装在里面的,你不能直接去取的
#什么是流,流就是连续的,你去读只能读一段
stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);
#通过malloc申请一段内存
s32Ret = SAMPLE_COMM_VENC_SaveStream(enPayLoadType[i], pFile[i], &stStream);
#分配了一个payload,我们这是当然是h.264
/******************************************************************************
* funciton : save H264 stream
******************************************************************************/
HI_S32 SAMPLE_COMM_VENC_SaveH264(FILE* fpH264File, VENC_STREAM_S *pstStream)
{
HI_S32 i;
for (i = 0; i < pstStream->u32PackCount; i++)
{
fwrite(pstStream->pstPack[i].pu8Addr+pstStream->pstPack[i].u32Offset,
pstStream->pstPack[i].u32Len-pstStream->pstPack[i].u32Offset, 1, fpH264File);
fflush(fpH264File);
}
return HI_SUCCESS;
}
#fwrite只是放到了内存
#fflush写到flash里面去了,确保断电不丢失,另一方面是内存有限
/*******************************************************
step 2.6 : release stream
*******************************************************/
s32Ret = HI_MPI_VENC_ReleaseStream(i, &stStream);
if (HI_SUCCESS != s32Ret)
{
free(stStream.pstPack);
stStream.pstPack = NULL;
break;
}
#写完了还要调用HI_MPI_VENC_ReleaseStream,海思内部的设计架构,不用考虑
/*******************************************************
* step 3 : close save-file
*******************************************************/
for (i = 0; i < s32ChnTotal; i++)
{
fclose(pFile[i]);
}
return NULL;
#我们不想录像后,就关闭文件
HI_VOID* SAMPLE_COMM_VENC_GetVencStreamProc(HI_VOID *p)
#这个函数是我们获取视频并且写成文件的一个主线程,这个线程一死,整个录像过程就掉了
/******************************************
step 6: stream venc process -- get stream, then save it to file.
1.可以把这个码流打包成一个MP4存储到你的硬盘里面去,这就是录像。
2.也可以分包,分成一个一个的视频包通过RTSP传出去。
3.也可以作为一个裸流直接丢到流文件里面。
******************************************/
s32Ret = SAMPLE_COMM_VENC_StartGetStream(s32ChnNum);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("Start Venc failed!\n");
goto END_VENC_1080P_CLASSIC_5;
}
printf("please press twice ENTER to exit this sample\n");
getchar();
getchar();
#这个主线程在这里可以结束,两次回车就会结束,录像停止
/******************************************
step 7: exit process
刚好是个一个逆过程,要把场地收拾干净,
就像一个栈,先进后出
******************************************/
SAMPLE_COMM_VENC_StopGetStream();
#主线程结束后,跳到这里,
HI_S32 SAMPLE_COMM_VENC_StopGetStream()
{
if (HI_TRUE == gs_stPara.bThreadStart)
{
gs_stPara.bThreadStart = HI_FALSE;
pthread_join(gs_VencPid, 0);
}
return HI_SUCCESS;
}
#gs_stPara.bThreadStart = HI_FALSE; 就把变量设置为FALSE,弄死
#回收线程 pthread_join(gs_VencPid, 0); 收尸
#整个程序结束
END_VENC_1080P_CLASSIC_5:
VpssGrp = 0;
switch(s32ChnNum)
{
case 3:
VpssChn = 2;
VencChn = 2;
SAMPLE_COMM_VENC_UnBindVpss(VencChn, VpssGrp, VpssChn);
SAMPLE_COMM_VENC_Stop(VencChn);
case 2:
VpssChn = 1;
VencChn = 1;
SAMPLE_COMM_VENC_UnBindVpss(VencChn, VpssGrp, VpssChn);
SAMPLE_COMM_VENC_Stop(VencChn);
case 1:
VpssChn = 0;
VencChn = 0;
SAMPLE_COMM_VENC_UnBindVpss(VencChn, VpssGrp, VpssChn);
SAMPLE_COMM_VENC_Stop(VencChn);
break;
}
SAMPLE_COMM_VI_UnBindVpss(stViConfig.enViMode);
END_VENC_1080P_CLASSIC_4: //vpss stop
VpssGrp = 0;
switch(s32ChnNum)
{
case 3:
VpssChn = 2;
SAMPLE_COMM_VPSS_DisableChn(VpssGrp, VpssChn);
case 2:
VpssChn = 1;
SAMPLE_COMM_VPSS_DisableChn(VpssGrp, VpssChn);
case 1:
VpssChn = 0;
SAMPLE_COMM_VPSS_DisableChn(VpssGrp, VpssChn);
break;
}
END_VENC_1080P_CLASSIC_3: //vpss stop
SAMPLE_COMM_VI_UnBindVpss(stViConfig.enViMode);
END_VENC_1080P_CLASSIC_2: //vpss stop
SAMPLE_COMM_VPSS_StopGroup(VpssGrp);
END_VENC_1080P_CLASSIC_1: //vi stop
SAMPLE_COMM_VI_StopVi(&stViConfig);
END_VENC_1080P_CLASSIC_0: //system exit
SAMPLE_COMM_SYS_Exit();
return s32Ret;
}
#各种不同级别恢复原位的过程,比如VPSS死了,你得把你初始化的MPP初始化给恢复原位
#注意一个倒影的问题,前面是123,后面是321的一个过程
==============================================================================================
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