海思多媒体(MPP)开发(8)——获取VI中的YUV数据

前言:

    海思多媒体处理平台(MPP)分为:视频输入(VI),视频处理(VPSS),视频编码(VENC),视频解码(VDEC),视频输出(VO)、视频侦测分析(VDA),音频输入(AI),音频输出(AO),音频编码(AENC),音频解码(ADEC),区域管理(REGION)等模块. 这里介绍视频输入模块(VI)中的直接获取YUV数据。

介绍:

    在正常的视频编码和视频输出的使用中,我们都是将VI通道绑定到其它的模块去,为其它的模块提供图像数据流。如果应用程序需要获取摄像头的原始YUV数据的时候,数据并不是从VO或是VENC模块去获取,海思有为这种使用场景预留接口,直接从VI获取图像原始数据。其中主要使用到这两个接口:HI_MPI_VI_SetFrameDepth 和HI_MPI_VI_GetFrame

HI_MPI_VI_GetFrame:

    设置可获取的 VI 图像最大深度。

    HI_S32 HI_MPI_VI_SetFrameDepth(VI_CHN ViChn, HI_U32 u32Depth);

此接口用于设置某一 VI 通道缓存的视频图像帧数。当用户设置缓存多帧视频图像时,用户可以获取到一定数目的连续图像数据。

注意事项:

(1)若指定 u32Depth 为 0,表示不需要系统为该 VI 通道缓存图像,故用户获取不到该VI 通道图像数据。系统默认不为 VI 通道缓存图像,即 u32Depth 默认为 0。

(2)若指定 u32Depth>0,系统将为该 VI 通道缓存 u32Depth 个图像,用户可通过接口HI_MPI_VI_GetFrame 获取该 VI 通道图像数据。具体分为以下几种情况:

  • 用户一直不获取图像。

系统将自动更新最旧的图像数据,保证用户一旦开始获取,就可获取到最近最新的 u32Depth 个连续图像。

  • 用户连续获取 u32Depth 次并一直不释放。

系统因获取不到 VB 而自动停止缓存新的 VI 图像,用户也不能获取新的 VI 图像。故建议用户保证获取和释放接口配对使用。

  • 用户获取/释放的速度比 VI 通道产生图像的速度慢。

系统将自动更新用户仍未获取的最旧的图像数据,保证缓存的图像队列为最近的新 VI 图像。由于用户不能保证获取速度,导致获取的可能不是连续图像。

(3)系统为每个 VI 通道缓存的 u32Depth 个 VI 图像数据,占用 MPP 内部的缓存块(简称 VB)。故用户应通过接口 HI_MPI_VB_SetConf 设置足够的 VB,否则可能由于系统缓存图像占用过多 VB,从而影响 VI 的正常图像采集,导致用户获取不到 VI 图像数据。支持动态调整 u32Depth。如用户在不需要获取该 VI 通道数据时,可设置 u32Depth 为 0,以减少 VI 通道占用内部 VB 的数量;在需要获取时,再设置 u32Depth 为合适的值,即可获取到设置时刻后的连续 VI 图像。

HI_MPI_VI_GetFrame

获取 VI 采集的图像。

HI_S32 HI_MPI_VI_GetFrame(VI_CHN ViChn, VIDEO_FRAME_INFO_S *pstFrameInfom,HI_S32 s32MilliSec);    

注意事项

  1. 此接口可以获取指定 VI 通道的视频图像信息。图像信息主要包括:图像的宽度、高度、像素格式、时间戳以及 YUV 各分量的物理地址。
  2. 此接口需在通道已启用后才有效。
  3. 支持多次获取后再释放,但建议获取和释放接口配对使用。
  4. 获取的物理地址信息来自 MPP 内部使用的 VideoBuffer,因此使用完之后,必须要调用 HI_MPI_VI_ReleaseFrame 接口释放其内存。
  5. pstFrameInfo -> stVFrame .u32PhyAddr[0]和 pstFrameInfo ->stVFrame .u32PhyAddr[1]分别指向图像的亮度分量和色度分量的物理地址。

实例介绍:

 (一)初始化VI通道

要从VI模块里面获取视频数据,首先需要初始化AD外设,然后初始化VI中的通道。

