04 第一个 C++ 程序
本页用仓库自带的 get_started 样例,带你跑通相机的最小采集链路。后文所有构建、部署、运行命令都围绕这个样例展开,可执行文件为 hv_sample_get_started。
先克隆 toolkit 发布仓库(gitee 与 github 内容一致,更多下载方式见 资料下载):
# gitee(国内推荐)
git clone https://gitee.com/ShiMetaPi_0/shimetapi_hybrid_vision_toolkit.git
# github
git clone https://github.com/ShiMetaPi/shimetapi_hybrid_vision_toolkit.gitv2.0 三后端(USB / MIPI / Ethernet)使用同一套 Camera API,后端由 DeviceConfig.backend 选择。
平台总览
Hybrid Vision Toolkit 发布版(shimetapi_Hybrid_vision_toolkit_release)以预编译 .so 分发,当前已适配 x86_64(USB)、S100、X5 三个平台——预编译库随仓库携带(lib/x86_64、lib/s100、lib/x5),构建只编译示例并链接对应架构的库:
| 平台 | 接入 | 预编译库 | 构建 | 状态 |
|---|---|---|---|---|
| x86_64(Ubuntu 主机) | USB 相机 | lib/x86_64 | ./run.sh build | ✅ 已适配 |
| S100(RDK 载板) | MIPI 模块 | lib/s100 | ./run.sh build s100 | ✅ 已适配 |
| X5(RDK 载板) | MIPI 模块 | lib/x5 | ./run.sh build x5 | ✅ 已适配 |
| RK3588 | MIPI 模块 | — | — | ⏳ 待适配 |
- 构建产物统一在
out/<arch>/build(三个架构互不覆盖);./run.sh --list查看预编译库就绪状态。 - 平台差异只体现在构建方式与部署路径——API 层面 USB / MIPI 完全一致,仅
DeviceConfig.backend与事件解码器不同(USB 用Evt2Decoder,MIPI 用MipiRaw8Decoder)。
1. USB(x86_64)
涉及 API
| API | 说明 | 文档 |
|---|---|---|
Camera | 统一采集(Init → StartStream → GetFrame → …) | 查看 → |
DeviceConfig | 配置后端 + VID/PID | 查看 → |
Frame | 统一帧(evs 事件字节 + 按 Frame.format 解释的 aps) | 查看 → |
EventCD | 解码后的事件 (x, y, t, polarity) | 查看 → |
Evt2Decoder | EVT2 字节流 → EventCD | 查看 → |
前置条件
sudo apt-get update
sudo apt-get install -y build-essential cmake libusb-1.0-0 libopencv-dev核心代码
下面是 get_started 样例的教学版代码——省略了命令行参数解析,聚焦 USB 后端的最小流程。仓库里 samples/cpp/get_started/main.cpp 的实际源码还支持命令行切换后端(--mipi / --sensor-index N,或命令行传 VID PID),完整逻辑见源文件。
USB 后端以 VID/PID 选设备,GetFrame 同步拉取组合帧(事件 + APS),再用 Evt2Decoder 解码:
#include <shimetapi/hv/camera.h>
#include <shimetapi/hv/device_config.h>
#include <shimetapi/codec/evt2_codec.h>
#include <iostream>
#include <vector>
int main() {
Shimeta::hv::Camera cam;
Shimeta::hv::DeviceConfig cfg;
cfg.backend = Shimeta::hv::Backend::Usb;
cfg.vendor_id = 0x1d6b; // 替换为你的实际 VID/PID
cfg.product_id = 0x0105;
cfg.event_fmt = Shimeta::hv::EventFormat::Evt2;
cam.Init(cfg);
if (!cam.StartStream()) {
std::cerr << "打开相机失败,请检查 USB 连接与权限。" << std::endl;
return 1;
}
std::cout << "相机已就绪" << std::endl;
// 同步拉取 10 帧
Shimeta::codec::Evt2Decoder dec;
Shimeta::Frame f;
for (int i = 0; i < 10; ++i) {
if (cam.GetFrame(f, 1000)) {
std::vector<Shimeta::EventCD> events;
dec.Decode(f.evs.data, f.evs.size, events); // 原始字节 → EventCD
std::cout << "frame " << i << ": evs=" << f.evs.size
<< " bytes, decoded " << events.size() << " events" << std::endl;
}
}
cam.StopStream();
cam.Destroy();
return 0;
}v2.0 相机给的是原始事件字节(Frame.evs),需用对应 codec 解码。EventCD 字段:x/y(坐标)、t(微秒时间戳)、polarity(true=CD_ON / false=CD_OFF)。
构建与运行
cd shimetapi_Hybrid_vision_toolkit # 即开头克隆的仓库目录
./run.sh build # 预编译库随仓库分发,只编示例;x86_64 主机上默认即 x86_64./out/x86_64/build/samples/cpp/get_started/hv_sample_get_started # 默认 0x1d6b:0x0105
./out/x86_64/build/samples/cpp/get_started/hv_sample_get_started 0x1d6b 0x0105 # 指定 VID PIDUSB 权限:若报 LIBUSB_ERROR_ACCESS,推荐 udev 规则(免 sudo):
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1d6b", ATTR{idProduct}=="0105", MODE="0666"' \
| sudo tee /etc/udev/rules.d/99-hv-camera.rules
sudo udevadm control --reload-rules && sudo udevadm trigger2. S100(MIPI / 交叉编译)
涉及 API
| API | 说明 | 文档 |
|---|---|---|
Camera | 统一采集(同 USB,后端改为 Mipi) | 查看 → |
DeviceConfig | backend + evs_fps / sensor_index | 查看 → |
Frame | 统一帧(evs RAW8 + aps 图像) | 查看 → |
MipiRaw8Decoder | RAW8 子帧流 → EventCD | 查看 → |
MIPI 不是 USB 设备:不用 VID/PID,而用传感器索引选设备。S100 上 apx003cc 传感器配置(linear_4096x256_raw8)的索引为 9(示例程序已由 CMake 按架构注入默认值)。
前置条件
以下均在 x86_64 主机上交叉编译(板卡上无需编译环境):
# 1) aarch64 交叉工具链(Ubuntu 系统包即可)
sudo apt-get install -y g++-aarch64-linux-gnu
# 2) S100 板级 sysroot(evs_device_vendor_sdk 仓库,gitee 与 github 内容一致)
git clone https://gitee.com/ShiMetaPi_0/evs_device_vendor_sdk.git # 国内推荐
# git clone https://github.com/ShiMetaPi/evs_device_vendor_sdk.git # 海外镜像
export S100_SYSROOT=$PWD/evs_device_vendor_sdk/source/hobot-multimedia/debian/usr核心代码
教学版代码——对应 get_started --mipi 在板上跑的流程(EVS-only 单 VC,用传感器索引选设备):
#include <shimetapi/hv/camera.h>
#include <shimetapi/hv/device_config.h>
#include <shimetapi/codec/mipi_raw8_codec.h>
#include <iostream>
#include <vector>
int main() {
Shimeta::hv::Camera cam;
Shimeta::hv::DeviceConfig cfg;
cfg.backend = Shimeta::hv::Backend::Mipi; // EVS-only 单 VC
cfg.sensor_index = 9; // S100:apx003cc linear_4096x256_raw8 的索引
cam.Init(cfg);
if (!cam.StartStream()) {
std::cerr << "无法启动 MIPI 设备" << std::endl;
return 1;
}
std::cout << "MIPI 设备已启动" << std::endl;
// MIPI 的 Frame.evs 是 apx003 RAW8 子帧流 → 用 MipiRaw8Decoder(不是 Evt2Decoder)
Shimeta::codec::MipiRaw8Decoder dec;
Shimeta::Frame f;
for (int i = 0; i < 10; ++i) {
if (cam.GetFrame(f, 1000)) {
std::vector<Shimeta::EventCD> events;
dec.Decode(f.evs.data, f.evs.size, events); // 自动按数据长度适配子帧数
std::cout << "frame " << i << ": decoded " << events.size() << " events" << std::endl;
}
}
cam.StopStream();
cam.Destroy();
return 0;
}MIPI 的
Frame.evs是 RAW8 子帧流,必须用MipiRaw8Decoder,不能用Evt2Decoder——这是 USB 和 MIPI 唯一的解码差异。
构建与部署
./run.sh build s100 # aarch64 工具链文件自动注入(toolchains/toolchain-aarch64-linux-gnu.cmake)
file out/s100/build/samples/cpp/get_started/hv_sample_get_started # 验证:应为 ELF aarch64out/s100/build 是自包含目录——构建时已把 lib/s100 的 libshimetapi_*.so 捆绑进去,样例 rpath 用 $ORIGIN 相对寻址,整个目录拷上板即可运行:
# 宿主机:部署到板卡
scp -r out/s100/build root@<板卡IP>:/app/
# 板卡上运行(跑的就是上方核心代码的模式)
export LD_LIBRARY_PATH=/app/build # 预编译库与可执行文件在同一 build 目录下
/app/build/samples/cpp/get_started/hv_sample_get_started --mipi # EVS-only,sensor_index 默认 9
/app/build/samples/cpp/get_started/hv_sample_get_started --mipi --sensor-index 9 # 显式指定索引板上不需要交叉编译环境
S100_SYSROOT、aarch64 工具链等只在编译主机上用;板卡上开箱即跑。OpenCV 类样例(player / live_record_display)还需把仓库 third_party/aarch64_opencv/lib/aarch64-linux-gnu 拷到板上并 export LD_LIBRARY_PATH 指向它。
3. X5(MIPI / 交叉编译)
X5 与 S100 用法一致(同一套 API、同一套样例),差异只有三处:SDK 路径环境变量、sensor_index 默认值、APS 输出格式。
前置条件
# 1) aarch64 交叉工具链(同 S100)
sudo apt-get install -y g++-aarch64-linux-gnu
# 2) X5 SDK 源码树(evs_device_vendor_sdk 仓库 x5_v3.4.1 分支,gitee 与 github 内容一致)
git clone -b x5_v3.4.1 --single-branch https://gitee.com/ShiMetaPi_0/evs_device_vendor_sdk.git
# 或(海外)git clone -b x5_v3.4.1 --single-branch https://github.com/ShiMetaPi/evs_device_vendor_sdk.git
export X5_SDK_ROOT=$PWD/evs_device_vendor_sdk # 告知构建脚本 SDK 位置核心代码与构建部署
代码与 S100 节逐行相同(同一套 Camera API + MipiRaw8Decoder),无需改源码即可交叉编译——X5 的差异只在构建配置与板端输出上,直接构建:
./run.sh build x5 # 读取 X5_SDK_ROOT
file out/x5/build/samples/cpp/get_started/hv_sample_get_started # 应为 ELF aarch64
# 部署与运行同 S100:整个目录拷上板,板上先设 LD_LIBRARY_PATH
scp -r out/x5/build root@<板卡IP>:/app/
export LD_LIBRARY_PATH=/app/build # 板卡上执行
/app/build/samples/cpp/get_started/hv_sample_get_started --mipi # EVS-only,sensor_index 默认 494. RK3588(待适配)
RK3588 平台尚未适配:当前发布版未提供 lib/rk3588 预编译库,./run.sh 亦未接入 rk3588 构建目标,./run.sh --list 暂不会列出该架构。
- 代码无需预改:适配后仍走
Backend::Mipi(EVS-only)同一套CameraAPI,MipiRaw8Decoder解码不变——届时只需换用lib/rk3588预编译库重新编译示例。 - 待补齐内容:
lib/rk3588预编译库(aarch64)+ 对应板级 sysroot 的交叉编译接入 + 板端验证(含 sensor_index 索引表)。 - 适配进度请关注 资料下载 的发布版更新,或联系技术支持。
5. 新建程序
在 samples/cpp/ 下新建案例(以 my_demo 为例)需要改 3 处,缺一不可——少改第 ② ③ 步时新样例不会参与编译。这三处修改对 USB、S100、X5 均相同;main.cpp 按目标平台选择本页 USB 或 MIPI 的核心代码。
① 创建样例
samples/cpp/my_demo/
├── CMakeLists.txt # 样例构建脚本
└── main.cpp # 样例源码(USB 或 MIPI 后端均可)CMakeLists.txt 照抄 get_started 的写法即可(链接根 CMakeLists 定义的 IMPORTED 目标,无需写头文件路径和 .so 位置):
add_executable(hv_sample_my_demo main.cpp)
target_link_libraries(hv_sample_my_demo PRIVATE
HVToolkit::shimetapi_hv HVToolkit::shimetapi_codec HVToolkit::shimetapi_io)- 多源文件:往
add_executable里追加,如add_executable(hv_sample_my_demo main.cpp utils.cpp) - 需要 OpenCV(显示窗口类):抄
samples/cpp/player/CMakeLists.txt的条件块;交叉编译时自动使用third_party/aarch64_opencv,无需板上安装 OpenCV。
② 注册子目录
add_subdirectory(cpp/my_demo)不加这行,目录建了也不会被编译。
③ 添加依赖
找到 add_dependencies(bundle_libs ...)(约 119 行),把新目标名追加进去:
add_dependencies(bundle_libs
hv_sample_get_started hv_sample_callback hv_sample_record hv_sample_viewer
hv_sample_bench_hw hv_sample_live_record_display hv_sample_player
hv_sample_my_demo) # ← 新增这步保证「预编译库捆绑到 build 根」在新样例构建后完成。不加虽能编译,但部署目录可能缺库。
④ 注册样例(可选)
SAMPLE_NAMES="get_started callback record viewer bench_hw live_record_display player my_demo"只影响 ./run.sh samples 是否列出新样例(OK/MISS 状态),与编译无关。
构建与运行
| 平台 | 构建 | 运行 |
|---|---|---|
| USB(x86_64) | ./run.sh build | ./out/x86_64/build/samples/cpp/my_demo/hv_sample_my_demo |
| S100(MIPI) | ./run.sh build s100 | 部署 out/s100/build 后运行 /app/build/samples/cpp/my_demo/hv_sample_my_demo --mipi |
| X5(MIPI) | ./run.sh build x5 | 部署 out/x5/build 后运行 /app/build/samples/cpp/my_demo/hv_sample_my_demo --mipi |
S100、X5 的交叉编译前置条件与部署环境变量沿用各自平台章节;MIPI 的 sensor_index 默认值分别为 9 和 49。
改完 CMakeLists 后产物异常时
CMake 会缓存旧的目标依赖关系。若改完行为不符合预期,删除对应的 out/<arch>/build 后重新构建。
6. 更多信息
S100 与 X5 的差异
| S100 | X5 | |
|---|---|---|
| SDK 环境变量 | S100_SYSROOT | X5_SDK_ROOT |
sensor_index 默认值¹ | 9 | 49 |
| APS 输出格式² | NV12(彩色) | Gray8(灰度) |
¹ 均为 apx003cc linear_4096x256_raw8 配置的索引——X5 的 SDK 传感器列表更长,同一配置落在 49。
² S100 的 APS 经 ISP/PYM 输出 NV12;X5 当前 ISP 2A ioctl 受限,APS 走 VIN 直读 RAW10→Gray8 bypass,属预期行为(非故障),应用按 Frame.format 分支解码即可。
7. 延伸阅读
- 完整 API 参考:C++ API
- 按任务深入:编程指引(打开相机 → 读事件 → 录制 → 去噪 → 显示 → 调参)
- 示例总览:示例程序总览
- 板卡侧完整流程(烧录镜像、硬件连接):RDK S100 载板适配 / RDK X5 载板适配
