Emx芯片对接手册

1.开发环境

  • Emx的开发基于Ubuntu 22.04版本
  • 主机需要安装 cmake >= 3.19,如果版本不够,可通过类似如下方式安装更新
    sudo apt install build-essential libssl-dev
    wget https://cmake.org/files/v3.26/cmake-3.26.2.tar.gz
    tar -zxvf cmake-3.26.2.tar.gz
    cd cmake-3.26.2
    sudo ./configure
    sudo make -j8
    sudo make install
  • 主机需要安装 python3
  • 提供对接系列芯片所使用的交叉编译链,用于Emx开发人员编译第三方库文件,放入Emx3dr(如需要加入特殊的编译选项请提前告知)
  • 得到由Emx开发人员提供的EmxSdk+Emx3dr开发包
  • 对接系列芯片相对应的开发板或者样机,保证系统正常引导,网络通信正常,并且可以挂载nfs文件系统进行调试。

2.概念解释

  这里有芯片对接开发过程中涉及到的一些概念的解释,可以先大概理解一下,后面随着模块开发进程的推进再常回来看看

芯片系列

  厂商芯片普遍存在多颗不同型号相互兼容的情况,这里指的兼容特指芯片厂商sdk的兼容,而不是pin脚等硬件的兼容。因为芯片的对接实际上是对芯片商sdk的二次封装,所以首先需要芯片厂商对自己将要对接的某颗芯片设置一个芯片的系列型号,用来覆盖所有sdk兼容的其他芯片,例如Hi351xE,cv182x,ss33xDE等,后面这个名字会到处用

模块

  EmxMediaServer将媒体功能的实现划分为若干个模块,目前支持的模块有System/Vi/Isp/Venc/Aenc/Adec/Snap/Osd/QrScan/Ai,这些模块都预留好了各自的回调函数(虚函数),供芯片厂商实现

配置文件

  芯片对接过程中需要完成上面提到的各个模块回调的实现,在实现模块回调的过程中会需要加载一些配置文件来实现程序的复用(例如启动的时候需要加载媒体内存或VB分配的大小,而不是直接写死在程序里),每个模块都有自己的配置文件。配置文件是实现减少版本发布数量的第一道关卡,灵活的配置文件可以让用户在不更新代码或sdk的基础上满足自己的需求,厂商需要在默认实现上尽可能的考虑通过配置文件的修改来满足各种现实需求,从而减少版本的发布   下面是一个视频编码Venc模块配置文件venc.json的例子,每个芯片厂商可配置的参数不尽相同,所以以自身实际情况为准,

[
{
"attr": {
"ViPipe": 0,
"VpssGrp": 0,
"VpssChn": 0,
"VencChn": 0,
"bind": false,
"modSizeKb": 1536,
"defFps": 15,
"u32MinQp": 31,
"u32MaxQp": 51
...
}
},
{
"attr": {
"ViPipe": 0,
"VpssGrp": 2,
"VpssChn": 0,
"VencChn": 1,
"bind": false,
"modSizeKb": 1536,
"defFps": 15,
"u32MinQp": 31,
"u32MaxQp": 51
...
}
}
]

可以在模块的回调函数中使用如下方法进行配置文件的加载,m_chn是此模块当前的通道号,文件名和通道号启动的时候由MediaServer传入

Json::Value root;
MediaServerCtx::GetInst()->GetCfg(m_module->m_name.c_str(), root);
auto &json = root[m_chn];

通道号

  针对上面配置文件的例子,这里提出通道的概念,关于各个模块的通道号m_chn,这个通道号是一个虚拟的概念,指的并不是实际的物理通道,例如视频编码VENC,如果支持4路视频编码,那么虚拟通道号是0 1 2 3,其对应的芯片物理编码通道可能是 0 2 3 5。虚拟通道的概念是为了方便上层应用使用,屏蔽不同芯片带来的差异,虚拟通道与实际物理通道的对应关系一般是需要写在模块的配置文件中,而模块的虚拟通道号就是json文件中json对象数组的索引下标,例如上面配置文件中的某款芯片的venc.json配置,数组包含了两个对象,分别代表了venc0和venc1两个通道, attr 表示通道配置参数,可以看到其对应的物理通道是ViPipe-0,VpssGrp-0,VpssChn-0,VencChn-0,并且还有一些编码常用的调教参数。 range 表示了这一路支持的编码参数取值范围,目前adec-aenc-venc三个模块需要按照如上的方式定义参数的取值范围