/******************************************************** 
Function:    BIAO_Get_VI_Frame
Description: 初始化VI  ,然后直接获取YUV数据
Input:  none
OutPut: none
Return: 0: success,none 0:error
Others: 
Author: Caibiao Lee
Date:   2020-02-02
*********************************************************/
int BIAO_Get_VI_Frame(void)
{
    SAMPLE_VI_MODE_E enViMode = SAMPLE_VI_MODE_2_720P;
    VIDEO_NORM_E enNorm = VIDEO_ENCODING_MODE_PAL;

    HI_U32 u32ViChnCnt = 4;
    HI_S32 s32VpssGrpCnt = 4;
    
    VB_CONF_S stVbConf;   

    HI_S32 i;
    HI_S32 s32Ret = HI_SUCCESS;
    HI_U32 u32BlkSize;
    HI_CHAR ch;
    SIZE_S stSize;
    HI_U32 u32WndNum;

    /******************************************
     step  1: init variable 
    ******************************************/ 
    memset(&stVbConf,0,sizeof(VB_CONF_S));
    u32BlkSize = SAMPLE_COMM_SYS_CalcPicVbBlkSize(enNorm,\
                PIC_HD720, SAMPLE_PIXEL_FORMAT, SAMPLE_SYS_ALIGN_WIDTH,COMPRESS_MODE_SEG);
    stVbConf.u32MaxPoolCnt = 128;

    /* video buffer*/
    //todo: vb=15
    stVbConf.astCommPool[0].u32BlkSize = u32BlkSize;
    stVbConf.astCommPool[0].u32BlkCnt = u32ViChnCnt * 8;

    /******************************************
     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 ERROR_0;
    }

    /******************************************
     step 3: start vi dev & chn
    ******************************************/
    s32Ret = SAMPLE_COMM_VI_Start(enViMode, enNorm);
    if (HI_SUCCESS != s32Ret)
    {
        SAMPLE_PRT("start vi failed!\n");
        goto ERROR_1;
    }

    BIAO_MPP_GET_VI_Frame(0);

ERROR_1: 
    SAMPLE_COMM_VI_Stop(enViMode);
    
ERROR_0:    
    SAMPLE_COMM_SYS_Exit();

}

(二)获取YUV数据

使用mpp系统接口获取YUV数据,然后将它们保存成文件:

