配置 Hi3516EV200 SDK 开发环境及串口调试

Linux 编译 SDK 自动配置脚本

OS: Ubuntu 14.04 x64

首先参考官方文档中 Hi3516EV200/Hi3516EV300/Hi3518EV300 Linux SDK 安装以及升级使用说明.pdf 找到 SDK 开发包 Hi3516EV200_SDK_V1.0.1.0.tgz

提取开发包中 Hi3516EV200_SDK_V1.0.1.0/package 目录下 osdrv.tgz

参考 osdrv.tgz 包中 osdrv/readme_cn.txt 下载编译所需的工具源码包

将 SDK 开发包、工具源码包放置在同一目录下下,并在目录中建立 Hi3516EV200_SDK_V1.0.1.0_installer.sh 脚本,写入如下内容

#!/bin/bash

# 替换 Shell

dpkg-reconfigure dash

# 修改开发包权限及属主

chmod 755 -R ../Hi3516EV200_SDK_V1.0.1.0_installer
chown root:root -R ../Hi3516EV200_SDK_V1.0.1.0_installer

# 安装依赖库

apt install -y libncurses5-dev make libc6 lib32z1 lib32stdc++6 zlib1g-dev \
    ncurses-term libncursesw5-dev g++ u-boot-tools texinfo texlive gawk curl \
    upx pngquant liblzo2-dev uuid-dev pkg-config \
    gperf bison

touch /etc/ld.so.preload
echo "" > /etc/ld.so.preload

# 解压交叉编译工具包并安装

tar zxvf arm-himix100-linux.tgz
chmod +x arm-himix100-linux/arm-himix100-linux.install
cd arm-himix100-linux && ./arm-himix100-linux.install && cd ..

# 配置环境变量

echo "export PATH=/opt/hisi-linux/x86-arm/arm-himix100-linux/bin:\$PATH" >> $HOME/.profile
echo "export PKG_CONFIG_PATH=\$PKG_CONFIG_PATH:/usr/lib/x86_64-linuxgnu/pkgconfig" >> $HOME/.profile

# 解压 SDK 开发包并展开

tar zxvf Hi3516EV200_SDK_V1.0.1.0.tgz
cd Hi3516EV200_SDK_V1.0.1.0 && ./sdk.unpack

# 将工具源码包移动至指定目录(参考 osdrv 下的 readme)

cp ../linux-4.9.37.tar.gz osdrv/opensource/kernel
cp ../yaffs2utils-0.2.9.tar.gz osdrv/tools/pc/mkyaffs2image
cp ../util-linux-2.31.tar.gz osdrv/tools/pc/cramfs_tool
cp ../gdb-7.9.1.tar.gz osdrv/tools/board/gdb
cp ../ncurses-6.0.tar.gz osdrv/tools/board/gdb

echo "[INFO] Use 'source ~/.bashrc' to confirm PATH changes."

完成以上步骤后目录结构应如下所示

Hi3516EV200_SDK_V1.0.1.0_installer
 ├─ arm-himix100-linux.tgz
 ├─ gdb-7.9.1.tar.gz
 ├─ Hi3516EV200_SDK_V1.0.1.0.tgz            //开发包
 ├─ Hi3516EV200_SDK_V1.0.1.0_installer.sh   //配置脚本
 ├─ linux-4.9.37.tar.gz
 ├─ ncurses-6.0.tar.gz
 ├─ util-linux-2.31.tar.gz
 └─ yaffs2utils-0.2.9.tar.gz

将以上文件夹拷贝至 Ubuntu 中,并赋予脚本执行权限

chmod +x Hi3516EV200_SDK_V1.0.1.0_installer.sh

执脚本执行时会弹框询问是否替换执行脚本的 shell,选择 No 即可
脚本执行成功后需要手动执行 source ~/.bashrc 命令应用环境变量的更改

进入开发包中 osdrv 目录下,开始编译

cd osdrv
make all

编译使用 gcc-5 g++-5

需要在顶层 Makefile 中添加 export LANG=C

Makefile 中使用了大量 >/dev/null 2>&1 重定向标准及错误输出,导致编译或配置过程出现错误时无法定位错误位置或解决方案,如果出现编译失败的情况请找到距离报错处最近的一条编译命令并手动编译,定位错误原因

根据 U-Boot 启动输出选择相应 SPI Nor Flash 块大小的 rootfs

首次启动会输出一个随机 MAC 地址,记录下来作为固定 MAC

串口调试

在开发板 Hi3516 ERNCV200 芯片临近板边缘的一侧 预留了两个过孔,其中 靠近晶振一侧 的过孔为 TX,接 USB-TTL 板 RX;另一侧为 RX,接 USB-TTL 的 TX,地线可以直接接在 开发板四脚的过孔

安装 CP210x 驱动后即可通过串口进入开发板终端

Hi3516EV200 烧录系统并配置板端环境

按照以上步骤编译好 SDK 后目录结构应如下所示

Hi3516EV200_SDK_V1.0.1.0
 ├─ drv
 ├─ mpp
 ├─ osal
 ├─ osdrv
 │   ├─ Makefile
 │   ├─ opensource
 │   ├─ pub
 │   ├─ readme_cn.txt
 │   ├─ readme_en.txt
 │   ├─ rootfs_scripts
 │   └─ tools
 ├─ package
 ├─ scripts
 ├─ sdk.cleanup
 └─ sdk.unpack

其中 mpp 目录用于存放初始化板端环境的脚本以及运行库

osdrv 目录用于存放编译好的分区镜像及相关工具,结构如下

osdrv
 ├─ Makefile
 ├─ opensource
 ├─ pub
 │   ├─ bin
 │   ├─ hi3516ev200_spi_image_uclibc
 │   │   ├─ rootfs_hi3516ev200_128k.jffs2
 │   │   ├─ rootfs_hi3516ev200_256k.jffs2
 │   │   ├─ rootfs_hi3516ev200_2k_128k_32M.ubifs
 │   │   ├─ rootfs_hi3516ev200_2k_24bit.yaffs2
 │   │   ├─ rootfs_hi3516ev200_2k_4bit.yaffs2
 │   │   ├─ rootfs_hi3516ev200_4k_24bit.yaffs2
 │   │   ├─ rootfs_hi3516ev200_4k_256k_50M.ubifs
 │   │   ├─ rootfs_hi3516ev200_4k_4bit.yaffs2
 │   │   ├─ rootfs_hi3516ev200_64k.jffs2
 │   │   ├─ u-boot-hi3516ev200.bin
 │   │   └─ uImage_hi3516ev200
 │   └─ rootfs_uclibc.tgz
 ├─ readme_cn.txt
 ├─ readme_en.txt
 ├─ rootfs_scripts
 └─ tools

