第二季5:配置视频捕获模块(step3:VI模块)

这篇具有很好参考价值的文章主要介绍了第二季5:配置视频捕获模块(step3:VI模块)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

前言

本文将详细介绍博文第二季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内部的硬件单元。

第二季5:配置视频捕获模块(step3:VI模块)

HI3518E的VI通道功能框图如下。这个VI设备只包含一个物理通道(即Chn0)。它支持720@30、1080@30等典型分辨率。这个物理通道(Chn0)和对应的VI设备(Dev0)是固定绑定的, 不能改变它们的绑定关系。HI3518E最多支持16个扩展通道,它们是物理通道的扩展(它们的数据来源于物理通道),主要实现缩放功能。

第二季5:配置视频捕获模块(step3:VI模块)

4、Sensor与SoC之间的接口

Sensor与SoC之间的接口主要包括MIPI、LVDS、DC(即并口),具体介绍见博客第4季2:并口、MIPI、LVDS的简介。

我们的AR0130和OV9712,和HI3518E之间的数据接口就是DC,而非MIPI。

5、宽动态(WDR)

简单地理解,宽动态技术即同一幅图的不同区域,其曝光程度不一样。

具体介绍见宽动态 (WDR)介绍和理解_Mr.TangR的博客。

第二季5:配置视频捕获模块(step3:VI模块)

实现宽动态这个功能需要硬件的支持,有些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

/* 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • LabVIEW应用开发——VI模块化

            我们在写C语言的时候,一些模块化的功能或者多次调用的功能,我们一般会用一个函数封装起来,方便使用并且让代码看起来更加的简洁。这种函数一般都会包含这几个概念, 输入参数 、 输出参数 和 返回值 。而LabVIEW的VI就可以当作是一个函数, 输入参数 就是

    2024年02月07日
    浏览(51)
  • 输入捕获模块的使用–超声波测距

    @(MSP432P401R) 输入捕获的配置 基本默认即可 输入捕获的API的使用 参数 Capture_Mode即捕获模式,经实际测试,MSP432P401R只能使用前三种模式 Capture_CallBackFxn即回调函数 Capture_PeriodUnits即捕获周期单位 函数表 全局配置,在ti_drivers_config.c文件中生成 功能函数 文档链接:file:///D:/MSP%

    2024年02月15日
    浏览(81)
  • Python灰帽——Scapy模块 / 数据包的构造、发送、接收、捕获

    \\\" 网络神器 \\\" scapy 是 python 的一个第三方模块,能够发送、捕获、分析和铸造网络数据包 主要功能:扫描、识别、测试、攻击、包铸造、抓包分析 在编辑器导入 scapy 包 简单构造 构造数据包 Scapy 中的分层结构 OSI 模型中的下层协议在前,以 / 隔开 Ether()/IP()/TCP() Ether 类用于设

    2024年02月22日
    浏览(45)
  • Debian11.7 配置vi非兼容模式

    对于最小化安装的 Debian11.7 ,起初有一个问题给我造成了困扰:那就是当我使用 vi 编辑文本文件时,我无法通过键入“i”来切换到输入模式,或者说,其实的确进入了输入模式,但是底部行并没有显式进行提示,另外,我无法使用 Backspace 键来删除字符,Delete 键是可以删除

    2024年02月08日
    浏览(37)
  • RK3588环境配置过程全记录 step 1

    最近在做毕设,由于是第一次接触NPU,所以踩了不少坑,顺便将它记录下来,防止后来者继续踩(doge)hh。 许多人都和我一样,刚到手拿到一块NPU算力版的时候都不知道从何下手,是先在PC端操作呢还是直接在板子上操作? 本人虽然熟悉一些类似于STM32 ,esp等板子,但是也是

    2024年02月09日
    浏览(51)
  • 【OpenCV常用函数:视频捕获函数】cv2.VideoCapture

    输入视频路径,创建VideoCapture的对象 该类的函数有: 1)video.isOpened: 检查视频捕获是否成功 2)video.read(): 读取视频帧,返回ret, frame,ret为bool类型,表示是否成功 3)video.release(): 关闭视频 4)video.get(prop): 获取video的属性 如果要读取视频的每一帧,然后进行相关的处理时,可

    2024年02月13日
    浏览(70)
  • C++ opencv设置视频的捕获方式为 MJPG设置失败

    我有一款4k摄像头,在设置分辨率为4k的时候总是出现帧率不够的情况, 使用命令查看 发现 因此我们需要设置视频捕获格式为 MJPG 报错如下: 这些警告可能是由于使用 GStreamer 后端而不是 V4L2(Video4Linux2)后端引起的。在某些系统上,默认情况下,OpenCV 使用 GStreamer 进行视频

    2024年02月07日
    浏览(31)
  • Python使用CV2库捕获和保存摄像头视频

    关于cv2库的安装和使用基础可参见https://blog.csdn.net/cnds123/article/details/126547307 特别提示:CV2指的是OpenCV2(Open Source Computer Vision Library),安装的时候是 opencv_python,但在导入的时候采用 import cv2。 学习本文需要你的计算机有摄像头,笔记本一般内置有摄像头,若是台式机可以连

    2024年02月16日
    浏览(43)
  • 在SpringBoot中使用FFmpegFrameGrabber捕获本地视频第一帧并保存

    目的是在上传MP4文件的时候就抓取到第一帧,保存在一个文件夹里,到时候前端调用就可以显示。毕设需要哈哈哈所以就做了。前端实现有点复杂,因为我还涉及了v-for的img去给js函数传参的问题。所以转用后端做一下。 特别感谢这位博主,提供了非常好的方法:StringBoot 通过

    2024年02月06日
    浏览(50)
  • Python使用CV2库捕获、播放和保存摄像头视频

    关于cv2库的安装和使用基础可参见https://blog.csdn.net/cnds123/article/details/126547307 特别提示:CV2指的是OpenCV2(Open Source Computer Vision Library),安装的时候是 opencv_python,但在导入的时候采用 import cv2。 学习本文需要你的计算机有摄像头,笔记本一般内置有摄像头,若是台式机可以连

    2024年02月03日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包