参数文件

  关于配置文件与参数文件,模块启动的时候使用 MediaServerCtx::GetInst()->GetCfg() 获取的是配置文件,这部分文件是客户不可见的,对应的还有参数文件,这部分文件是用户可通过接口配置的(通常是OnParamChanged接口),加密的,比较简单的,例如下面venc的参数文件venc.json,同样数组下标代表通道号,与配置文件的通道是一一对应的。一般来讲参数文件同样也是各个模块启动过程中需要的。

[
{
"codec": 1,
"width": 2304,
"height": 1296,
"fps": 15,
"bitRate": 1480,
"rcMode": 2,
"iFrameInv": 4,
"flip": false,
"mirror": false,
"drawRect": false
},
{
"codec": 0,
"width": 640,
"height": 360,
"fps": 15,
"bitRate": 285,
"rcMode": 2,
"iFrameInv": 4,
"flip": false,
"mirror": false,
"drawRect": false
}
]

模块的配置文件名与参数文件名一般是一样的,但是二者存放的位置不同,配置文件存放于configs目录下,参数文件存在param目录下

scene

  提到配置文件和参数文件就不得不提到scene,当我们去实现某一款设备的时候可能会遇到需要底层参数大面积改动的情况,例如,一款多目拼接设备正常情况下需要输出拼接后的编码视频,但某种情况下需要输出拼接前各个sensor独立的编码图像。又例如某款内存极为紧张的设备去动态切换分辨率,类似这种需求的切换很大可能是需要调整PipeLine甚至媒体内存的分配,而这种复杂的调整没办法简单的通过参数组合调用去动态实现,因此这里引入了scene的概念,每一个scene就是一个使用场景,同时对应一组配置和参数(system.json+venc.json...),例如刚刚提到的多目设备就可以分为两个scene,一个命名为multiSenStitch的scene保存的是多目拼接输出下的一组配置参数,另个一命名为mutiSenIndep的scene保存的是各个sensor独立编码输出的一组配置参数。这样,实际应用中就可以通过切换scene来进行两个使用场景的切换,具体切换过程是使用当前的scene配置+参数停止当前MediaServer所有的模块,然后使用新的scene配置+参数启动所有模块   当一颗芯片首次进行对接的时候,需要首先实现默认的scene,也就是常规IPC应用场景下的一组配置参数,因为scene的引入,因此所有模块的配置和参数都是存储在某一个scene目录下,例如configs/multiSenStitch/venc.jsonparam/multiSenStitch/venc.json

profile

  芯片对接的过程中,各个模块都有自身的配置文件,因此很多情况下,新的需求可以仅通过修改配置文件即可满足,但同样存在不能仅靠修改配置文件就能满足需求的情况,因此,这里提出profile的概念,一个profile就是某款芯片的一个完整的代码实现,起初,新的芯片接入只需要实现chipXDefault(chipX为芯片系列)这个默认的profile即可,当出现无法通过配置文件满足的需求时,可能某些模块的代码需要进行一些特殊的定制化的修改,当面临这样的修改时,就可以新建一个profile,姑且命名为profileA,profileA可以继承chipXDefault通用的部分,然后override自身特殊的部分,从而实现特殊的功能,同样的也可建立profileBprofileC等等,他们之间可以有多层次的继承关系

>注意:profile的命名应当是芯片系列名称+合适的后缀,例如hi351xDouStitch,cv182xDefault等

3.工程搭建

  芯片厂商拿到EmxSdk以及Emx3dr后就可以愉快的进行对接开发了

1.拷贝工程模板

  将EmxSdk中的EmxMediaDevelop拷贝到一个让自己舒适的地方,然后进入到scripts目录执行下面的操作,这一步是为了将依赖的EmxSdk和Emx3dr目录软连接到EmxMediaDevelop工程目录下,方便为后续编译和打包提供一致性环境(不连接编不过的)

chmod +x *
./softLink.py the/path/of/Public

下面来介绍一下这个工程里面的内容