osdrv/pub 目录下以开发板型号命名的文件夹即为相应的系统分区镜像

烧写系统

打开 HiTool 工具,通过串口连接开发板,将 传输方式 更改为串口,使用按分区烧写,按照如下分区配置烧写镜像

串口烧写需要进入 boot,所以开始烧写前需要将开发板下电,点击烧写按钮开始烧写后在 15 秒内上电

分区名 偏移 长度 文件名 器件类型 文件系统
fastboot 0 (0M) 80000 (1M) u-boot-hi3516ev200.bin spi nor none
kernel 100000 (1M) 400000 (4M) uImage_hi3516ev200 spi nor none
rootfs 500000 (5M) b00000 (11M) rootfs_hi3516ev200_64k.jffs2 spi nor none

配置启动参数

在 HiTool 中打开终端工具,使用串口连接开发板

分区镜像烧写完成后会自动执行 reset 命令从 u-boot 重启系统,但由于没有配置启动参数导致系统只能进入 u-boot,无法进入系统

u-boot 模式下终端格式为 hisilicon #,进入系统后格式为 路径 #

在 u-boot 终端下输入如下命令,参数根据烧写的镜像以及实际需求进行配置

开发板的内存分为两种,一种是 OS Memory,一种是 MMZ Memory;开发板内存的管理机制为 从总内存中减去 OS Memory,剩下的分配给 MMZ,而开发板推送视频流等服务需要占用一定 MMZ Memory。
使用 setenv 命令设置系统变量后需要在重启前使用 saveenv 保存,否则所有更改在重启后都会丢失
启动参数中的文件系统类型、分区名以及分区长度需要与 HiTool 中烧写时设置一致,否则在启动时会找不到分区
Linux 系统中重启的命令为 reboot,但在 u-boot 中需要使用 reset 重启

setenv bootargs 'mem=32M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 rw mtdparts=hi_sfc:1M(boot),4M(kernel),11M(rootfs)'
setenv bootcmd 'sf probe 0;sf read 0x42000000 0x100000 0x400000;bootm 0x42000000'
saveenv
reset

配置 NFS 服务端

由于开发板内存不足以支持开发工作,并且无法支持板端编译,需要在服务器上开启 NFS 服务,将服务器上的指定文件夹作为网络硬盘挂载在开发板上

以下步骤需要在服务器上执行

安装 NFS Server

apt install nfs-kernel-server

添加 NFS 配置到 /etc/exports 并重启 NFS 服务

/nfs/server/path 是服务器上的 NFS 路径,后面指定允许访问的地址,可以使用 * 指定地址段,括号内是文件夹操作权限

echo "/nfs/server/path 192.168.*(rw,sync,no_root_squash,no_subtree_check)" >> /etc/exports
/etc/init.d/nfs-kernel-server restart

配置完成后需要将编译好的 mpp 目录复制到 NFS 目录中,用于板端加载 sensor 驱动

开发包中 PQTools 目录下 Hi3516EV200_PQ_V1.0.1.0.tgz 解压后移动到 NFS 目录中,用于启动板端业务

配置网络

由于新版 SDK 中未包含 DHCP 服务,并且启动时会使用随机 MAC 地址,可能对路由表造成影响,这里在进入系统后手动修改网络配置

/etc/init.d/rcS 会在每次启动时被调用
以下设置中 eth0 为网卡名
ifconfig eth0 downifconfig eth0 up 用于重启网卡
ifconfig eth0 hw ether 设置 MAC 地址,建议查找开发板启动日志,使用任意一个随机生成的地址,避免冲突
ifconfig eth0 192.168.199.145 netmask 255.255.255.0 设置了开发板的 IP 地址和子网掩码,建议在局域网内的电脑上执行 arp -a 命令或查看路由器,设置一个未被分配但在同一网段(即地址的前三组与上位机相同)的 IP 地址;无特殊情况子网掩码设置 255.255.255.0 即可
route add default gw 命令用于设置默认路由,一般为路由器管理地址,特殊情况请查看路由器或交换机配置

echo "" >> /etc/init.d/rcS
echo "ifconfig eth0 down" >> /etc/init.d/rcS
echo "ifconfig eth0 up" >> /etc/init.d/rcS
echo "ifconfig eth0 hw ether E0:62:90:ED:AB:29" >> /etc/init.d/rcS
echo "ifconfig eth0 192.168.199.145 netmask 255.255.255.0" >> /etc/init.d/rcS
echo "route add default gw 192.168.199.1" >> /etc/init.d/rcS

重启开发板即可使用设置好的配置访问网络

挂载 NFS 并加载驱动

进入系统后输入以下命令挂载 NFS 并设置开机自动加载 sensor 驱动并开启 PQTool 、视频流及 telnet 服务

/nfs/server/path 是服务器上的 NFS 路径,后面是挂载在开发板上的路径,通常位于 /mnt 目录下
mpp/ko/load3516ev200 脚本中 -osmem 参数中配置的内存需要与系统启动参数配置一致,原理相同
Hi3516EV200_PQ_V1.0.1.0/HiIspTool.sh 脚本需要根据输入的 sensor 型号来选择配置文件,但在实际测试中发现 imx307 型号需要调用 imx307_2l 配置文件才能正常调起 vi,使用时需要注意

echo "mount -t nfs -o nolock -o tcp -o rsize=32768,wsize=32768 192.168.199.244:/nfs/server/path /mnt" \
>> /etc/init.d/rcS
echo "cd /mnt/mpp/ko && ./load3516ev200 -i -sensor0 imx307" >> /etc/init.d/rcS
echo "cd /mnt/Hi3516EV200_PQ_V1.0.1.0 && ./HiIspTool.sh -a imx307_2l" >> /etc/init.d/rcS
echo "telnetd&" >> /etc/init.d/rcS

配置板端环境变量

echo "" >> /root/.profile
echo "export LD_LIBRARY_PATH=/mnt/mpp/lib:\$LD_LIBRARY_PATH" >> /root/.profile
echo "export LD_LIBRARY_PATH=/mnt/Hi3516EV200_PQ_V1.0.1.0/libs:\$LD_LIBRARY_PATH" >> /root/.profile

重启后即可使用 PQTool 以及 ITTP_Stream 进行调试

运行原理

系统结构

MPP 内部处理流程

查看设备状态

# cat /proc/umap/vpss 

[VPSS] Version: [Hi3516EV200_MPP_V1.0.1.0 B050 Release], Build Time[May  9 2019, 22:51:50]


