1.官方sampe
(1)海思官方提供的sampe位于Z:\Hi3518E_SDK_V1.0.3.0\mpp\sample中
sample其实是很多个例程,所以有很多个Main,每一个例程面向一个典型应用。
common 是通用性主体函数,里面的东西全是被其他程序调用的。我们只分析视频编码venc
(2)基本的架构是:venc中的main调用venc中的功能函数,再调用common中的功能函数,
common中的功能函数再调用mpp中的API,mpp的API在哪里?mpp中的API实现的函数体在ko里面,
他们是以库的形式(驱动)来提供的,这些ko你是看不见海思的源码的,你只能去查海思的手册,
HiMPP IPC V2.0 媒体处理软件开发参考.pdf,这份文档就是mpp的API手册,里面包括了所有mpp的
的API,API(驱动)最终会去调用hi3518E内部的硬件单元(内部有ARM DSP)。
mpp就是海思图像处理的这套体系
mpp里面包含ko(运行时要加载的动态库) lib(静态库) sample(我们的应用程序)
hifb显示用的,free buffer,可以接屏幕显示。
除了common(被其他目录的函数调用),其他目录下面的都是一个独立的程序(例程,面向一个典型应用),有自己的Main函数。
海思这一套到了很多关键的地方就是调用了一个库函数,很多情况很多核心的东西是看不到的,只知道调用这个库函数就能完成操作,
研究的重点是怎么去调这个库函数,怎么去传参,参数是什么意思,参数如果不对会返回怎么样一个错误,
(3)sampe的配置和编译,目录要完全搞清楚了再动。
2.sampe代码的学习的关键
(1)得理解很多基础概念,譬如图像采集原理、模拟数字、通道(海思发明的)、绑定等等。
说白了就是海思的人在定义概念,我们通过手册去理解概念。
(2)得从宏观上理解整个视频采集、内部传递、处理、编码输出、网络传输等的过程。
(3)的反复看代码,熟才能生巧,才能帮助理解整个代码。
(4)的查阅mpp手册,熟悉海思这一套API的规矩和一般用法。
(5)从sample/venc/Sampe_venc.c中的main函数开始看
3.几个基本的概念
(1) 分辨率
VGA(640*480) D1(720*576) 720p 1080p 4K(4096*2160) 8K(8192*4320)
(2) 视频压缩标准
h.264 H.264也称作MPEG-4AVC(Advanced Video Coding),是一种视频压缩标准,同时也是一种被广泛使用的
高精度视频录制、压缩、和发布格式。
h.265/HEVC的编码架构大致上和h.264/AVC的架构相似。目前的HEVC标准总共有三种模式:Main、Main10、Main Still Picture.
(3)fps(frame per second)帧率
4.图像像素格式深度理解1
(1)颜色的学问
a.颜色是主观的还是客观存在的? 颜色的本质是光的波长,但是因为有人去看了才有了颜色这个概念。
是主观存在的概念。意思就是脱离人眼去研究颜色是没有意义的。
b.颜色的三个关键概念:亮度、色度、饱和度。
c.人的眼睛并非理想完美的颜色识别器件,图像表达也有清晰度和质量高低的差异。
d.科学研究如何定义(或者表达、记录、计算)一种颜色?色彩空间的概念
颜色其实是有无数种,在RGB888中,RGB分别用8位二级制来表示,那么颜色就有2^24=16777216种,
这其实是一种简化,其实颜色是有无数种的,因为光的波长是连续分布的。用这种简化已经足够应对
人眼,再细分人眼也看不出区别。
RGB就是一种色彩空间,色彩空间 不止一种,比如还有YUV,计算机领域主要有RGB和YUV这两种色彩空间,
他们之间可以转换。
色彩空间是一种方法,研究的是如何用数字来表达一种颜色。
5.rawRGB和图像采集过程
(1)图像采集的过程:光照在成像物体被反射->镜头汇聚->Sensor光电转换(模拟的光信号转为模拟的电信号)->ADC为rawRGB(这个数字的电信号就rawRGB)
(2)sensor上每一个像素点只采集一种颜色,像素点的上方有一个滤镜,只透一种颜色。这种采集到的就是rawRGB,一个像素只有一个颜色分量。因为sensor
单个像素根本就没有办法同时采集到三种颜色。
(3)rawRGB和RGB都是用来描述图像的,图像采集时RGB是由rawRGB计算而来的
(4)因为图像颜色本身有一定连贯性,而且人眼是非理想的,因此图像采集和再显示给人这整个构成中有三个要素:分辨率(多大像素)、pictch、观看距离
(5)如果是视频、质量好坏还要加上帧率framerate
(6)图像的表达、压缩、修整等相关技术,就发生在rawRGB进来以后的各个环节
6.RGB
(1)RGB有RGB565(要求不严格的领域里面还过的去)和RGB888(1600万色,真彩色,颜色精度高),ARGB(A表示透明度)等多种子分类,
也可以搞一个RGB101010,只不过那么高的精度对人眼的意义不大。RGB888对人眼已经完全够用了。
(2)RGB的本质:自然界的颜色可以分成红绿蓝,是因为自然界颜色是可以叠加的。将色度分解为R、G、B三部分,然后记录下亮度数据
注意:用RGB来表示颜色,我们只是记录R G B各自的亮度数据,并没有记录颜色。例如0x234567 值23就是R的亮度值,45就是G的亮度值,67就是B的亮度值。
RGB就是由rawRGB转换而来,做了一个平均运算,这个像素缺的那两个颜色值去周围取平均来补充。
一幅图像的尺寸是1024*1024像素,每个像素的灰度是8比特,如果图像不压缩,则需要(1024*1024*8 bit)(1MB)的存储空间
RGB有RGB556和RGB888,ARGB等多种子分类。R表示透明度
RGB888表示R需要8bit,G需要8bit,B需要8bit,加起来需要24bit,
RGB565是16位的,2个字节,就是R-5bit G-6bit B-5bit
正常的RGB24是由24位即3个字节来描述一个像素,R G B各8位,而实际中为了减少图像数据的尺寸,如视频领域,对R G B 所使用的位数进行的缩减,如RGB565 RGB555
颜色是可以叠加的,RGB的本质:将色度分解为R、G、B三部分,本身并没有记录颜色,只是记录了亮度。
例如0x00234567 00表示透明度,23表示红色的亮度,45表示绿色的亮度,67表示蓝色的亮度
用数学方法记录下来的就是为了复现,
RGB的优势:方便数字化表达,广泛用于数字化彩色显示器,计算机编程等领域。
RGG的劣势:和传统的灰度图(黑白图像,没有色度,只有亮度)兼容不好,表达颜色的效率不高。
4. YUV
在这个体系了里面,亮度Y与色度UV是分开的,只有Y就是黑白图像,再加上UV就是彩色图像了,YUV的一个好处
就是让彩色系统和传统黑白系统很好地兼容。一幅彩色的YUV图可以瞬间变成黑白的,YUV的出现就是为了兼容传统的
黑白图像。电视广播发的是YUV的图,彩色电视全盘接受,黑白电视把色度UV丢掉就行。如果给的是RGB信号,黑白电视机
根本就不认,因为在黑白电视机年代根本就还没有RGB这种模型。但是RGB的亮度信息是分成三个参量的。
YUV和RGB的相同点是:都是用来表达颜色的数学方法;不同点是:对颜色的描述思路和方法不同。
RGB将一个颜色拆解为3个基色的亮度组合,YUV将一个颜色分解为一个颜色分解为一个亮度和两个色度的组合。
RGB和YUV是可以互相转换,是一个浮点数运算过程。
R = Y + 1.4075 * (V-128);
G = Y - 0.3455 * (U-128) - 0.7169*(V-128);
B = Y + 1.779 * (U-128);
Y = 0.299*R + 0.587*G + 0.114*B;
U = (B-Y)/1.772;
V = (R-Y)/1.402; (U~(-128-127))
我们不用关心公式是怎么来的,关心的是相互转换的快慢。建议用DSP或者GPU来算,CUP来算就比较慢。
YCbCr,几乎可以理解为YUV,差别比较小
5. YUV格式
YUV格式通常有两大类:packed格式和平面planar格式。前者将YUV分量存放在同一个数组中,通常是几个
相邻的像素组成一个宏像素(macro-pixel);而后者使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。
假设有4个像素,每个像素都有一个YUV
所以:Y0U0V0 Y1U1V1 Y2U2V2 Y3U3V3
packed:定义四个数组
[Y0,U0,V0],[Y1,U1,V1],[Y2,U2,V2],[Y3,U3,V3] 将YUV分量存放在同一个数组中,如果是取一幅图像的一半
当然是用packed的好处理,直接去掉前面或者后面的两个数组即可。
planer:
[Y0,Y1,Y2,Y3],[U0,U1,U2,U3],[V0,V1,V2,V3] 如果是感觉图片曝光有点暗,我们要调亮一些,当然是planer好处理,
直接去调第一个数组就行。
packed格式比较简单,planer格式比较复杂
我们主要来关注planer
YUV422
YUV420 一个像素对应一个Y,相邻的2*2像素小方块对应一个U和一个V,为什么UV可以简化,因为相邻的像素颜色相似性很高,
packed这种格式比较简单,实践中见的大多数是planar格式
我们可以看出任何一种格式Y都没有简化,因为对于人眼亮度信息比色度信息更加敏感。
6. 有多种YUV相关的概念需要弄清楚
YUV
YUYV
YUV422
YUV420 (YUV411更加合适)
YUV422 planar (YUV422P)
YUV420 planar (YUV420P)
YUV422 semi planar (YUV422SP)
YUV420 semi planar(YUV420SP)
YUV420SP与YUV420P的数据格式它们的UV排列在原理上是完全不同的。
planer:
[Y0,Y1,Y2,Y3],[U0,U1,U2,U3],[V0,V1,V2,V3]
Semi palanar
[Y0,Y1,Y2,Y3],[U0,V0],[U1,V1],[U2,V2],[U3,V3]
YUV420的格式6个值Y1 Y2 Y3 Y4 U1 V1对应到每个像素怎么来表达
第一个像素点[Y1,U1,V1]
第二个像素点[Y2,U1,V1]
第三个像素点[Y3,U1,V1]
第四个像素点[Y4,U1,V1]
这四个像素点是共用了UV,但是Y是各自的
7. mpp
mpp与驱动处在同一个层级,其实本来就是驱动,只不过以KO、API的形式提供的而已,只不过没有源代码而已。
驱动上面才是应用层。
(1)VI模块捕获视频图像,可对其做剪切、缩放等处理,并输出多路不同分辨率的图像数据。
注意:海思的枚举定义规范所有的字母大写,用下划线来分开,最后会有一个E,告诉你这是个枚举类型。
例如:PAYLOAD_TYPE_E 你在传输视频的时候,视频的类型我们叫PAYLOAD_TYPE
你把RTSP视频传输看做是用饺子皮把饺子馅运走,PAYLOAD_TYPE就是问你这个饺子是什么馅的
RTSP可以传送的类型(馅)有如下类型:RTSP本来就是用来传送一些音视频的
PT_H264
PT_MP3
PT_AAC
...
这个音视频编码的种类特别多,你要用PAYLOAD_TYPE来告诉别人你要传输的是什么视频
enPayLoad[3] 这个以en开头的就告诉别人你这个是emue类型
stVpssGrpAttr 以st打头的就告诉别人你这个是一个结构体
VPSS_GRP_ATTR_S 这个类型表示结构体类型,以_S结尾
PAYLOAD_TYPE_E enPayLoad[3] =
VPSS_GRP_ATTR_S stVpssGrpAttr = {0}; st打头说明为结构体变量
typedef enum
{
PT_PCMU = 0,
PT_1016 = 1,
PT_G721 = 2,
PT_GSM = 3,
PT_G723 = 4,
PT_DVI4_8K = 5,
PT_DVI4_16K = 6,
PT_LPC = 7,
PT_PCMA = 8,
PT_G722 = 9,
PT_S16BE_STEREO = 10,
PT_S16BE_MONO = 11,
PT_QCELP = 12,
PT_CN = 13,
PT_MPEGAUDIO = 14,
PT_G728 = 15,
PT_DVI4_3 = 16,
PT_DVI4_4 = 17,
PT_G729 = 18,
PT_G711A = 19,
PT_G711U = 20,
PT_G726 = 21,
PT_G729A = 22,
PT_LPCM = 23,
PT_CelB = 25,
PT_JPEG = 26,
PT_CUSM = 27,
PT_NV = 28,
PT_PICW = 29,
PT_CPV = 30,
PT_H261 = 31,
PT_MPEGVIDEO = 32,
PT_MPEG2TS = 33,
PT_H263 = 34,
PT_SPEG = 35,
PT_MPEG2VIDEO = 36,
PT_AAC = 37,
PT_WMA9STD = 38,
PT_HEAAC = 39,
PT_PCM_VOICE = 40,
PT_PCM_AUDIO = 41,
PT_AACLC = 42,
PT_MP3 = 43,
PT_ADPCMA = 49,
PT_AEC = 50,
PT_X_LD = 95,
PT_H264 = 96,
PT_D_GSM_HR = 200,
PT_D_GSM_EFR = 201,
PT_D_L8 = 202,
PT_D_RED = 203,
PT_D_VDVI = 204,
PT_D_BT656 = 220,
PT_D_H263_1998 = 221,
PT_D_MP1S = 222,
PT_D_MP2P = 223,
PT_D_BMPEG = 224,
PT_MP4VIDEO = 230,
PT_MP4AUDIO = 237,
PT_VC1 = 238,
PT_JVC_ASF = 255,
PT_D_AVI = 256,
PT_DIVX3 = 257,
PT_AVS = 258,
PT_REAL8 = 259,
PT_REAL9 = 260,
PT_VP6 = 261,
PT_VP6F = 262,
PT_VP6A = 263,
PT_SORENSON = 264,
PT_H265 = 265,
PT_MAX = 266,
PT_AMR = 1001,
PT_MJPEG = 1002,
PT_AMRWB = 1003,
PT_BUTT //这个是一个结尾标志符,不能用的,不要以为PT_BUTT也是一种传输类型,值为1004,
} PAYLOAD_TYPE_E; //值不重要,重要的是最后一个
typedef enum hiPIC_SIZE_E
{
PIC_QCIF = 0,
PIC_CIF,
PIC_2CIF,
PIC_HD1,
PIC_D1,
PIC_960H,
PIC_QVGA,
PIC_VGA,
PIC_XGA,
PIC_SXGA,
PIC_UXGA,
PIC_QXGA,
PIC_WVGA,
PIC_WSXGA,
PIC_WUXGA,
PIC_WQXGA,
PIC_HD720,
PIC_HD1080,
PIC_2304x1296,
PIC_2592x1520,
PIC_5M,
PIC_2688x1944,
PIC_6M,
PIC_UHD4K,
PIC_3Kx3K,
PIC_12M,
PIC_16M,
PIC_BUTT
} PIC_SIZE_E;
HI_S32 SAMPLE_VENC_NORMALP_CLASSIC(HI_VOID)
{
PAYLOAD_TYPE_E enPayLoad[2]= {PT_H265, PT_H264}; //告诉你有两路码流,编码类型一路是H.265,一路是H.264
PIC_SIZE_E enSize[2] = {PIC_HD1080, PIC_HD1080}; //告诉你这两路码流的分辨率的大小,都是1080p
VB_CONF_S stVbConf; //视频缓存池
7.2 视频缓存池
7.2.1 什么是视频缓冲值
(1)视频的本质是多帧的图片,图片的本质是RGB或rawRGB数据,要占用一段连续内存。
(2)视频的裁剪、缩放、修正处理等各种操作,本质上就是对内存中的数据进行运算。
(3)视频缓存池(VB, video buffer)就是一段很大,又被合理划分和管理的内存,用来做
视频数据的暂存和运算场地
(4)公共视频缓存池的公共2字,可以理解为全局变量,也就是各个模块都能访问的一段内存
(5)看似视频缓存块在各个模块之间流转,实际上并没有内存复制,而是指针在传递
(6)视频缓冲池的内存由MPP来维护,我们在系统启动时就把整个SDRAM分成了2部分:系统部分
(由linux kernel来维护管理)和mpp部分(由mpp系统来维护管理)。
(7)缓冲池需要几个,每个中包含几个缓冲块,每个缓存块多大,都是可以由用户程序设置
好参数,然后调用MPP的相应API来向MPP申请分配的。
7.2.2 相关的数据结构和API
(1)VB_CONF_S 通过这个结构体告诉MPP缓冲池要几个,每个缓冲池要几个缓存块,每个缓冲块有多大
(2)HI_MPI_VB_SetConf //VB_CONF_S只是做了个模型,这里是设置,
(3)HI_MPI_VB_Init //真正执行了这个操作,开始分配内存了
VB_CONF_S(点菜,在菜单上把对应的菜给划上了)这个结构体变量定义,做了填充,把模型定下来了;
HI_MPI_VB_SetConf(把点好的那个菜单交给服务员了)之后就是把模型告诉了
VB了,HI_MPI_VB_Init (后厨开始按照菜单开始做菜了)之后就是VB真正去执行了,开始分配内存。
HI_MPI_VB_SetConf() 这个函数就在之前你装载的某个ko里面
这里需要注意顺序:先HI_MPI_VB_SetConf()再HI_MPI_VB_Init(),相当于先点菜,厨师再做菜
typedef struct hiVB_CONF_S
{
HI_U32 u32MaxPoolCnt;
struct hiVB_CPOOL_S
{
HI_U32 u32BlkSize; //每个缓冲池的内部block的大小
HI_U32 u32BlkCnt; //每个缓冲池的内部block的数量
HI_CHAR acMmzName[MAX_MMZ_NAME_LEN]; //每个缓冲池的名字
}astCommPool[VB_MAX_COMM_POOLS]; //#define VB_MAX_COMM_POOLS 16
} VB_CONF_S; //相当于定义了16个公共缓冲池
这一部分是填充那些变量
图像的采集,启动VI部分的dev(设备)和chn(通道)
VI之后就会传给VPSS,传给VPSS之前先要把它启动起来
vi bind vpss:VI和VPSS是两个独立的模块.通过bind这操作,MPP系统提供的一个API(bind api),
通过调用bind api就可以把VI和VPSS这两个模块绑定起来。绑定起来之后有什么作用呢?
VI这边当它采集到一帧图像丢到一个VB(VIDEO BUFFER视频缓冲池)里面的缓存块里面之后,这个
缓存块会自动传送到VPSS里面去。
VPSS处理完之后就会到VENC里面去。这一步就开始启动VENC单元,如果你要添加水印信息就在这一步去
添加。H.264编码也在这一步去研究。编码完之后呢就得到H.264的一个码流了。
我们得到H.264码流之后怎么处理呢?
(1)可以把这个码流打包成一个MP4存储到硬盘里面去,这就是录像。
(2)也可以分包,分成一个一个视频包通过RTSP传出去。
(3)也可以像sample一样直接作为一个裸流直接丢到流文件里面去。
那么这种流文件必须通过像VLC这种能够解析裸流文件的播放器来观看。
所以说编码模块只负责输出一段H.264的裸流,这个裸流要怎么办是你的事。
如果不想录了之后就按两次回车跑到SAMPLE_COMM_VENC_StopGetStream();里面去
2.8 程序流程分析和MPP初始化详解
(1)初始化MPP的变量
HI_S32 SAMPLE_COMM_VI_GetSizeBySensor(PIC_SIZE_E* penSize)
{
HI_S32 s32Ret = HI_SUCCESS;
SAMPLE_VI_MODE_E enMode = SENSOR_TYPE; //这个SENSOR_TYPE其实是在Makefile.param里面配置的SONY_IMX323_CMOS_1080P_30FPS
if (!penSize) //要传一个可访问的地址
{
return HI_FAILURE;
}
switch (enMode)
{
case PANASONIC_MN34220_SUBLVDS_720P_120FPS:
case PANASONIC_MN34220_MIPI_720P_120FPS:
case SONY_IMX117_LVDS_720P_240FPS:
*penSize = PIC_HD720;
break;
case APTINA_MT9P006_DC_1080P_30FPS:
case PANASONIC_MN34220_SUBLVDS_1080P_30FPS:
case PANASONIC_MN34220_MIPI_1080P_30FPS:
case OMNIVISION_OV4689_MIPI_1080P_30FPS:
case OMNIVISION_OV2718_MIPI_1080P_30FPS:
case OMNIVISION_OV2718_2A_MIPI_1080P_30FPS:
case SONY_IMX323_CMOS_1080P_30FPS: //我们就case到这里
case APTINA_AR0237_LVDS_1080P_30FPS:
case APTINA_AR0330_MIPI_1080P_30FPS:
case SONY_IMX178_LVDS_1080P_30FPS:
case SONY_IMX185_MIPI_1080P_30FPS:
case SONY_IMX290_MIPI_1080P_30FPS:
case JX_F22_MIPI_1080P_20FPS:
case SMARTSENS_SC2235_CMOS_1080P_20FPS:
case SONY_IMX117_LVDS_1080P_120FPS:
case APTINA_AR0230_HISPI_1080P_30FPS:
*penSize = PIC_HD1080; //上面这一堆case都会到这里来,
break;
case APTINA_AR0330_MIPI_1536P_25FPS:
*penSize = PIC_QXGA;
break;
case APTINA_AR0330_MIPI_1296P_25FPS:
*penSize = PIC_2304x1296;
break;
case OMNIVISION_OV4689_MIPI_4M_30FPS:
*penSize = PIC_2592x1520;
break;
case SONY_IMX178_LVDS_5M_30FPS:
case OMNIVISION_OV5658_MIPI_5M_30FPS:
*penSize = PIC_5M;
break;
case SONY_IMX226_LVDS_4K_30FPS:
case SONY_IMX117_LVDS_4K_30FPS:
*penSize = PIC_UHD4K;
break;
default:
break;
}
return s32Ret;
}
SAMPLE_COMM_VI_GetSizeBySensor(&enSize[0]); //最终enSize[0]变成了PIC_HD1080
stVbConf.u32MaxPoolCnt = 128; //规定缓冲池的上限,就是个空头支票,结合内存大小来给,海思那边有经验的人给出的
PIC_SIZE_E enSize[2] = {PIC_HD1080, PIC_HD1080}; //最开始初始化的时候其实也是PIC_HD1080
s32ChnNum //标识出来是几路码流,比如3路码流是什么意思?同一个sensor输出3路不同分辨率的码流
一路主码流(原始的没有裁剪的,全功能版本),另外两路(在主码流的基础上经过裁剪缩放等操作简化而来,阉割版本)
为子码流,客户用手机看的话就用子码流看就行了,没必要看主码流,还可以省点网络带宽,还比较流畅。
裸流:开发过程中的半成品,把文件头一解析就知道这个文件的分辨率, mp4等都可以查一下文件头来看。
裸流是没有这个文件头的。720p 1280*720=92万像素至少要留点余量,100万像素以上
1080p 1920*1080 = 207万像素 至少sensor也要210万像素以上
if(s32ChnNum >= 1) //每一路码流对应一个缓冲池
{
u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,\
enSize[0], SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH);//关键是把enSize[0]传进去,
stVbConf.astCommPool[0].u32BlkSize = u32BlkSize; //从上面的得到enSize[0]为PIC_HD1080
#ifndef hi3516ev100 //把SAMPLE_PIXEL_FORMAT(像素格式)传进去,RGB888,RGB565占的字节就不一样
stVbConf.astCommPool[0].u32BlkCnt = 10; //SAMPLE_SYS_ALIGN_WIDTH对齐排布
#else
stVbConf.astCommPool[0].u32BlkCnt = 5;
#endif
}
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 =10;
}
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 = 10;
}
为什么不同的码流要对应不同的pool呢?因为不同的码流分辨率不一样,对应的blocksize的大小也就不一样
//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)); //YUV422 4个像素占4+2+2=8个字节,那么每个像素平均占8/4=2个字节
u32VbSize=宽*高*每个像素的字节数 //YUV420 4个像素占4+2+0=6个字节,那么每个像素平均占6/4=1.5个字节
u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(gs_enNorm,\ //最后算出来u32BlkSize一帧图像所需内存的大小
enSize[0], SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH);
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(); //注意顺序,释放的时候是SYS在前,VB在后的
HI_MPI_VB_Exit(); //怕你之前的VB和SYS没处理干净,保险起见,打扫卫生的
if (NULL == pstVbConf)
{
SAMPLE_PRT("input parameter is null, it is invaild!\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_VB_SetConf(pstVbConf); //建立的时候是VB在前
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_VB_SetConf failed!\n");
return HI_FAILURE;
}
s32Ret = HI_MPI_VB_Init();
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("HI_MPI_VB_Init failed!\n");
return HI_FAILURE;
}
//只要把VB(缓冲池搞好了),SYS里面都是自动化的,海思封装的特别好,我们要操心的是VB的参数pstVbconf
8.1 学习方法:绘制调用关系图谱
(1)学习重点1:全局把控熟悉整个过程全景视图
VI里面:DEV和chanel
8.2 常用Sensor的接口有三种:MIPI LVDS DC
WDR :宽动态范围,很亮和很暗都可以看得很清楚的一种技术。硬件本身要支持才行。
动态范围:在图像里面能看到的最亮的和最暗的一个比例。
ISP:image signal process图像信号处理,3518E内部的一个硬件单元,这个硬件单元就是用来做ISP运算的,
这个模块在MPP里面被封装成了API,SAMPLE_COMM_ISP_Init()函数就是启动ISP这个硬件单元,把这个线程
运行起来,ISP主要是用来处理图像,比如典型的3A,如果你自己添加了一个专用的ISP芯片,那么你可以不用
3518E内部的这个ISP模块(不启动它)。
8.3 进入ISP启动函数SAMPLE_COMM_ISP_Init()我们发现sensor_register_callback(),但是我们找不到
sensor_register_callback()在哪里定义的,其实它在sensor驱动里面定义的,
驱动定义目录为Z:\Hi3516CV300_SDK_V1.0.3.0\mpp\component\isp\sensor\sony_imx323\imx323_cmos.c
int sensor_register_callback(void)
{
ISP_DEV IspDev = 0;
HI_S32 s32Ret;
ALG_LIB_S stLib;
ISP_SENSOR_REGISTER_S stIspRegister;
AE_SENSOR_REGISTER_S stAeRegister;
AWB_SENSOR_REGISTER_S stAwbRegister;
cmos_init_sensor_exp_function(&stIspRegister.stSnsExp);
s32Ret = HI_MPI_ISP_SensorRegCallBack(IspDev, IMX323_ID, &stIspRegister);
if (s32Ret)
{
printf("sensor register callback function failed!\n");
return s32Ret;
}
stLib.s32Id = 0;
strncpy(stLib.acLibName, HI_AE_LIB_NAME, sizeof(HI_AE_LIB_NAME));
cmos_init_ae_exp_function(&stAeRegister.stSnsExp);
s32Ret = HI_MPI_AE_SensorRegCallBack(IspDev, &stLib, IMX323_ID, &stAeRegister);
if (s32Ret)
{
printf("sensor register callback function to ae lib failed!\n");
return s32Ret;
}
stLib.s32Id = 0;
strncpy(stLib.acLibName, HI_AWB_LIB_NAME, sizeof(HI_AWB_LIB_NAME));
cmos_init_awb_exp_function(&stAwbRegister.stSnsExp);
s32Ret = HI_MPI_AWB_SensorRegCallBack(IspDev, &stLib, IMX323_ID, &stAwbRegister);
if (s32Ret)
{
printf("sensor register callback function to ae lib failed!\n");
return s32Ret;
}
return 0;
}
这个函数的使用可以参考 ISP_3A 开发指南.pdf HiISP 开发参考.pdf
8.4 HI3518E内部ISP单元是隶属于VI模块的,VI模块就包含3大部分:
第一部分是和sensor对接的部分;第二部分就是ISP,第三部分就是VI dev和channel
dev就是用来采集图像的一个硬件单元,一个dev可能有好几个通道(分支),可以分成好几条道路(分支),然后
来进行不同的裁剪,并且每一个通道和后面进行绑定,dev就是一个整体管道,我这个管道有一个入口,从sensor这
边进去的,但是有3个甚至10出口(通道),每一个出口连接了后端的一个流程,
Hi3519v101可以接两路sensor自然就有两个dev和两个channel
9 VPSS部分详解
VPSS(Video Process Sub-System) 对一幅图像进行统一处理,不是针对某一块
去噪、去隔行(把隔行扫描的转成逐行扫描的,之前有的sensor是隔行扫描的,现在的sensor一般都是逐行扫描的了)
然后再对各通道分别进行缩放(我们出来的图像有1080p,有720p,VGA的等就是因为进行了缩放,是在VPSS这里来处理的)
最后输出多种不同分辨率的图像(从这里可以看出一进多出的,VI是一路进来,VPSS是多路输出的)。
FRC(Frame Rate Control) Crop(裁剪) NR(Noise Reduce) LDC(Lens Distortion Correction)
Rotate Cover/Overlay Scale(缩放) Mirror/Flip FishEye(鱼眼)
基本概念:
9.1 VPSS手册部分解读
9.2 VPSS的函数调用关系图谱
9.3 VPSS部分详解2
9.3.1 VPSS的Grp和Chn
Grp是一个组合,一个物理硬件VPSS在软件上的一个映射,如果我们只有一个Grp的话就是物理硬件VPSS在软件上的一个
1:1的一个映射。
chn是Grp里面的通道,这个通道有物理的有扩展的,物理通道有对应的硬件,扩展通道没有对应的硬件,扩展通道其实
是对应了一些功能(缩放等),
注意:VI的chn和VPSS的chn是两回事,VI那边的chn是VI里面Dev里面的chn,VI那边DEV的地位有点类似于VPSS这边的Grp。
对接的时候是VI这边的chn去绑定VPSS这边的Grp。
10.图像编码压缩基本原理
为什么需要压缩,因为原始的图像数据太多,不利于传输。
10.1 常见图像、视频、音频数据中存在的冗余类型:
(1)空间冗余
一幅图像表面上个采样点的颜色之间往往存在空间的连贯性,比如图片,两只老鼠的颜色,背后的墙,灰色的地板,颜色
都一样。这些颜色相同的块就可以压缩。
空间冗余主要发生在单张图片,比如我们的照片。
(2)时间冗余
这种冗余主要针对视频。
运动图像(视频)一般为位于一时间轴区间的一组连续画面,其中的相邻帧往往包含相同的背景和移动物体,只不过移动物体
所在的空间位置略有不同,所以后一帧的数据与前一帧的数据有许多共同的地方,这种共同性是由于相邻帧记录了相邻时刻的
同一场景画面,所以称为时间冗余。
(3)视觉冗余
人类视觉系统对细微的颜色差异感觉不明显。
说白了就是图像本身存的清晰度是高于人眼的识别度的,高出了的这些清晰度其实媚眼抛给瞎子了。
所以干脆把它去掉算了。去掉后对图像来说清晰度是下降了,但对人眼来说感觉不出来。
10.2 一些概念
压缩比
图像质量
压缩和解压缩的速度
H.264 profile: 图像清晰度的一个标准(可以理解为普通的清晰度,高清的清晰度)
BP(基本的,清晰度最差的,这种编码速度快,压缩比也会高)
MP(中等的,主流的)
HP(高清的,清晰度最好)
JPEG:用来做图片的,不是视频
MOTION JPEG(MJPEG) : 早于H.264的视频压缩标准,过时了
H.265 只支持MP
码率控制器(RC):什么是码率?码率就是数据传输时单位时间传送的数据位数。一般我们用的单位是kbps即千位每秒。
通俗一点理解就是取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件。
但是文件体积与取样率是成正比的,所以几乎所有的编码格式重视的都是如何用最低的码率达到最少的失真。
10.3 VPSS与VENC绑定关系
if(s32ChnNum >= 1)
{
VpssGrp = 0;
VpssChn = 0;
VencChn = 0; //从这里可以看出来VPSS的Chn0和Venc的Chn0绑定
s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[0],\
gs_enNorm, enSize[0], enRcMode,u32Profile,ROTATE_NONE);
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;
}
}
裸流:没有文件头,没有文件格式。文件头就是告诉别人我这个文件(视频,图片等)是什么格式的,比如是mp4的等等。
播放器拿到这个文件首先要获取我的文件头知道是什么格式的文件后才去处理。
视频裸流(只有视频数据)你拿到普通的播放器是播放不了的,