├── Public # 刚刚软连接过来的,里面存放了各个EmxSdk版本和依赖库
├── CMakeLists.txt # 工程编译的顶层cmake文件,不需要改动
├── EmxMedia.cpp # 固定的抽象工厂实例化代码文件,不需要改动
├── EmxMediaVersion # 版本号记录文件,不需要理会,每次编译自动更新
├── chip # 这个下面全是代码,存放不同芯片厂商以及芯片系列的模块回调源文件
│   └── manufactoryX # 这里假设厂商名是manufactoryX
│   └── chipX # 这里假设芯片系列是chipX
│   ├── DefaultSystem.cpp # System模块默认实现
│   ├── DefaultSystem.hpp
│   ├── ... # 等等
│   ├── DefaultVenc.cpp # Venc模块默认实现
│   ├── DefaultVenc.hpp
│   └── chipXDefault # 这是一个默认profile,文件夹名称与profile/chipXDefault相同
│   ├── chipXDefaultFactory.cpp # 构造默认profile实现的工厂
│   └── chipXDefaultFactory.hpp
├── common # 这里面用于存放一些通用的代码
├── profile # 这里面是每个profile的各种配置文件
│   └── chipXDefault # chipXDefault配置文件
│   ├── build.cmake # profile的cmake构建配置文件,需要修改
│   ├── configs # 芯片各个模块针对此profile的配置文件,需要修改
│   └── configs.json # profile的信息,需要修改
└── scripts # 各种脚本
├── build.py # 单独profile编译
├── release.py # 所有profile编译并发布
├── softLink.py # 软连接
└── utils.py # 工具

2.编译测试

  此时将profile/chipXDefault/configs.jsonEmxSdkVersion的值修改为EmxSdk目录中的最新版本,例如"EmxSdk-20231031-1",然后进入scripts目录下执行:

./build.py

由于没有指定参数,此时应当会出现下面的打印,profile list列出了当前已经实现的profile有哪些(通过遍历profile目录)

usage:./build.py profile_index
eg. ./build.py 0
-------------------profile list-------------------
0:chipXDefault

此时我们执行build命令并携带profile的索引号,编译chipXDefault

./build.py 0

一通编译打印之后应该会在EmxMediaDevelop/output目录下生成了EmxMedia-Debug文件夹,这里面包含了一个完整的profile的输出

如果没编过,看看cmake版本是否正确,EmxSdk版本是否正确,softLink的两个路径是否正确...

EmxMedia-Debug # debug版本
└── chipXDefault # profile名
├── EmxMediaServer # 编译出来的Media服务程序!虽然目前还是个空壳子,啥功能没有
├── build.cmake # 下面这些就是直接从工程profile文件夹下面拷贝过来的
├── configs
└── configs.json

3.发布测试

  执行下面命令即可,会将所有的profile目录下的所有profile进行编译打包

./release.py

最终输出包含版本信息的所有支持的profile

EmxMedia-xxxxxxxx-x
└── chipXDefault

这个EmxMedia-xxxxxxxx-x就是芯片对接最终版本交付文件

4.工程配置

  通过工程的搭建,目前可以完成chipXDefault的发布,接下来通过工程配置,就可以完成芯片厂商即将对接芯片的发布,期间也会对各种工程配置文件如何修改和编写进行详细的说明

0.命名

  首先要对即将对接芯片系列进行命名,包括厂商-芯片系列-默认profile,可以参考如下定义方式,**后面的所有说明均以此为例**

厂商-ChipManufactory:cvitek
芯片系列-ChipSerial:cv182x
默认profile-Profile:cv182xDefault

1.填充Emx3dr/chip

  Emx3dr/chip中存储着各个芯片厂商自己的sdk文件,对接前需要将芯片厂商自己的sdk中的头文件/库文件/sample文件(如果对接需要用到并编译这些代码)/驱动文件 按照格式放入到Emx3dr/chip中,路径依次是chip-cvitek-cv182x-sdk版本号-头文件+库文件+驱动+[sample],这样才能保证后面工程的顺利编译,像下面这样:

chip
├── cvitek
│   ├── cv182x
│   │   ├── 2.1.1.12
│   │   │   ├── include
│   │   │   ├── lib
│   │   │   ├── drivers
│   │   │   └── sample
│   │   └── 2.1.1.36
│   │   ├── include
│   │   ├── lib
│   │   ├── drivers
│   │   └── sample
│   └── cv183x
│   └── 2.2.0.1
│   ├── include
│   ├── lib
│      └── drivers
...

其中sdk版本号按照厂商自己的规则来就行