-------------------------------MODULE PARAM----------------------------------------------------------
u32VpssVbSource    bOneBufferforLowDelay    u32VpssSplitNodeNum    bHdrSupport    bNrQuickStart
              0                        0                      3              0                0

-------------------------------VPSS GRP ATTR---------------------------------------------------------
   GrpID    MaxW    MaxH      PixFmt    DRange  SrcFRate  DstFRate bUserCtrl    NrEn    NrType    RefCmp  MotionMode
       0    1920    1080   YVU-SP420      SDR8        -1        -1         Y       Y     VIDEO         Y      Normal

-------------------------------VPSS CHN ATTR---------------------------------------------------------
   GrpID  PhyChnID  Enable    Mode   Width  Height  SrcFRate  DstFRate   Depth   Align  MirrorEn  FlipEn  bBufferShare  ProcMode
       0         0       Y    USER    1920    1080        -1        -1       0       0         N       N             N     VIDEO
       0         1       Y    USER     640     360        -1        -1       1       0         N       N             N     VIDEO

-------------------------------VPSS EXT-CHN ATTR--------------------------------------------------
   GrpID  ExtChnID  Enable  SrcChn   Width  Height  SrcFRate  DstFRate   Depth   Align  ProcMode

-------------------------------VPSS GRP CROP INFO-----------------------------------------------------
   GrpID  CropEn  CoorType   CoorX   CoorY   Width  Height    OriW    OriH   TrimX   TrimY TrimWid TrimHgt
       0       N       RAT       0       0       0       0

-------------------------------VPSS CHN CROP INFO-----------------------------------------------------
   GrpID   ChnID  CropEn  CoorType   CoorX   CoorY   Width  Height   TrimX   TrimY TrimWid TrimHgt
       0       0       N       RAT       0       0       0       0
       0       1       N       RAT       0       0       0       0

-------------------------------VPSS GRP PIC QUEUE----------------------------------------------------
   GrpID  FreeLen0  BusyLen0     Delay    Backup
       0         9         0         0         0

-------------------------------VPSS GRP PIC INFO---------------------------------------------------
   GrpID   Width  Height      Pixfmt    Videofmt      DRange    Compress
       0    1920    1080   YVU-SP420      LINEAR        SDR8           N

-------------------------------VPSS GRP WORK STATUS---------------------------------------------------
   GrpID   RecvPic0   ViLost0   VdecLost0  NewDo  OldDo  NewUnDo  OldUnDo  StartFl  bStart  CostTm  MaxCostTm
       0          0         0           0      0      0        0        0        0       Y       0          0

-------------------------------VPSS CHN OUTPUT RESOLUTION--------------------------------------------
   GrpID   ChnID  Enable   Width  Height      Pixfmt    Videofmt      DRange    Compress  SendOk   FrameRate
       0       0       Y    1920    1080   YVU-SP420      LINEAR        SDR8           N 2075695          30
       0       1       Y     640     360   YVU-SP420      LINEAR        SDR8           N       0          30

-------------------------------VPSS 3DNR X PARAM------------------------------------------------------
-------------------------------VPSS GPR0 3DNR PARAM-------------------------------------
   GrpID    Intf   Version   OptMode       ISO     Ref
       0    NR_X     VER_3    MANUAL       110       1
   NRyEn_0                   NRyEn_1                   NRyEn_2                   NRyEn_3
         1                         1                         1                         1
    SFS2_0                    SFS2_1                    SFS2_2                    SFS2_3
        20                        20                        20                        20
    SFS4_0                    SFS4_1                    SFS4_2                    SFS4_3
        30                        30                        30                        30
     TFS_0              TFS0_1        TFS1_1             TFS_2
        12                  12            12                12
                       MATH0_1       MATH1_1            MATH_2                    MATH_3
                           100           100               100                       100

-------------------------------VPSS CHN LBA ATTR---------------------------------------------------------
   GrpID   ChnID  Aspect  videoX  videoY  videoW  videoH     BgColor

-------------------------------VPSS CHN ROTATE ATTR---------------------------------------------------
   GrpID   ChnID  Rotate

-------------------------------VPSS CHN LDCV3 ATTR----------------------------------------------------
   GrpID   ChnID  Enable  viewType   XOffset   YOffset       DistortionRatio       MinRatio

-------------------------------VPSS CHN LOWDELAY ATTR-------------------------------------------------
   GrpID   ChnID  Enable   LineCnt   OneBufEnable   OneBufAddr
       0       0       Y         1              N   0x41e26000

-------------------------------VPSS CHN BUF WRAP ATTR------------------------------------------------
   GrpID   ChnID  Enable   BufLine   WrapBufSize
       0       0       Y       192        552960

-------------------------------FRAME INTERRUPT ATTR---------------------------------------------------
   GrpID        IntType      EarlyLine
       0      EARLY_END            128

-------------------------------VPSS GRP PIC PTS---------------------------------------------------------
   GrpID         FirstPicPTS           CurPicPTS
       0             7597459         69258932864

-------------------------------DRV WORK STATUS--------------------------------------------------------
       StartSuc0       StartSuc1         LinkInt   StartErr0  NodeIdErr0   StartErr1  NodeIdErr1      BusErr
               0               0               0           0           0           0           0           0

-------------------------------DRV NODE QUEUE---------------------------------------------------------
   FreeNum   WaitNum  Busy00  Busy01    Sel0  Busy10  Busy11    Sel1    Proc
         9         0       0       0       0       0       0       0       0

-------------------------------INT WORK STATUS--------------------------------------------------------
     CntPerSec  MaxCntPerSec        CostTm    MostCostTm  CostTmPerSec MCostTmPerSec
             0             0             0             0             0             0

从 VPSS 中获取图片

获取/设置 VPSS 通道属性

HI_S32 HI_MPI_VPSS_GetChnAttr(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, VPSS_CHN_ATTR_S *pstChnAttr);
HI_S32 HI_MPI_VPSS_SetChnAttr(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, const VPSS_CHN_ATTR_S *pstChnAttr);

获取/设置 VPSS 通道扩展属性

HI_S32 HI_MPI_VPSS_GetExtChnAttr(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, VPSS_EXT_CHN_ATTR_S *pstExtChnAttr);
HI_S32 HI_MPI_VPSS_SetExtChnAttr(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, const VPSS_EXT_CHN_ATTR_S *pstExtChnAttr);

重载 VPSS

HI_S32 VPSS_Restore(VPSS_GRP VpssGrp, VPSS_CHN VpssChn);

从 VPSS 通道获取图片

HI_S32 HI_MPI_VPSS_GetChnFrame(VPSS_GRP VpssGrp, VPSS_CHN VpssChn,
    VIDEO_FRAME_INFO_S *pstVideoFrame, HI_S32 s32MilliSec);