/******************************************************** 
Function:    BIAO_MPP_GET_VI_Frame
Description: 循环获取YUV数据
Input:  s32ViChn 通道号
OutPut: none
Return: 0: success,none 0:error
Others: 
Author: Caibiao Lee
Date:   2020-02-02
*********************************************************/
void BIAO_MPP_GET_VI_Frame(int s32ViChn)
{

    int s32Ret=-1;
    unsigned int u32Depth;
    unsigned int i = 10;
    unsigned int j = 0;
    unsigned int u32Size = 0;
    VIDEO_FRAME_INFO_S stFrameInfom;
    HI_S32 s32MilliSec;
    FILE * l_Fp = NULL;
    unsigned char *l_pUserAddr;

    s32Ret=HI_MPI_VI_GetFrameDepth(s32ViChn, &u32Depth);

    if(s32Ret!=0)
    {
        printf("%s %d HI_MPI_VI_GetFrameDepth(%d) err=%#x\n",__FUNCTION__,__LINE__,
            s32ViChn, s32Ret);
        return -1;
    }

    if(u32Depth==0)
    {
        s32Ret=HI_MPI_VI_SetFrameDepth(s32ViChn, 1);
        if(s32Ret!=0)
        {
            printf("%s %d HI_MPI_VI_SetFrameDepth(%d) err=%#x\n",
                __FUNCTION__,__LINE__, s32ViChn, s32Ret);
            return -2;
        }
    }

    i=10;
    while(i--)
    {
        s32Ret=HI_MPI_VI_GetFrame(s32ViChn, &stFrameInfom, s32MilliSec);
        if(s32Ret!=0)
        {
            printf("%s %d HI_MPI_VI_GetFrame(%d) err=%#x\n",__FUNCTION__,__LINE__,
                s32ViChn, s32Ret);
             usleep(100000);
            //return -3;
            continue;
        }

        if(i!=1)
        {
            sleep(1);
            continue;
        }
        
        printf("u32Width        = 0x%x \n",stFrameInfom.stVFrame.u32Width);
        printf("u32Height       = 0x%x \n",stFrameInfom.stVFrame.u32Height);
        printf("u32Field        = 0x%x \n",stFrameInfom.stVFrame.u32Field);
        printf("enPixelFormat   = 0x%x \n",stFrameInfom.stVFrame.enPixelFormat);
        printf("enVideoFormat   = 0x%x \n\n",stFrameInfom.stVFrame.enVideoFormat);

        for(j=0;j<3;j++)
        {
            printf("j = %d u32PhyAddr    = 0x%x \n",j,stFrameInfom.stVFrame.u32PhyAddr[j]);
            printf("j = %d pVirAddr        = 0x%x \n",j,stFrameInfom.stVFrame.pVirAddr[j]);
            printf("j = %d u32Stride       = 0x%x \n",j,stFrameInfom.stVFrame.u32Stride[j]);
            printf("j = %d u32HeaderPhyAddr= 0x%x \n",j,stFrameInfom.stVFrame.u32HeaderPhyAddr[j]);
            printf("j = %d pHeaderVirAddr  = 0x%x \n",j,stFrameInfom.stVFrame.pHeaderVirAddr[j]);
            printf("j = %d u32HeaderStride = 0x%x\n\n",j,stFrameInfom.stVFrame.u32HeaderStride[j]); 
        }

        printf("s16OffsetBottom = 0x%x \n",stFrameInfom.stVFrame.s16OffsetBottom);
        printf("s16OffsetLeft   = 0x%x \n",stFrameInfom.stVFrame.s16OffsetLeft);
        printf("s16OffsetRight  = 0x%x \n",stFrameInfom.stVFrame.s16OffsetRight);
        printf("u64pts          = 0x%x \n",stFrameInfom.stVFrame.u64pts);
        printf("u32TimeRef      = 0x%x \n",stFrameInfom.stVFrame.u32TimeRef);
        printf("u32PrivateData  = 0x%x \n",stFrameInfom.stVFrame.u32PrivateData); 
        printf("enFlashType     = 0x%x\n\n",stFrameInfom.stVFrame.stSupplement.enFlashType);

        
#if 0
        l_Fp = fopen("yuv420.yuv","w+");
        if(NULL==l_Fp)
        {
            printf("%s %d file open error \n",__FUNCTION__,__LINE__);
            break;
        }

        /**Y 分量**/
        u32Size = stFrameInfom.stVFrame.u32Stride[0]*stFrameInfom.stVFrame.u32Height;
        s32Ret = fwrite(stFrameInfom.stVFrame.u32PhyAddr[0],1,u32Size,l_Fp);
        if(s32Ret!=u32Size)
        {
            fclose(l_Fp);
            printf("%s %d fwrite file error %d \n",__FUNCTION__,__LINE__,s32Ret);
            break;
        }else
        {
            printf("%s %d write file len = %d \n",__FUNCTION__,__LINE__,s32Ret);
        }


        /**UV 分量**/
        u32Size = stFrameInfom.stVFrame.u32Stride[1]*stFrameInfom.stVFrame.u32Height;
        s32Ret = fwrite(stFrameInfom.stVFrame.u32PhyAddr[1],1,u32Size,l_Fp);
        if(s32Ret!=u32Size)
        {
            fclose(l_Fp);
            printf("%s %d fwrite file error %d \n",__FUNCTION__,__LINE__,s32Ret);
            break;
        }else
        {
            printf("%s %d write file len = %d \n",__FUNCTION__,__LINE__,s32Ret);
        }

        fclose(l_Fp);
#endif
   
       u32Size = stFrameInfom.stVFrame.u32Stride[0]*stFrameInfom.stVFrame.u32Height*3/2; 
       l_pUserAddr =(unsigned char *)HI_MPI_SYS_Mmap(stFrameInfom.stVFrame.u32PhyAddr[0], u32Size);
       if(NULL!=l_pUserAddr)
       {
            
           l_Fp = fopen("yuv420.yuv","w+");
           if(NULL==l_Fp)
           {
               printf("%s %d file open error \n",__FUNCTION__,__LINE__);
               break;
           }
                      
           s32Ret = fwrite(l_pUserAddr,1,u32Size,l_Fp);
           if(s32Ret!=u32Size)
           {
               fclose(l_Fp);
               printf("%s %d fwrite file error %d \n",__FUNCTION__,__LINE__,s32Ret);
               break;
           }else
           {
               printf("%s %d write file len = %d \n",__FUNCTION__,__LINE__,s32Ret);
           }

           HI_MPI_SYS_Munmap(l_pUserAddr, u32Size);
           fclose(l_Fp);
       }
 
        HI_MPI_VI_ReleaseFrame(s32ViChn, &stFrameInfom);

        break;
        
        //usleep(100000);

    }

    return 0;

}

这里有几个点非常重要:

(1)如果VI缓存图像深度为0,需要根据需求设置该深度值。

(2)HI_MPI_VI_GetFrame获取到图像数据的地址是物理地址,但是我们应用程序的地址需要的是虚拟地址,所以需要将物理地址转换为虚拟地址之后才能使用,否则读取不到数据。需要使用HI_MPI_SYS_Mmap 接口映射虚拟地址和HI_MPI_SYS_Munmap解除映射。

(3)stFrameInfom.stVFrame.u32PhyAddr[0] 存放的是图像的亮度分量,stFrameInfom.stVFrame.u32PhyAddr[1]存放的是色读分量。他们两个分量的地址虽然是分开给出的,但是他们的物理地址是连续分布的,所以一次可以将他们两个分量一起进行内存映射。

(4)设备刚启动的时候,有可能VI里面还没有数据,第一次获取数据可能会返回0xa010800e 错误码,表示没有数据。