2.建立profile

  进入EmxMediaDevelop/profile中,将chipXDefault复制为cv182xDefault   进入EmxMediaDevelop/chip中,复制manufactoryX为cvitek,然后将cvitek下的chipX统一替换为cv182x,chipXDefault替换为cv182xDefault,包括:

  • chipX目录名
  • chipXDefault目录名
  • chipXDefaultFactory.xpp文件名及其中代码内容

3.编写configs.json

  EmxMediaDevelop/profile/cv182xDefault/configs.json文件存储了工程在编译打包过程中需要的必要信息,厂商需要按照实际情况完成修改,内容示例如下:

{
"EmxSdkVersion": "EmxSdkDebug",
"ChipManufactory": "cvitek",
"ChipSerial": "cv182x",
"ChipSdkVersion": "2.1.1.19",
"Cross": "arm-cvitek-linux-uclibcgnueabihf",
"CrossPath": "/opt/toolchain/cvitek/arm-cvitek-linux-uclibcgnueabihf/bin",
"runtime": {
"lib": [
"libboost_system.so.1.68.0",
"libcnpy.so",
...
"libsys.so"
],
"drivers": [
"cv182x_base.ko",
...
"cvi_mipi_rx.ko",
"loadko"
]
}
}
Key Type Description
EmxSdkVersion string Emx版本,修改为实际基于的版本,例如EmxSdk-20231031-1
ChipManufactory string 芯片厂商
ChipSerial string 芯片系列
ChipSdkVersion string Emx3dr/chip中基于的芯片sdk版本
Cross string 编译链
CrossPath string 编译链的绝对路径,这个值可以为空"",如果某种情况下找不到编译链的时候才需要填写
runtime obj 这部分内容指示固件打包的时候需要将Emx3dr/chip芯片sdk中的哪些文件复制到固件中,也就是设备运行时需要依赖的芯片库和驱动等
runtime.lib string array 设备运行依赖动态库文件列表
runtime.drivers string array 设备运行依赖驱动文件列表

注意runtime.drivers中需要一个由芯片厂商编写的名为 loadko 的默认脚本负责加载本目录下依赖的所有driver

4.编写build.cmake

  顶层CMakeLists.txt会根据选择的profile定位并包含此文件,是芯片厂编译自身代码的入口,其中有一些由顶层CMakeLists.txt传进来的变量可以使用,包括:

CMAKE_SOURCE_DIR : 工程顶层EmxMediaDevelop的绝对路径
ChipManufactory :厂商名
ChipSerial :芯片系列
ChipSdkVersion :芯片sdk版本号
Profile :当前profile
EmxChipPath :Emx3dr/chip路径

下面说明这个文件内需要做的事情

  • 赋值ChipSources变量 这一步收集所有需要编译的源文件并赋值给ChipSources变量,如果有profile继承的话,就将需要继承的profile源文件也加入进来,例如:
    aux_source_directory(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial} ChipSources)
    aux_source_directory(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/${Profile} ChipSources)
    aux_source_directory(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/sample ChipSources)
  • 赋值ChipLibs变量 将最终需要链接的库文件添加到ChipLibs中,例如:
    set(ChipLibs
    libz.a
    libisp.a
    libae.a
    libawb.a
    libaf.a
    libvenc.a
    ...
    )
  • 添加路径依赖 自行添加头文件和库文件路径依赖,例如:
    # 头文件依赖
    include_directories(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial})
    include_directories(${CMAKE_SOURCE_DIR}/chip/${ChipManufactory}/${ChipSerial}/${Profile})
    include_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/include)
    ...
    # 库文件依赖
    link_directories(${EmxChipPath}/${ChipManufactory}/${ChipSerial}/${ChipSdkVersion}/lib)
    ...
  • 自定义宏定义和编译选项 厂商可以在这里添加自己需要的定义
    add_definitions(-DCVI_MODIFIED)
    add_compile_options(-DSENSOR_SMS_SC3335)

另外需要注意,为了提高代码质量EmxMediaDevelop以及包括EmxSdk本身的开发默认是打开-Werror编译选项的,所有告警都会被当作错误处理,这一点希望对接人员不要随意忽略告警的产生

  至此,就完成了cv182x芯片的工程配置,可以使用3.工程搭建中的方法尝试编译和发布一下新的profile,如果编译发布没有问题,可以进行下面的上板运行工作了