读图片

申请内存空间

SAMPLE_COMM_IVE_CreateImage(IVE_IMAGE_S* pstImg, IVE_IMAGE_TYPE_E enType, HI_U32 u32Width, HI_U32 u32Height);

读取图片

HI_S32 SAMPLE_COMM_IVE_ReadFile(IVE_IMAGE_S* pstImg, FILE* pFp);

保存图片

申请内存空间

SAMPLE_COMM_IVE_CreateImage(IVE_IMAGE_S* pstImg, IVE_IMAGE_TYPE_E enType, HI_U32 u32Width, HI_U32 u32Height);

保存图片

HI_S32 SAMPLE_COMM_IVE_WriteFile(IVE_IMAGE_S* pstImg, FILE* pFp);

判断函数执行结果

HI_S32 s32Ret;
s32Ret = function();
SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),function failed!\n",s32Ret);

边缘检测

输入图像

输出边缘

判断任务是否完成

HI_S32 s32Ret = HI_SUCCESS;
HI_BOOL bBlock = HI_TRUE;
HI_BOOL bFinish = HI_FALSE;
IVE_HANDLE hIveHandle;

s32Ret = HI_MPI_IVE_LKOpticalFlowPyr(&hIveHandle,
      pstStLk->astPrevPyr, pstStLk->astNextPyr,
      &pstStLk->stPrevPts, &pstStLk->stNextPts,
      &pstStLk->stStatus, &pstStLk->stErr,
      &pstStLk->stLkPyrCtrl,HI_TRUE);
SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_LKOpticalFlowPyr failed!\n",s32Ret);

s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
while (HI_ERR_IVE_QUERY_TIMEOUT == s32Ret)
{
   usleep(100);
   s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
}
SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_Query failed!\n",s32Ret);

调试光流示例

代码调试

光流部分的代码位于板端 /mnt/mpp/sample/ive/sample/sample_ive_st_and_lc.c 文件中

编译时进入 /mnt/mmpp/sample/ive 目录下编译整个 ive 的示例,运行相应模块即可

./sample_ive_main 5

执行流程

static HI_S32 SAMPLE_IVE_St_Lk_Init(SAMPLE_IVE_ST_LK_S* pstStLk, HI_CHAR* pchSrcFileName,
        HI_U32 u32Width, HI_U32 u32Height, HI_U32 u32PyrWidth, HI_U32 u32PyrHeight, HI_U8 u8MaxLevel)
// 初始化运行光流需要的结构体

在 YUV 中绘制点、线

HI_VOID swap(int *swapX, int *swapY)
{
    int swapTemp = *swapX;
    *swapX = *swapY;
    *swapY = swapTemp;
}

HI_VOID drawPoint(unsigned char *picData,
                int pointX, int pointY,
                int picWidth, int picHeight,
                int pointColorY)
{
    int pointOffset = (pointY - 1) * picWidth + (pointX - 1);
    int i, j;
    for (i = ( pointY < 2 ? 0 : -1 ); i < ( pointY > (picHeight - 1) ? 1 : 2 ); i++)
    {
        for (j = ( pointX < 2 ? 0 : -1 ); j < ( pointX > (picWidth - 1) ? 1 : 2 ); j++)
        {
            picData[pointOffset + picWidth * i + j] = pointColorY;
        }
    }
}