(5)刚启动获取设备的时候,可能获取到的图像是空的,也就是图像全是黑的画面,从后面几帧开始就正常了。

上面代码运行的结果如下:

BIAO_MPP_GET_VI_Frame 352 HI_MPI_VI_GetFrame(0) err=0xa010800e
u32Width        = 0x500 
u32Height       = 0x2d0 
u32Field        = 0x4 
enPixelFormat   = 0x17 
enVideoFormat   = 0x0 

j = 0 u32PhyAddr    = 0x92243c00 
j = 0 pVirAddr        = 0x0 
j = 0 u32Stride       = 0x500 
j = 0 u32HeaderPhyAddr= 0x0 
j = 0 pHeaderVirAddr  = 0x0 
j = 0 u32HeaderStride = 0x0

j = 1 u32PhyAddr    = 0x92324c00 
j = 1 pVirAddr        = 0x0 
j = 1 u32Stride       = 0x500 
j = 1 u32HeaderPhyAddr= 0x0 
j = 1 pHeaderVirAddr  = 0x0 
j = 1 u32HeaderStride = 0x0

j = 2 u32PhyAddr    = 0x0 
j = 2 pVirAddr        = 0x0 
j = 2 u32Stride       = 0x0 
j = 2 u32HeaderPhyAddr= 0x0 
j = 2 pHeaderVirAddr  = 0x0 
j = 2 u32HeaderStride = 0x0

s16OffsetBottom = 0x0 
s16OffsetLeft   = 0x0 
s16OffsetRight  = 0x0 
u64pts          = 0x1 
u32TimeRef      = 0x164 
u32PrivateData  = 0x0 
enFlashType     = 0x0

BIAO_MPP_GET_VI_Frame 448 write file len = 1382400 
program exit normally!

从上面结果可以看出:

(1)VI输入的图像分辨率是1280*720;(0x500*0x2d0)

(2)像素格式为0x17对应海思的PIXEL_FORMAT_E中的PIXEL_FORMAT_YUV_SEMIPLANAR_420

(3)图像跨距u32Stride = 0x500 ,这里要注意下,跨距有可能与图像宽度不一样,跨距表示图像宽度加上图像附加数据,在没有附件数据的时候,他与图形的宽度时一样的。

因为图像是YUV420格式的,所以Y分量的大小等于图像的大小,YU分量是图像分量的1/2。YUV420图像数据的分布如下:

按照上面介绍的参数,使用pYUV工具查看我们程序生成的文件yuv420.yuv,显示如下:

本章频测工程可以从「目录与序言」提供的地址去获取

本专栏第一篇文章「目录与序言」列出了专栏的完整目录,按目录顺序阅读,有助于你的理解。

----------------------------------------------------------------2022.08.28----------------------------------------------------------------

新的文章内容和附件工程文件

已更新在博客首页和:

|工|·-·|重|·-·|浩|:liwen01

感恩您的关注,谢谢~

  • 15
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
要将海思的YVU数据格式转换为YUV数据格式,你可以使用以下代码片段: ```cpp // 定义YVU数据格式的宽度和高度 int width = 640; // 图像宽度 int height = 480; // 图像高度 // 定义YVU数据格式Y、V、U分量的起始地址 HI_U8* yData = buffer; // Y分量起始地址 HI_U8* vData = yData + width * height; // V分量起始地址 HI_U8* uData = vData + width * height / 4; // U分量起始地址 // 创建一个与输入图像大小相同的输出图像 cv::Mat yuvImage(height, width, CV_8UC3); // 将YVU数据格式转换为YUV数据格式 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int index = y * width + x; int uvIndex = y / 2 * width / 2 + x / 2; // 获取Y、U、V分量的值 unsigned char Y = yData[index]; unsigned char U = uData[uvIndex]; unsigned char V = vData[uvIndex]; // 计算YUV分量的位置 int yuvIndex = y * width * 3 + x * 3; // 将YUV分量存储到输出图像 yuvImage.data[yuvIndex] = Y; yuvImage.data[yuvIndex + 1] = U; yuvImage.data[yuvIndex + 2] = V; } } ``` 在上述代码,我们假设YVU数据格式为YVU420P,在内存依次存储Y、V、U分量。我们首先根据图像的宽度和高度计算出Y、V、U分量的起始地址。然后,我们创建一个与输入图像大小相同的输出图像。接下来,我们使用双层循环遍历每个像素,从Y、V、U分量获取对应的值,并将它们存储到输出图像的相应位置。 请注意,上述代码仅适用于YVU420P格式的图像,如果你的图像格式不同,需要根据实际情况进行调整。同时,上述代码的图像处理是逐像素进行的,可能效率较低。如果需要更高效的处理方法,可以考虑使用OpenCV等库提供的相关函数或者使用并行计算技术进行加速。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

li_wen01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值