以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
前言
本文将详细介绍博文第二季3:sample_venc.c的整体分析提及的“配置视频捕获模块”。
分析方法上,我们首先介绍VI模块相关的宽动态、设备、通道等概念,然后绘制VI模块的函数调用关系图谱,接着讲解具体的代码细节。学习效果上,要把控全局,掌握一些新的概念和对应的数据结构,理解关键操作在哪里设置,将来需要修改的时候能找到地方。
一、VI模块的相关概念
1、离线/在线模式
VI和VPSS的协作模式分为以下 2 种:
VI/VPSS 离线模式,是指 VI 进行时序解析后将图像数据写出到 DDR,VPSS 从DDR 中载入 VI 采集的数据进行图像处理,是传统 Hi3518/Hi3520D 等芯片的VI/VPSS 的协作模式。
VI/VPSS 在线模式,是指 VI 进行时序解析后直接在芯片内部将数据传递到 VPSS,中间无 DDR 写出的过程。在线模式可以省一定的带宽和内存,降低端到端的延时。需要注意的是,在线模式时,因为 VI 不写出数据到 DDR,无法进行CoverEx、OverlayEx、 Rotate、 LDC 等操作,需要在 VPSS 各通道写出后再进行Rotate/LDC 等处理,而且有些功能只在离线下能支持,比如 DIS。
这两种模式的切换可以由 load 脚本(即博客第4季3:Hi3518e的sensor接口引脚复用设置中的脚本文件load3518e)中的参数 vi_vpss_online来控制。如下所示,在insert_ko()函数中有:
insert_ko()
{
# sys config
sys_config;
# driver load
insmod mmz.ko mmz=anonymous,0,$mmz_start,$mmz_size anony=1 || report_error
insmod hi_media.ko
insmod hi3518e_base.ko
insmod hi3518e_sys.ko vi_vpss_online=$b_arg_online sensor=$SNS_TYPE
//这里
#省略部分代码
}
我们可以在调用load3518e文件时传入参数offline,如果不设置这个参数,则默认为online。
我们做实验时都是没有设置这个参数的,因此都是online的。
2、VI模块的功能
通过 BT656/601/1120 接口或 DC 接口(即并口)、MIPI Rx(含 MIPI 接口、LVDS 接口和 HISPI 接口)接收视频数据。当工作在离线模式时,将接收到的数据存入到指定的内存区域;当工作在在线模式时,VI会将数据直接送给 VPSS。在此过程中,VI 模块可以对接收到的原始视频图像数据进行裁剪等处理,并实现一路原始视频图像输入,输出一路视频图像功能(VI设备只有一个且它的通道只有一个)。
3、VI模块的组成
VI模块包含三大内容:和sensor对接的部分(采用什么接口等等内容)、ISP、VI设备和通道。
其中ISP是“ image signal process ”的缩写,即图像信号处理。HI3518E内部集成了ISP硬件单元,这个ISP单元在功能上隶属于VI模块。
HI3518E的硬件单元功能框图如下,可知HI3518E芯片只有一个VI设备(即Dev0),它支持上面提到的那些接口的输入。注意,VI设备不是sensor,它是HI3518E内部的硬件单元。
HI3518E的VI通道功能框图如下。这个VI设备只包含一个物理通道(即Chn0)。它支持720@30、1080@30等典型分辨率。这个物理通道(Chn0)和对应的VI设备(Dev0)是固定绑定的, 不能改变它们的绑定关系。HI3518E最多支持16个扩展通道,它们是物理通道的扩展(它们的数据来源于物理通道),主要实现缩放功能。
4、Sensor与SoC之间的接口
Sensor与SoC之间的接口主要包括MIPI、LVDS、DC(即并口),具体介绍见博客第4季2:并口、MIPI、LVDS的简介。
我们的AR0130和OV9712,和HI3518E之间的数据接口就是DC,而非MIPI。
5、宽动态(WDR)
简单地理解,宽动态技术即同一幅图的不同区域,其曝光程度不一样。
具体介绍见宽动态 (WDR)介绍和理解_Mr.TangR的博客。
实现宽动态这个功能需要硬件的支持,有些sensor支持,有些sensor不支持,比如我们的AR0130和OV9712就不支持这个功能。
二、VI模块的函数调用关系
VI模块的函数调用关系如下所示(或者见链接)。
SAMPLE_COMM_VI_StartVi
IsSensorInput//此函数检测是否sensor输入,因为有的可能是其他输入方式
SAMPLE_COMM_VI_StartIspAndVi
step1:SAMPLE_COMM_VI_StartMIPI//此函数操作sensor驱动中的ioctl函数,对sensor进行必要的初始化
SAMPLE_COMM_VI_SetMipiAttr
fd = open("/dev/hi_mipi", O_RDWR);
ioctl(fd, HI_MIPI_SET_DEV_ATTR, pstcomboDevAttr)
step2:SAMPLE_COMM_ISP_Init//此函数初始化内部的ISP单元
sensor_register_callback//…………具体介绍在第4季4篇章。此函数在sensor的驱动里package\mpp\component\isp\sensor\ar0130\ar0130_cmos.c,可以看文档《ISP_3A开发指南.pdf》
HI_MPI_AE_Register//此函数注册AE单元,AE即自动曝光
HI_MPI_AWB_Register//此函数注册AWB单元,AWB即白平衡
HI_MPI_AF_Register//此函数注册自动对焦(AF)单元
HI_MPI_ISP_MemInit//此函数给ISP单元分配必要的内存
HI_MPI_ISP_SetWDRMode//此函数设置宽动态相关属性
HI_MPI_ISP_SetPubAttr//此函数通过传参(函数前填充了参数内容)告知ISP单元sensor的一些属性以便ISP
HI_MPI_ISP_Init//此函数初始化ISP
step3:SAMPLE_COMM_ISP_Run//此函数通过创建线程,让ISP运行
pthread_create(&gs_IspPid, &attr, (void* (*)(void*))Test_ISP_Run, NULL)
Test_ISP_Run
HI_MPI_ISP_Run
step4:SAMPLE_COMM_VI_StartDev//此函数打开(采集图像的)设备
HI_MPI_VI_SetDevAttr//设置dev的属性
HI_MPI_ISP_GetWDRMode
HI_MPI_VI_SetWDRAttr
HI_MPI_VI_EnableDev//启动dev单元
step5:SAMPLE_COMM_VI_StartChn//此函数打开通道
HI_MPI_VI_SetChnAttr//设置通道属性
HI_MPI_VI_SetRotate
HI_MPI_VI_EnableChn//打开通道
由此可知,该模块涉及以下几个步骤:
- Sensor的初始化操作
- ISP单元的初始化与运行操作(注册3A、设置宽动态、初始化ISP、运行 ISP等内容)
- 打开采集图像的设备(设置设备的属性,然后启动设备)
- 打开通道(设置通道的属性,然后打开通道)
下面我们将详细介绍这几个步骤涉及到的概念与代码细节。
三、VI模块代码详解
1、VI模块的整体代码
其中stViConfig这个变量的数据类型是SAMPLE_VI_CONFIG_S,其成员enViMode表示摄像头sensor的种类,不同sensor有着不同的分辨率和帧率;enRotate表示是否旋转图像;enNorm表示图像制式;enViChnSet表示是否将图像flip或者mirror。具体介绍见博客内容第二季4:MPP模块的初始化。
/******************************************
step 3: start vi dev & chn to capture
******************************************/
stViConfig.enViMode = SENSOR_TYPE;//sensor的类型定义在Makefile.param文件中
stViConfig.enRotate = ROTATE_NONE;//图像是否旋转
stViConfig.enNorm = VIDEO_ENCODING_MODE_AUTO;//图像制式
stViConfig.enViChnSet = VI_CHN_SET_NORMAL;//是否flip或mirror
stViConfig.enWDRMode = WDR_MODE_NONE;//设置宽动态相关内容
s32Ret = SAMPLE_COMM_VI_StartVi(&stViConfig);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("start vi failed!\n");
goto END_VENC_MJPEG_JPEG_1;
}
SAMPLE_COMM_VI_StartVi函数位于sample_comm_venc.c文件,代码内容如下。
HI_S32 SAMPLE_COMM_VI_StartVi(SAMPLE_VI_CONFIG_S* pstViConfig)
{
HI_S32 s32Ret = HI_SUCCESS;
SAMPLE_VI_MODE_E enViMode;
if(!pstViConfig)
{
SAMPLE_PRT("%s: null ptr\n", __FUNCTION__);
return HI_FAILURE;
}
enViMode = pstViConfig->enViMode;
if(!IsSensorInput(enViMode))
{
s32Ret = SAMPLE_COMM_VI_StartBT656(pstViConfig);//从其他渠道,比如电视图像信号
}
else
{
s32Ret = SAMPLE_COMM_VI_StartIspAndVi(pstViConfig);//此处是sensor输入的,我们分析这个路线
}
return s32Ret;
}
其中IsSensorInput函数内部通过根据传参是否为某个具体型号的sensor,来判断图像数据是否为sensor输入,这里不再赘述。SAMPLE_COMM_VI_StartIspAndVi函数是真正开启VI模块的函数,我们将详细分析。
2、函数SAMPLE_COMM_VI_StartIspAndVi的分析
函数SAMPLE_COMM_VI_StartIspAndVi位于sample_comm_venc.c文件中,具体内容如下。
HI_S32 SAMPLE_COMM_VI_StartIspAndVi(SAMPLE_VI_CONFIG_S* pstViConfig)
{
HI_S32 i, s32Ret = HI_SUCCESS;
VI_DEV ViDev;
VI_CHN ViChn;
HI_U32 u32DevNum = 1;
HI_U32 u32ChnNum = 1;
SIZE_S stTargetSize;
RECT_S stCapRect;
SAMPLE_VI_MODE_E enViMode;
if(!pstViConfig)
{
SAMPLE_PRT("%s: null ptr\n", __FUNCTION__);
return HI_FAILURE;
}
enViMode = pstViConfig->enViMode;
/******************************************
step 1: mipi configure
******************************************/
s32Ret = SAMPLE_COMM_VI_StartMIPI(pstViConfig);
if (HI_SUCCESS != s32Ret)
{
SAMPLE_PRT("%s: MIPI init failed!\n", __FUNCTION__);
return HI_FAILURE;
}
/******************************************
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;
}
/******************************************
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
******************************************************/
for (i = 0; i < u32DevNum; i++)
{
ViDev = i; //设备号 设备类型
s32Ret = SAMPLE_COMM_VI_StartDev(ViDev, enViMode);
if (HI_SUCCESS != s32Ret) //这里的设备,也就是sensor。
{
SAMPLE_PRT("%s: start vi dev[%d] failed!\n", __FUNCTION__, i);
return HI_FAILURE;
}
}
/******************************************************
* Step 5: config & start vicap chn (max 1)
******************************************************/
for (i = 0; i < u32ChnNum; i++)
{
ViChn = i;
stCapRect.s32X = 0;
stCapRect.s32Y = 0;
switch (enViMode)
{
case APTINA_9M034_DC_720P_30FPS:
case APTINA_AR0130_DC_720P_30FPS:
case SONY_IMX222_DC_720P_30FPS:
case OMNIVISION_OV9712_DC_720P_30FPS:
case OMNIVISION_OV9732_DC_720P_30FPS:
case OMNIVISION_OV9750_MIPI_720P_30FPS:
case OMNIVISION_OV9752_MIPI_720P_30FPS:
stCapRect.u32Width = 1280;
stCapRect.u32Height = 720;
break;
case SONY_IMX222_DC_1080P_30FPS:
case APTINA_AR0230_HISPI_1080P_30FPS:
case PANASONIC_MN34222_MIPI_1080P_30FPS:
case OMNIVISION_OV2718_MIPI_1080P_25FPS:
stCapRect.u32Width = 1920;
stCapRect.u32Height = 1080;
break;
default:
stCapRect.u32Width = 1920;
stCapRect.u32Height = 1080;
break;
}
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;
}
}
return s32Ret;
}
可知该函数将整个过程划分为5个步骤,具体分析如下。
(1)step1:配置MIPI
这一步主要是函数SAMPLE_COMM_VI_StartMIPI(pstViConfig)。其中pstViConfig变量是上层函数传过来的、指向SAMPLE_VI_CONFIG_S类型变量stViConfig的指针。
该函数又调用SAMPLE_COMM_VI_SetMipiAttr(pstViConfig),后者函数内容如下:
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;
}
函数开头定义了一个combo_dev_attr_t类型的指针变量pstcomboDevAttr,然后根据sensor的型号来给这个指针变量赋值,赋值的内容已经代码写好(这些内容厂商一般都提供的)。
然后打开名叫“/dev/hi_mipi”的设备文件,利用ioctl函数,以fd、pstcomboDevAttr、HI_MIPI_SET_DEV_ATTR作为参数,对MIPI接口进行配置。值得一提的是,我们的sensor和HI3518E之间的数据接口不是MIPI,这里的MIPI应该是对sensor和HI3518E之间的接口的一种统称而已,并不是说接口就是MIPI。这里配置MIPI,其实是对sensor进行一些初始化。
combo_dev_attr_t类型定义如下:
typedef struct
{
input_mode_t input_mode; /* input mode: MIPI/LVDS/SUBLVDS/HISPI/DC */
union
{
mipi_dev_attr_t mipi_attr;
lvds_dev_attr_t lvds_attr;
};
}combo_dev_attr_t;
(2)step2:初始化ISP
这一步主要是函数SAMPLE_COMM_ISP_Init(pstViConfig->enWDRMode)。
其中pstViConfig变量指向stViConfig变量的指针,这个变量的成员enWDRMode表示宽动态相关的内容,定义如下。
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;
我们来看一下函数SAMPLE_COMM_ISP_Init的内容。根据本文第一节,该函数的调用关系如下:
step2:SAMPLE_COMM_ISP_Init//此函数初始化内部的ISP单元
sensor_register_callback
//具体介绍见第4季4。
//此函数在sensor驱动mpp\component\isp\sensor\ar0130\ar0130_cmos.c里
//具体介绍见文档《ISP_3A开发指南.pdf》
HI_MPI_AE_Register//此函数注册AE单元,AE即自动曝光
HI_MPI_AWB_Register//此函数注册AWB单元,AWB即白平衡
HI_MPI_AF_Register//此函数注册自动对焦(AF)单元
HI_MPI_ISP_MemInit//此函数给ISP单元分配必要的内存
HI_MPI_ISP_SetWDRMode//此函数设置宽动态相关属性
HI_MPI_ISP_SetPubAttr//此函数通过传参告知ISP单元sensor的一些属性以便ISP
HI_MPI_ISP_Init//此函数初始化ISP
首先定义了ISP_PUB_ATTR_S结构体变量stPubAttr,以及ALG_LIB_S结构体变量stLib。这两个结构体的定义如下:
typedef struct hiISP_PUB_ATTR_S
{
RECT_S stWndRect; /* RW. */
HI_FLOAT f32FrameRate; /* RW. */
ISP_BAYER_FORMAT_E enBayer; /* RW. */
} ISP_PUB_ATTR_S;
typedef struct hiALG_LIB_S
{
HI_S32 s32Id;
HI_CHAR acLibName[20];
} ALG_LIB_S;
然后先填充stLib变量的成员,用来先后注册AE、AWB、AF单元。
接着定义ISP_WDR_MODE_S结构体变量stWdrMode,并填充其成员enWDRMode,然后利用HI_MPI_ISP_SetWDRMode函数来设置宽动态的内容。ISP_WDR_MODE_S结构体的定义如下:
typedef struct hiISP_WDR_MODE_S
{
WDR_MODE_E enWDRMode;
} ISP_WDR_MODE_S;
接着根据sensor型号来填充stPubAttr变量的成员,然后调用HI_MPI_ISP_SetPubAttr函数来设置。
最后调用HI_MPI_ISP_Init函数来初始化ISP。
(3)step3:运行ISP
这一步主要是开启Test_ISP_Run线程来运行ISP。
/******************************************************************************
* funciton : ISP Run
******************************************************************************/
HI_S32 SAMPLE_COMM_ISP_Run()
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 4096 * 1024);
if (0 != pthread_create(&gs_IspPid, &attr, (void* (*)(void*))Test_ISP_Run, NULL))
{
printf("%s: create isp running thread failed!\n", __FUNCTION__);
pthread_attr_destroy(&attr);
return HI_FAILURE;
}
usleep(1000);
pthread_attr_destroy(&attr);
}
Test_ISP_Run线程中调用HI_MPI_ISP_Run(IspDev)函数(参数IspDev值为0),函数内容如下:
/*****************************************************************************
Prototype : HI_MPI_ISP_Run
Description : isp firmware recurrent task, always run in a single thread.
Input : I_VOID **
Output : None
Return Value :
Process :
Note :
History
1.Date : 2011/1/13
Author : x00100808
Modification : Created function
*****************************************************************************/
HI_S32 HI_MPI_ISP_Run(ISP_DEV IspDev)
{
HI_S32 s32Ret;
HI_U32 u32IntStatus = 0;
HI_BOOL bEn;
HI_U32 u32WDRmode;
ISP_CTX_S *pstIspCtx = HI_NULL;
/* 1. check status */
ISP_CHECK_DEV(IspDev);
ISP_GET_CTX(IspDev, pstIspCtx);
ISP_CHECK_POINTER(pstIspCtx);
ISP_CHECK_OPEN(IspDev);
ISP_CHECK_SENSOR_REGISTER(IspDev);
ISP_CHECK_MEM_INIT(IspDev);
ISP_CHECK_ISP_INIT(IspDev);
if (HI_TRUE == pstIspCtx->stIspParaRec.bRun)
{
ISP_TRACE(HI_DBG_ERR, "ISP[%d] Run failed!\n", IspDev);
return HI_ERR_ISP_ILLEGAL_PARAM;
}
pthread_mutex_lock(&pstIspCtx->stLock);
/* Sometimes HI_MPI_ISP_Run thread is not scheduled to run before calling HI_MPI_ISP_Exit. */
if (HI_FALSE == pstIspCtx->stIspParaRec.bRunEn)
{
pthread_mutex_unlock(&pstIspCtx->stLock);
return HI_SUCCESS;
}
/* 2. enable interrupt */
bEn = HI_TRUE;
if (ioctl(g_as32IspFd[IspDev], ISP_SET_INT_ENABLE, &bEn) < 0)
{
ISP_TRACE(HI_DBG_ERR, "Enable ISP[%d] interrupt failed!\n", IspDev);
pthread_mutex_unlock(&pstIspCtx->stLock);
return -1;
}
pstIspCtx->stIspParaRec.bRun = HI_TRUE;
pthread_mutex_unlock(&pstIspCtx->stLock);
while (1)
{
pthread_mutex_lock(&pstIspCtx->stLock);
if (HI_FALSE == pstIspCtx->stIspParaRec.bRunEn)
{
pthread_mutex_unlock(&pstIspCtx->stLock);
break;
}
/*change resolution */
ISP_SwitchImageMode(IspDev);
u32WDRmode = hi_ext_system_sensor_wdr_mode_read();
/* swtich linear/WDR mode, width/height, fps */
if (pstIspCtx->u8SnsWDRMode != u32WDRmode)
{
pstIspCtx->u8SnsWDRMode = u32WDRmode;
ISP_SwitchWDRMode(IspDev);
}
{
u32IntStatus = 0;
/* 3. waked up by the interrupt */
s32Ret = ioctl(g_as32IspFd[IspDev], ISP_GET_FRAME_EDGE, &u32IntStatus);
if (s32Ret)
{
}
else
{
/* 4.isp firmware calculate, include AE/AWB, etc. */
if (ISP_1ST_INT & u32IntStatus)
{
ISP_Run(IspDev);
}
}
}
pthread_mutex_unlock(&pstIspCtx->stLock);
usleep(10);
}
/* 8. disable interrupt */
bEn = HI_FALSE;
if (ioctl(g_as32IspFd[IspDev], ISP_SET_INT_ENABLE, &bEn) < 0)
{
ISP_TRACE(HI_DBG_ERR, "Disable ISP[%d] interrupt failed!\n", IspDev);
}
return HI_SUCCESS;
}
(4)step4:配置与打开VI设备
这一步涉及函数SAMPLE_COMM_VI_StartDev(ViDev, enViMode),其中ViDev表示设备号(设备号也就是sensor号,这里只有一个,为0),enViMode表示sensor的型号。
我们来看一下SAMPLE_COMM_VI_StartDev函数的内部。
/*****************************************************************************
* function : star vi dev (cfg vi_dev_attr; set_dev_cfg; enable dev)
*****************************************************************************/
HI_S32 SAMPLE_COMM_VI_StartDev(VI_DEV ViDev, SAMPLE_VI_MODE_E enViMode)
{
HI_S32 s32Ret;
HI_S32 s32IspDev = 0;
ISP_WDR_MODE_S stWdrMode;
VI_DEV_ATTR_S stViDevAttr;//重点关注这个设备属性结构体
memset(&stViDevAttr,0,sizeof(stViDevAttr));//接下来将填充设备属性结构体的内容
switch (enViMode)
{
//省略部分代码
case APTINA_AR0130_DC_720P_30FPS:
memcpy(&stViDevAttr,&DEV_ATTR_9M034_DC_720P_BASE,sizeof(stViDevAttr));
break;
//省略部分代码
case OMNIVISION_OV9712_DC_720P_30FPS:
case OMNIVISION_OV9732_DC_720P_30FPS:
memcpy(&stViDevAttr,&DEV_ATTR_OV9732_DC_720P_BASE,sizeof(stViDevAttr));
stViDevAttr.stDevRect.s32X = 0;
stViDevAttr.stDevRect.s32Y = 0;
stViDevAttr.stDevRect.u32Width = 1280;
stViDevAttr.stDevRect.u32Height = 720;
break;
case OMNIVISION_OV9750_MIPI_720P_30FPS:
case OMNIVISION_OV9752_MIPI_720P_30FPS:
memcpy(&stViDevAttr,&DEV_ATTR_MIPI_BASE,sizeof(stViDevAttr));
stViDevAttr.stDevRect.s32X = 0;
stViDevAttr.stDevRect.s32Y = 0;
stViDevAttr.stDevRect.u32Width = 1280;
stViDevAttr.stDevRect.u32Height = 720;
break;
case OMNIVISION_OV2718_MIPI_1080P_25FPS:
memcpy(&stViDevAttr,&DEV_ATTR_MIPI_BASE,sizeof(stViDevAttr));
stViDevAttr.stDevRect.s32X = 0;
stViDevAttr.stDevRect.s32Y = 0;
stViDevAttr.stDevRect.u32Width = 1920;
stViDevAttr.stDevRect.u32Height = 1080;
break;
default:
memcpy(&stViDevAttr,&DEV_ATTR_LVDS_BASE,sizeof(stViDevAttr));
}
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;
}
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;
}
VI_WDR_ATTR_S stWdrAttr;
stWdrAttr.enWDRMode = stWdrMode.enWDRMode;
stWdrAttr.bCompress = HI_FALSE;
s32Ret = HI_MPI_VI_SetWDRAttr(ViDev, &stWdrAttr);//设置设备的宽动态
if (s32Ret)
{
SAMPLE_PRT("HI_MPI_VI_SetWDRAttr failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
}
s32Ret = HI_MPI_VI_EnableDev(ViDev);//启动设备
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("HI_MPI_VI_EnableDev failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
return HI_SUCCESS;
}
因为这一步是要配置与打开设备,所以首先定义了一个设备属性结构体变量stViDevAttr,然后根据sensor的型号来填充stViDevAttr的成员。填充时使用 memcpy 函数,它的参数1就是stViDevAttr这个结构体,参数2表示具体型号的sensor的设备属性(具体属性已经写死在代码里),参数2的内容将拷贝到参数1中。完成stViDevAttr这个结构体部分成员的填充后,调用HI_MPI_VI_SetDevAttr函数来设置设备的属性。
其中,设备属性结构体的定义如下。
/* the attributes of a VI device */
typedef struct hiVI_DEV_ATTR_S
{
VI_INTF_MODE_E enIntfMode; /* Interface mode */
VI_WORK_MODE_E enWorkMode; /*1-, 2-, or 4-channel multiplexed work mode */
HI_U32 au32CompMask[2]; /* Component mask */
VI_SCAN_MODE_E enScanMode; /* Input scanning mode (progressive or interlaced) */
HI_S32 s32AdChnId[4]; /* AD channel ID. Typically, the default value -1 is recommended */
/* The below members must be configured in BT.601 mode or DC mode and are invalid in other modes */
VI_DATA_YUV_SEQ_E enDataSeq; /* Input data sequence (only the YUV format is supported) */
VI_SYNC_CFG_S stSynCfg; /* Sync timing. This member must be configured in BT.601 mode or DC mode */
VI_DATA_PATH_E enDataPath; /* ISP enable or bypass */
VI_DATA_TYPE_E enInputDataType; /* RGB: CSC-709 or CSC-601, PT YUV444 disable; YUV: default yuv CSC coef PT YUV444 enable. */
HI_BOOL bDataRev; /* Data reverse */
RECT_S stDevRect; /* Dev capture rect */
} VI_DEV_ATTR_S;
然后定义一个宽动态相关的结构体变量stWdrAttr,接下来也是填充stWdrAttr这个结构体变量的成员,最后使用HI_MPI_VI_SetWDRAttr来设置宽动态的相关内容。
最后,调用HI_MPI_VI_EnableDev函数来启动设备。
(5)step5:配置与打开VI通道
step5首先根据sensor的型号填充结构体变量stCapRect、stTargetSize的成员,这两个结构体变量成员都包括图像分辨率里的w与h,这里即填充w与h。
然后调用SAMPLE_COMM_VI_StartChn(ViChn, &stCapRect, &stTargetSize, pstViConfig)来打开通道。其中参数1的ViChn表示要打开的通道编号(这里只有一个通道,编号为0),参数2和参数3都是输出型参数,参数4是在上一层函数传过来的stViConfig变量,这个变量含有VI模块相关的设置信息(见本文第二部分的描述)。
我们来看一下这个函数的内容。
/*****************************************************************************
* function : star vi chn
*****************************************************************************/
HI_S32 SAMPLE_COMM_VI_StartChn(VI_CHN ViChn, RECT_S *pstCapRect, SIZE_S *pstTarSize, SAMPLE_VI_CONFIG_S* pstViConfig)
{
HI_S32 s32Ret;
VI_CHN_ATTR_S stChnAttr;
ROTATE_E enRotate = ROTATE_NONE;
SAMPLE_VI_CHN_SET_E enViChnSet = VI_CHN_SET_NORMAL;
if(pstViConfig)
{
enViChnSet = pstViConfig->enViChnSet;
enRotate = pstViConfig->enRotate;
}
/* step 5: config & start vicap dev */
memcpy(&stChnAttr.stCapRect, pstCapRect, sizeof(RECT_S));
stChnAttr.enCapSel = VI_CAPSEL_BOTH;
/* to show scale. this is a sample only, we want to show dist_size = D1 only */
stChnAttr.stDestSize.u32Width = pstTarSize->u32Width;
stChnAttr.stDestSize.u32Height = pstTarSize->u32Height;
stChnAttr.enPixFormat = PIXEL_FORMAT_YUV_SEMIPLANAR_420; /* sp420 or sp422 */
stChnAttr.bMirror = HI_FALSE;
stChnAttr.bFlip = HI_FALSE;
switch(enViChnSet)
{
case VI_CHN_SET_MIRROR:
stChnAttr.bMirror = HI_TRUE;
break;
case VI_CHN_SET_FLIP:
stChnAttr.bFlip = HI_TRUE;
break;
case VI_CHN_SET_FLIP_MIRROR:
stChnAttr.bMirror = HI_TRUE;
stChnAttr.bFlip = HI_TRUE;
break;
default:
break;
}
stChnAttr.s32SrcFrameRate = -1;
stChnAttr.s32DstFrameRate = -1;
stChnAttr.enCompressMode = COMPRESS_MODE_NONE;
s32Ret = HI_MPI_VI_SetChnAttr(ViChn, &stChnAttr);
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
if(ROTATE_NONE != enRotate)
{
s32Ret = HI_MPI_VI_SetRotate(ViChn, enRotate);
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("HI_MPI_VI_SetRotate failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
}
s32Ret = HI_MPI_VI_EnableChn(ViChn);
if (s32Ret != HI_SUCCESS)
{
SAMPLE_PRT("failed with %#x!\n", s32Ret);
return HI_FAILURE;
}
return HI_SUCCESS;
}
由此可知,该函数首先定义通道属性结构体变量stChnAttr,然后再填充该变量的成员,接着利用HI_MPI_VI_SetChnAttr函数来设置通道属性,最后利用HI_MPI_VI_EnableChn函数来打开通道。文章来源:https://www.toymoban.com/news/detail-427864.html
其中,通道属性结构体的定义如下。文章来源地址https://www.toymoban.com/news/detail-427864.html
/* the attributes of a VI channel */
typedef struct hiVI_CHN_ATTR_S
{
RECT_S stCapRect; /* the capture rect (corresponding to the size of the picture captured by a VI device).
For primary channels, the stCapRect's u32Width and u32Height are static attributes. That is,
the value of them can be changed only after primary and secondary channels are disabled.
For secondary channels, stCapRect is an invalid attribute */
SIZE_S stDestSize; /* Target picture size.
For primary channels, stDestSize must be equal to stCapRect's u32Width and u32Height,
because primary channel doesn't have scale capability. Additionally, it is a static
attribute, That is, the value of stDestSize can be changed only after primary and
secondary channels are disabled.
For secondary channels, stDestSize is a dynamic attribute */
VI_CAPSEL_E enCapSel; /* Frame/field select. It is used only in interlaced mode.
For primary channels, enCapSel is a static attribute */
PIXEL_FORMAT_E enPixFormat; /* Pixel storage format. Only the formats semi-planar420 and semi-planar422 are supported */
COMPRESS_MODE_E enCompressMode; /* 256B Segment compress or no compress. */
HI_BOOL bMirror; /* Whether to mirror */
HI_BOOL bFlip; /* Whether to flip */
HI_S32 s32SrcFrameRate; /* Source frame rate. The value -1 indicates that the frame rate is not controlled */
HI_S32 s32DstFrameRate; /* Target frame rate. The value -1 indicates that the frame rate is not controlled */
} VI_CHN_ATTR_S;
到了这里,关于第二季5:配置视频捕获模块(step3:VI模块)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!