HI_VOID drawLine(unsigned char *picData,
                int pointStartX, int pointStartY,
                int pointEndX, int pointEndY,
                int picWidth, int picHeight,
                int pointColorY, int pointColorU, int pointColorV)
{
    if (pointStartX < 0 || pointStartX >= picWidth ||
        pointStartY < 0 || pointStartY >= picHeight ||
        pointEndX < 0 || pointEndX >= picWidth ||
        pointEndY < 0 || pointEndY >= picHeight)
        {
            return;
        }

    int dx, dy, deltax, deltay, error, pointStartYTemp, x;
    dx = abs(pointEndX - pointStartX);
    dy = abs(pointEndY - pointStartY);

    if (dy>dx?1:0)
    {
        swap(&pointStartX, &pointStartY);
        swap(&pointEndX, &pointEndY);
    }
    if (pointStartX > pointEndX)
    {
        swap(&pointStartX, &pointEndX);
        swap(&pointStartY, &pointEndY);
    }

    deltax = pointEndX - pointStartX;
    deltay = abs(pointEndY - pointStartY);

    error = deltax / 2;
    pointStartYTemp = pointStartY;

    for (x = pointStartX; x < pointEndX; x++)
    {
        if (dy>dx?1:0)
        {
            picData[x*picWidth + pointStartYTemp] = pointColorY;
            picData[picWidth*picHeight + x/2*picWidth/2 + pointStartYTemp/2] = pointColorU;
            picData[picWidth*picHeight + picWidth*picHeight/4 + x/2*picWidth/2 + pointStartYTemp/2] = pointColorV;
        } else {
            picData[pointStartYTemp*picWidth + x] = pointColorY;
            picData[picWidth*picHeight + pointStartYTemp/2*picWidth/2 + x/2] = pointColorU;
            picData[picWidth*picHeight + picWidth*picHeight/4 + pointStartYTemp/2*picWidth/2 + x/2] = pointColorV;
        }

        error -= deltay;
        if (error < 0) 
        {
            pointStartYTemp += ( pointStartY < pointEndY ? 1 : -1 );
            error += deltax;
        }
    }
}
static HI_S32 SAMPLE_IVE_St_LkProc(SAMPLE_IVE_ST_LK_S* pstStLk)
{
    HI_U32 u32FrameNum = 4989;
    HI_U32 i, k;
    HI_U16 u16RectNum;
    HI_S32 s32Ret = HI_SUCCESS;
    HI_BOOL bBlock = HI_TRUE;
    HI_BOOL bFinish = HI_FALSE;
    IVE_HANDLE hIveHandle;
    IVE_DMA_CTRL_S stDmaCtrl;
    IVE_ST_CORNER_INFO_S* pstCornerInfo = SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(IVE_ST_CORNER_INFO_S, pstStLk->stCorner.u64VirAddr);
    IVE_POINT_S25Q7_S *psts25q7NextPts = SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(IVE_POINT_S25Q7_S, pstStLk->stNextPts.u64VirAddr);
    IVE_POINT_S25Q7_S lkPrevPtsArray[512] = {0};
    IVE_POINT_S25Q7_S lkNextPtsArray[512] = {0};
    HI_U8 lkStatusArray[512] = {0};
    HI_U9Q7 lkErrArray[512] = {0};

    memset(&stDmaCtrl,0,sizeof(stDmaCtrl));
    stDmaCtrl.enMode = IVE_DMA_MODE_DIRECT_COPY;

    char *LKOutImgPath = "./data/output/stlk/stlk_out_image.yuv";
    FILE *LKOutImg = fopen(LKOutImgPath, "w");
    LKOutImg = fopen(LKOutImgPath, "ab+");

    for (i = 0; i < u32FrameNum; i++)
    {
        SAMPLE_PRT("Proc frame %d\n", i);
        s32Ret = SAMPLE_COMM_IVE_ReadFile(&pstStLk->stSrcYuv, pstStLk->pFpSrc);
        SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),Read src file failed!\n",s32Ret);

        // 初始化: 坐标 跟踪状态 误差
        memset(lkPrevPtsArray, 0, sizeof(IVE_POINT_S25Q7_S)*512);
        memset(lkNextPtsArray, 0, sizeof(IVE_POINT_S25Q7_S)*512);
        memset(lkStatusArray, 0, sizeof(HI_U8)*512);
        memset(lkErrArray, 0, sizeof(HI_U9Q7)*512);

        int picWidth = pstStLk->stSrcYuv.u32Width;
        int picHeight = pstStLk->stSrcYuv.u32Height;

        s32Ret = SAMPLE_IVE_St_Lk_DMA(&hIveHandle,&pstStLk->stSrcYuv,&pstStLk->astNextPyr[0],&stDmaCtrl,HI_FALSE);
        SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),SAMPLE_IVE_St_Lk_DMA failed!\n",s32Ret);

        for (k = 1; k <= pstStLk->stLkPyrCtrl.u8MaxLevel; k++)
        {
            s32Ret = SAMPLE_IVE_St_Lk_PyrDown(pstStLk, &pstStLk->astNextPyr[k - 1], &pstStLk->astNextPyr[k]);
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),SAMPLE_IVE_St_Lk_PyrDown level %d failed!\n",s32Ret,k);
        }
        
        // 第 0 帧获取角点,每 3 帧重新获取角点
        if (i==0 || i%3==0)
        // if (1)
        {
            s32Ret = HI_MPI_IVE_STCandiCorner(&hIveHandle, &pstStLk->astNextPyr[0], &pstStLk->stStDst,
                                              &pstStLk->stStCandiCornerCtrl, HI_TRUE);
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_STCandiCorner failed!\n",s32Ret);

            s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
            while (HI_ERR_IVE_QUERY_TIMEOUT == s32Ret)
            {
                usleep(100);
                s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
            }
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_Query failed!\n",s32Ret);

            s32Ret = HI_MPI_IVE_STCorner(&pstStLk->stStDst, &pstStLk->stCorner, &pstStLk->stStCornerCtrl);
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_STCorner failed!\n",s32Ret);

            pstStLk->stLkPyrCtrl.u16PtsNum = pstCornerInfo->u16CornerNum;
            for (k = 0; k < pstStLk->stLkPyrCtrl.u16PtsNum; k++)
            {
                psts25q7NextPts[k].s25q7X = (HI_S32)(pstCornerInfo->astCorner[k].u16X << 7);
                psts25q7NextPts[k].s25q7Y = (HI_S32)(pstCornerInfo->astCorner[k].u16Y << 7);
                printf("DEBUG =>=>=>=>=>=>=>=>=>=> frame: [%d]; point: [%d]; psts25q7NextPts[%d].s25q7X: [%d]; psts25q7NextPts[%d].s25q7Y: [%d]\n",
                        i, k, k, psts25q7NextPts[k].s25q7X >> 7, k, psts25q7NextPts[k].s25q7Y >> 7);
            }
        }
        else
        {
            s32Ret = HI_MPI_IVE_LKOpticalFlowPyr(&hIveHandle,
                    pstStLk->astPrevPyr, pstStLk->astNextPyr,
                    &pstStLk->stPrevPts, &pstStLk->stNextPts,
                    &pstStLk->stStatus, &pstStLk->stErr,
                    &pstStLk->stLkPyrCtrl,HI_TRUE);
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_LKOpticalFlowPyr failed!\n",s32Ret);

            s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
            while (HI_ERR_IVE_QUERY_TIMEOUT == s32Ret)
            {
                usleep(100);
                s32Ret = HI_MPI_IVE_Query(hIveHandle, &bFinish, bBlock);
            }
            SAMPLE_CHECK_EXPR_RET(HI_SUCCESS != s32Ret,s32Ret,"Error(%#x),HI_MPI_IVE_Query failed!\n",s32Ret);

            unsigned char *pic = (unsigned char *)malloc(picWidth * picHeight * 1.5);
            memcpy(pic, SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID, pstStLk->stSrcYuv.au64VirAddr[0]), picWidth * picHeight * 1.5);

            int prevPointsX, prevPointsY, nextPointsX, nextPointsY;
            int pointNum;
            int pointStatus, pointErr;

            memcpy(lkPrevPtsArray, SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID, pstStLk->stPrevPts.u64VirAddr), pstStLk->stPrevPts.u32Size);
            memcpy(lkNextPtsArray, SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID, pstStLk->stNextPts.u64VirAddr), pstStLk->stNextPts.u32Size);
            memcpy(lkStatusArray, SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID, pstStLk->stStatus.u64VirAddr), pstStLk->stStatus.u32Size);
            memcpy(lkErrArray, SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID, pstStLk->stErr.u64VirAddr), pstStLk->stErr.u32Size);
            for (pointNum = 0; pointNum < MAX_POINT_NUM; pointNum++)
            {
                pointStatus = lkStatusArray[pointNum];
                pointErr = lkErrArray[pointNum] >> 7;
                prevPointsX = lkPrevPtsArray[pointNum].s25q7X >> 7;
                prevPointsY = lkPrevPtsArray[pointNum].s25q7Y >> 7;
                nextPointsX = lkNextPtsArray[pointNum].s25q7X >> 7;
                nextPointsY = lkNextPtsArray[pointNum].s25q7Y >> 7;

                if (pointStatus > 1 || prevPointsX < 1 || prevPointsY < 1 || nextPointsX < 1 || nextPointsY < 1)
                    break;

                printf("DEBUG =>=>=>=>=>=>=>=>=>=> frame: [%d]; point: [%d]; stStatus: [%d]; stErr: [%d]; stPrevPts: [%d], [%d]; stNextPts: [%d], [%d]\n",
                        i, pointNum, pointStatus, pointErr,
                        prevPointsX, prevPointsY, nextPointsX, nextPointsY);

                // if (pointStatus == 1 && pointErr != 0)
                if(pointStatus == 1)
                {
                    // drawPoint(pic, nextPointsX, nextPointsY, picWidth, picHeight, pointErr==0?0:100);
                    drawPoint(pic, nextPointsX, nextPointsY, picWidth, picHeight, 0);
                    // drawLine(pic, prevPointsX, prevPointsY, nextPointsX, nextPointsY, picWidth, picHeight, 0, 0, 0);
                    drawLine(pic, nextPointsX, nextPointsY, nextPointsX + 2, nextPointsY + 2, picWidth, picHeight, 0, 0, 0);
                }
            }            

            fwrite(pic, 1, picWidth * picHeight * 1.5, LKOutImg);
            free(pic);

            u16RectNum = 0;
            for (k = 0; k < pstStLk->stLkPyrCtrl.u16PtsNum; k++)
            {
                if(! (SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_U8,pstStLk->stStatus.u64VirAddr))[k])
                {
                    continue;
                }
                psts25q7NextPts[u16RectNum].s25q7X = psts25q7NextPts[k].s25q7X;
                psts25q7NextPts[u16RectNum].s25q7Y = psts25q7NextPts[k].s25q7Y;
                u16RectNum++;
            }

            pstStLk->stLkPyrCtrl.u16PtsNum = u16RectNum;
        }

        memcpy(SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID,pstStLk->stPrevPts.u64VirAddr),
                SAMPLE_COMM_IVE_CONVERT_64BIT_ADDR(HI_VOID,pstStLk->stNextPts.u64VirAddr),
                sizeof(IVE_POINT_S25Q7_S) * pstStLk->stLkPyrCtrl.u16PtsNum);

        SAMPLE_IVE_St_Lk_CopyPyr(pstStLk->astNextPyr, pstStLk->astPrevPyr, pstStLk->stLkPyrCtrl.u8MaxLevel);
    }
    return s32Ret;
}