5.调试运行

  Emx的运行需要特定的运行环境,首先保证设备(或开发板)上电并已经加载好芯片需要的运行环境,包括各种ko,然后需要设备具备挂载nfs文件系统的功能(或者使用效率稍微低一些的sd卡挂载方式,以下仅以nfs举例)

1.创建固件目录结构

  修改并使用如下脚本在当前目录下创建调试目录emx

#!/bin/sh
# 先修改这些变量
Cross=arm-linux-gnueabihf
# 需要绝对路径
EmxSdkVersionPath=/xxx/the/path/of/EmxSdk-xxxxxxxx-x
Emx3drPath=/xxx/the/path/of/Emx3dr
EmxProfilePath=/xxx/the/path/of/EmxMedia-xxxxxxxx-x/chipXDefault
# 构建目录
mkdir -p emx
cd emx
mkdir -p firmware data/normal data/backup
cd firmware
mkdir -p 3dr/bin 3dr/lib app parm media/bin media/lib configs/media/scene/default
# 拷贝sdk
cp -r ${EmxSdkVersionPath}/cross/${Cross}/bin app
cp -r ${EmxSdkVersionPath}/cross/${Cross}/lib app
chmod +x app/bin/*
# 拷贝第三方库,注意版本号更新
cp ${Emx3drPath}/common/jsoncpp/1.9.3/${Cross}/lib/*.so* 3dr/lib
cp ${Emx3drPath}/common/libuv/1.44.2/${Cross}/lib/*.so* 3dr/lib
# 拷贝param,调试阶段这里先使用明文参数
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/orgParam/* param/
# 拷贝通用psp-param
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/psp/param/* param/
# 拷贝通用configs
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/orgConfigs/* configs/
# 拷贝通用psp-configs
cp -r ${EmxSdkVersionPath}/EmxProductTemplate/psp/configs/* configs/
# 将scene中的配置文件加入到scene中
cp ${EmxProfilePath}/configs/* configs/media/scene/default
# 拷贝EmxMediaServer
cp ${EmxProfilePath}/EmxMediaServer media/bin
chmod +x media/bin/*
# 芯片运行需要依赖的厂商自身的动态需要自行拷贝到media/lib下
# 如果已经在系统默认路径下如/lib /usr/lib则可以不用拷贝

2.挂载

  将刚刚生成的emx目录挂载到设备上,这里假设挂载到设备的/mnt/nfs目录下

mount -t nfs -o nolock 192.168.10.123/home/xx/workspace/emx /mnt/nfs

3.修改环境变量

  运行前需要向环境变量中添加firmware的所在路径,可以将以下内容写到某个文件中source一下

export EMX_DATA_DIR=/mnt/nfs/emx/data
export EMX_FIRMWARE_DIR=/mnt/nfs/emx/firmware
export EMX_3DR_DIR=${EMX_FIRMWARE_DIR}/3dr
export EMX_APP_DIR=${EMX_FIRMWARE_DIR}/app
export EMX_MEDIA_DIR=${EMX_FIRMWARE_DIR}/media
export EMX_CONFIGS_DIR=${EMX_FIRMWARE_DIR}/configs
export EMX_PARAM_DIR=${EMX_FIRMWARE_DIR}/param
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
export PATH=${PATH}:${EMX_3DR_DIR}/bin
export PATH=${PATH}:${EMX_APP_DIR}/bin
export PATH=${PATH}:${EMX_APP_DIR}/scripts
export PATH=${PATH}:${EMX_MEDIA_DIR}/bin
export PATH=${PATH}:${EMX_MEDIA_DIR}/scripts
export LD_LIBRARY_PATH=/lib:/usr/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_3DR_DIR}/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_APP_DIR}/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${EMX_MEDIA_DIR}/lib
# 调试阶段定义这个环境变量可以暂时使用明文参数
export EMX_PLAINT_PARAM=1

4.执行

  EmxMediaServer依赖EmxCoreServer,运行如下指令,即可启动EmxMediaServer并进入后台执行

EmxCoreServer -b
EmxMediaServer -b

5.测试工具(todo)

  完成模块对接后,可以运行如下指令,然后配合客户端EmxClient.exe,进行EmxMediaServer的模块测试,需保证通过完整测试用例

EmxToolsV2 -b