测试结果

图片中黑色角点:HI_MPI_IVE_LKOpticalFlowPyr 输出 pstStatus1(跟踪成功)并且输出 pstErr0(相似度误差为 0

灰色角点:pstStatus1 并且输出 pstErr 大于 0

绿色直线:角点从上一帧图片跟踪到当前帧的路径

第 1 帧

第 76 帧

补充(光流部分)

IVE 中的光流主要分为三部分

  1. HI_MPI_IVE_STCandiCorner 检测图片中所有角点,此时将所有角点绘制在 1 张图片上,数量不确定
  2. HI_MPI_IVE_STCorner 将上一步图像中所有角点读出,按照规则进行过滤(两角点间的距离),此时角点数量 <= 500
  3. HI_MPI_IVE_LKOpticalFlowPyr 将筛选后的角点作为光流中 下一帧图像角点 的初值,开始跟踪

在该流程中,只使用第 1 帧(或指定帧)图像检测角点,然后将过滤后的角点应用于第 2 帧的光流中。

  • 在实际测试中发现获取角点会“消耗”掉 1 帧图像,原因可能是此时的角点并非完全准确(可能包含被判断为跟踪失败的无效角点)。
    • 测试[1]方法:将流程修改为在每 1 帧图像上都检测角点再跑光流
    • 测试[1]结果:每一帧的角点都与上一帧相差较大,并且多为无意义的角点
    • 测试[1]序列:stlk_out_imag_all_every.yuv
    • 测试[2]方法:将流程修改为在第 1 帧和每 3 帧图像上获取角点再跑光流,其他帧只运行光流
    • 测试[2]结果:相较测试[1]情况有所好转,但无意义角点依然居多,每 3 帧的角点相差仍然很大
    • 测试[2]序列:stlk_out_image_all_3f.yuv
  • 进行对照测试发现检测的角点比较稳定,但抽帧后帧率需要调整
    • 测试[3]方法:流程修改为将第 1 帧和每 3 帧用于获取角点,其余帧用于运行光流
    • 测试[3]结果:大多数角点跟踪正常,极少部分出现不稳定的情况
    • 测试[3]序列:stlk_out_image_3f.yuv
    • 调整帧率:例如原视频

从 VPSS 取视频帧

static HI_S32 VPSS_Restore(VPSS_GRP VpssGrp, VPSS_CHN VpssChn)
{
    HI_S32 s32Ret = HI_FAILURE;
    VPSS_CHN_ATTR_S stChnAttr;
    VPSS_EXT_CHN_ATTR_S stExtChnAttr;

    if (VB_INVALID_POOLID != stFrame.u32PoolId)
    {
        s32Ret = HI_MPI_VPSS_ReleaseChnFrame(VpssGrp, VpssChn, &stFrame);

        if (HI_SUCCESS != s32Ret)
        {
            printf("Release Chn Frame error\n");
        }

        stFrame.u32PoolId = VB_INVALID_POOLID;
    }

    if (-1 != hHandle)
    {
        HI_MPI_VGS_CancelJob(hHandle);
        hHandle = -1;
    }

    if (HI_NULL != stMem.pVirAddr)
    {
        HI_MPI_SYS_Munmap((HI_VOID *)stMem.pVirAddr, u32BlkSize);
        stMem.u64PhyAddr = HI_NULL;
    }

    if (VB_INVALID_POOLID != stMem.hPool)
    {
        HI_MPI_VB_ReleaseBlock(stMem.hBlock);
        stMem.hPool = VB_INVALID_POOLID;
    }

    if (VB_INVALID_POOLID != hPool)
    {
        HI_MPI_VB_DestroyPool(hPool);
        hPool = VB_INVALID_POOLID;
    }

    if (HI_NULL != pUserPageAddr[0])
    {
        HI_MPI_SYS_Munmap(pUserPageAddr[0], u32Size);
        pUserPageAddr[0] = HI_NULL;
    }

    if (u32VpssDepthFlag)
    {
        if (VpssChn >= VPSS_CHN3)
        {
            s32Ret = HI_MPI_VPSS_GetExtChnAttr(VpssGrp, VpssChn, &stExtChnAttr);
        }
        else
        {
            s32Ret = HI_MPI_VPSS_GetChnAttr(VpssGrp, VpssChn, &stChnAttr);
        }

        if (s32Ret != HI_SUCCESS)
        {
            printf("get chn attr error\n");
        }

        if (VpssChn >= VPSS_CHN3)
        {
            stExtChnAttr.u32Depth = u32OrigDepth;
            s32Ret = HI_MPI_VPSS_SetExtChnAttr(VpssGrp, VpssChn, &stExtChnAttr);
        }
        else
        {
            stChnAttr.u32Depth = u32OrigDepth;
            s32Ret = HI_MPI_VPSS_SetChnAttr(VpssGrp, VpssChn, &stChnAttr);
        }

        if (s32Ret != HI_SUCCESS)
        {
            printf("set depth error\n");
        }

        u32VpssDepthFlag = 0;
    }
    return HI_SUCCESS;
}

HI_VOID STREAM_TEST()
{
    HI_U32 u32Depth = 2;
    HI_S32 s32MilliSec = -1;
    HI_S32 s32Times = 10;
    HI_S32 s32Ret;
    VPSS_CHN_ATTR_S stChnAttr;
    VPSS_EXT_CHN_ATTR_S stExtChnAttr;
    HI_U32 u32Cnt = 0;
    VPSS_GRP Grp = 0;
    VPSS_CHN Chn = 1;

    SAMPLE_COMM_IVE_CreateImage(&frameImg, IVE_IMAGE_TYPE_YUV420SP, u32FrameWidth, u32FrameHeight);
    fopen("./data/output/stream/stream_output.yuv", "wb");
    outFrameFile = fopen("./data/output/stream/stream_output.yuv", "ab+");

    if (Chn >= VPSS_CHN3)
    {
        s32Ret = HI_MPI_VPSS_GetExtChnAttr(Grp, Chn, &stExtChnAttr);
        u32OrigDepth = stExtChnAttr.u32Depth;
    }
    else
    {
        s32Ret = HI_MPI_VPSS_GetChnAttr(Grp, Chn, &stChnAttr);
        u32OrigDepth = stChnAttr.u32Depth;
    }

    if (s32Ret != HI_SUCCESS)
    {
        printf("get chn attr error\n");
        return;
    }

    if (Chn >= VPSS_CHN3)
    {
        stExtChnAttr.u32Depth = u32Depth;
        stExtChnAttr.u32Width = u32FrameWidth;
        stExtChnAttr.u32Height = u32FrameHeight;
        stExtChnAttr.enPixelFormat = PIXEL_FORMAT_YVU_SEMIPLANAR_420;
        s32Ret = HI_MPI_VPSS_SetExtChnAttr(Grp, Chn, &stExtChnAttr);
    }
    else
    {
        stChnAttr.u32Depth = u32Depth;
        stChnAttr.u32Width = u32FrameWidth;
        stChnAttr.u32Height = u32FrameHeight;
        stChnAttr.enPixelFormat = PIXEL_FORMAT_YVU_SEMIPLANAR_420;
        s32Ret = HI_MPI_VPSS_SetChnAttr(Grp, Chn, &stChnAttr);
    }

    if (s32Ret != HI_SUCCESS)
    {
        printf("set depth error\n");
        VPSS_Restore(Grp, Chn);
        return;
    }

    u32VpssDepthFlag = 1;

    memset(&stFrame, 0, sizeof(stFrame));
    stFrame.u32PoolId = VB_INVALID_POOLID;
    stFrame.stVFrame.u32Width = u32FrameWidth;
    stFrame.stVFrame.u32Height = u32FrameHeight;

    while (HI_MPI_VPSS_GetChnFrame(Grp, Chn, &stFrame, s32MilliSec) != HI_SUCCESS)
    {
        s32Times--;
        if (0 >= s32Times)
        {
            s32Ret = HI_MPI_VPSS_GetChnFrame(Grp, Chn, &stFrame, s32MilliSec);
            SAMPLE_PRT("HI_MPI_VPSS_GetChnFrame Error(%#x)\n", s32Ret);
            printf("get frame error for 10 times,now exit\n");
            VPSS_Restore(Grp, Chn);
            return;
        }
        usleep(40000);
    }

    if (VIDEO_FORMAT_LINEAR != stFrame.stVFrame.enVideoFormat)
    {
        printf("only support linear frame dump\n");
        HI_MPI_VPSS_ReleaseChnFrame(Grp, Chn, &stFrame);
        stFrame.u32PoolId = VB_INVALID_POOLID;
        return;
    }

    fflush(stdout);

    s32Ret = HI_MPI_VPSS_ReleaseChnFrame(Grp, Chn, &stFrame);

    if (HI_SUCCESS != s32Ret)
    {
        printf("Release frame error ,now exit\n");
        VPSS_Restore(Grp, Chn);
        return;
    }

    stFrame.u32PoolId = VB_INVALID_POOLID;

    while (u32Cnt < 100)
    // while (1)
    {
        u32Cnt++;
        if (HI_MPI_VPSS_GetChnFrame(Grp, Chn, &stFrame, s32MilliSec) != HI_SUCCESS)
        {
            printf("Get frame fail\n");
            usleep(1000);
            continue;
        }

        if ((COMPRESS_MODE_NONE != stFrame.stVFrame.enCompressMode))
        {
            printf("Do not support decompress\n");
            HI_MPI_VPSS_ReleaseChnFrame(Grp, Chn, &stFrame);
            VPSS_Restore(Grp, Chn);
            return;
        }

        if (DYNAMIC_RANGE_SDR8 == stFrame.stVFrame.enDynamicRange)
        {
            sample_yuv_8bit_dump(&stFrame.stVFrame);
            printf("DEBUG =>=>=>=>=>=>=>=> 8bit Width: [%d]; Height: [%d]\n", stFrame.stVFrame.u32Width, stFrame.stVFrame.u32Height);

            if (u32Cnt != 1)
            {
                drawLine((void *)(long)frameImg.au64VirAddr[0], u32Cnt*4, u32Cnt*4, 1920 - u32Cnt*4, 1080 - u32Cnt*4, u32FrameWidth, u32FrameHeight, 0, 0, 0);
                SAMPLE_COMM_IVE_WriteFile(&frameImg, outFrameFile);
            }
        }
        printf("Get frame %d\n", u32Cnt);

        s32Ret = HI_MPI_VPSS_ReleaseChnFrame(Grp, Chn, &stFrame);

        if (HI_SUCCESS != s32Ret)
        {
            printf("Release frame error ,now exit\n");
            VPSS_Restore(Grp, Chn);
            return;
        }

        stFrame.u32PoolId = VB_INVALID_POOLID;
    }
    fclose(outFrameFile);

    VPSS_Restore(Grp, Chn);
    return;
}

内存管理

测试条件: 开机自动加载 mpp 相关库,不开启推流服务

OS Memory

Mem: 15140K used, 10648K free

实际测试中发现在程序中创建的 OS 内存空间不会随着程序结束自动释放

运行一段包含创建 OS 内存空间的示例后等待其执行完成,通过 top 观察到程序执行前后 OS Memory 均为如下数值

Mem: 19204K used, 6584K free

使用 fopen 打开文件占用 OS Memory

MMZ Memory

测试方法:

使用 SAMPLE_COMM_IVE_CreateImage 函数创建一张 1920*1080 YUV420SP 图片

测试结果:

MMZ 内存在程序结束后自动释放,或调用 IVE_MMZ_FREE 函数手动释放

创建图片过程中调用了 HI_MPI_SYS_MmzAlloc 函数,原型如下

HI_S32 HI_MPI_SYS_MmzAlloc(HI_U64 *pu64PhyAddr, void **ppVirAddr, const HI_CHAR *strMmb, const HI_CHAR *strZone, HI_U32 u32Len)

四个参数分别为: 内存物理地址指针输出,指向内存虚拟地址指针的指针输出,MMB 名称的字符串指针输入,MMZ zone 名称的字符串指针输入,要申请的内存块大小输入

该函数根据内存长度申请用户态 MMZ 内存并返回内存地址,具体现象可通过如下命令查看

watch -n 1 cat /proc/media-mem

watch -n 1 [command] 代表每 1 秒执行一次命令

创建图片后可以看到输出中增加了一行信息

可以调用 usleep 函数暂停程序,方便观察

|-MMB: phys(0x42226000, 0x4251DFFF), kvirt=0x00000000, flags=0x00000000, length=3040KB,    name=""

其中 phys 表示内存的起始和结束地址,length 表示内存长度,name 为该块内存的名称

这里创建 1920 \times 1080 YUV420SP 格式图片,实际需要的空间为 1920 \times 1080 \times 1.5 = 3037.5~\text{KB},由于板端要求内存 4K 对齐,实际占用 3040 KB 内存空间

更正

该部分对开发板中某一模块进行了针对性的测试,得出了与预计不符的结论或验证了可能性,记录测试方法、过程与结果

内存部分

Hi3516EV200 开发板中共有 64M DDR 内存可供使用,主要分为两部分

  • OS Memory
    • 由 Linux 操作系统管理
  • MMZ Memory
    • 由 osal 模块管理,供 MPP(媒体软件处理平台) 单独使用
|---------|----------------|  0x40000000
| 32M     | Linux OS       |
| ------- | -------------- |  0x42000000
| 32M     | MMZ            |
| ------- | -------------- |  0x48000000

MMZ Memory 起始地址的计算方式为 0x40000000 + (HEX)(os_mem_size * 1024 * 1024)
例如为 OS 分配了 32M 内存后的 MMZ Memory 起始地址为 0x40000000 + (HEX)(32 * 1024 * 1024) = 0x42000000
0x40000000 为 Kernel 的起始地址

其中 OS Memory 需要通过 bootargs 进行设置,并且需要修改 bootcmd 中的 MMZ 内存起始地址

在系统引导阶段按任意键进入 boot 命令行,boot 命令行中提示符为 hisilicon #

printenv
# 显示已配置的变量信息,后面的配置基于该命令输出进行修改
setenv bootargs "mem=32M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 rw mtdparts=hi_sfc:1M(boot),4M(kernel),11M(rootfs)"
# 设置 bootargs 变量,mem 为 OS Memory
setenv bootcmd "sf probe 0;sf read 0x42000000 0x100000 0x400000;bootm 0x42000000"
# 修改 sf read 后面的地址以及 bootm 地址为 MMZ 起始地址
saveenv
# 将修改后的设置保存到系统中
printenv
# 确认更改生效
reset
# 重启系统

MMZ Memory 由 mpp/ko/hi_osal.ko 管理,在 mpp/ko/load3516ev200 脚本中第 89 行有如下命令:

insmod hi_osal.ko anony=1 mmz_allocator=hisi mmz=anonymous,0,$mmz_start,$mmz_size || report_error

insmod 命令用于向 Kernel 中载入模块,后面的参数是在加载模块时输入的参数,其中 调用了 mmz_startmmz_size 两个变量用于设置,相关变量的定义位于第 17

# DDR start:0x40000000, kernel start:0x40000000,  OS(32M); MMZ start:0x42000000
mem_total=64                  # 64M, total mem
mem_start=0x40000000          # phy mem start
os_mem_size=32                # 32M, os mem
mmz_start=0x42000000;         # mmz start addr
mmz_size=32M;                 # 32M, mmz size

在以上定义中 mem_total mem_start 以及 os_mem_size 仅用于打印日志,实际配置时按照 bootargs 和 bootcmd 进行设置即可

OS Memory 与 MMZ Memory 之和需要小于或等于 mem_total

注意:以下部分内容来自 HiMPP V4.0 媒体处理软件 FAQ.pdf 1.2.1 OS 保留内存和线程栈大小调整,在实际测试中发现并未达到预期效果(或测试方法不正确)

/etc/profile 中加入下面两行命令即可修改保留内存为 4M

echo 2 >/proc/sys/kernel/randomize_va_space
echo 4096 >/proc/sys/vm/min_free_kbytes

randomize_va_space 用于设置 ASLR(地址随机化) 的状态

0 - 关闭
1 - 将 mmap 的基址,stack 和 vdso 的内存页随机化
2 - 在 1 的基础上增加 heap(栈) 的随机化

min_free_kbytes 用于调整系统保留的最低空闲内存

该变量会在系统初始化时根据内存大小计算一个默认值,计算规则如下

\text{min\_free\_kbytes} = \sqrt{\text{lowmem\_kbytes} \times 16}
其中 lowmem_kbytes 可以认为是系统内存大小

注意:以上部分内容来自 HiMPP V4.0 媒体处理软件 FAQ.pdf 1.2.1 OS 保留内存和线程栈大小调整,在实际测试中发现并未达到预期效果(或测试方法不正确)

硬件部分

输出模式: VO -> 8bit LCD

Hi3516EV200 SoC 手册中说明了芯片本身支持 LCD 输出,并且提供了 LCD_CLK LCD_DE LCD_HS LCD_VS 以及 LCD_DATA0 - LCD_DATA7 12 个引脚用于 LCD 输出,但实际在板端并未设计相应的硬件接口,导致无法外接屏幕

推流部分

由于板端 VO 没有接入硬件设备,在该开发板中只能使用 RTSP 协议进行推流查看实时图像,启动网络视频流需要建立 web server,该项目位于 Hi3516EV200_PQ_V1.0.1.0 目录下,使用 ./ittb_stream imx307_2l./HiIspTool.sh -s imx307_2l 即可打开 web server 并建立一个小型推流服务器

按照预先配置的 32M OS Memory,其中系统本身及加载 sensor 驱动需要占用 12M 内存,启动该服务需要占用大概 5M 内存,系统保留内存约 8M (可调整,此时剩余可用内存大约 5M 左右,根据系统运行状态上下浮动约 1M

LICENSED UNDER CC BY-NC-SA 4.0
Comment