集成指南
本篇讲如何把 X-Brain SDK 集成进你的 C/C++ 工程:编译链接、连接、订阅模型、回调内存管理、C 接口。
编译与链接
C++(推荐)
# <arch> 取 x86_64 或 aarch64,与目标机架构一致
g++ -std=c++17 my_app.cpp \
-I<sdk>/include \
-L<sdk>/lib/<arch> -llixel_sdk \
-Wl,-rpath,'$ORIGIN/lib/<arch>' \
-o my_app
-I<sdk>/include:头文件目录(两架构通用)。-llixel_sdk+-L<sdk>/lib/<arch>:链接对应架构的动态库。-Wl,-rpath,'$ORIGIN/lib/<arch>':让程序运行时按相对路径找到liblixel_sdk.so。也可用绝对路径-Wl,-rpath,/opt/x-brain-sdk/lib/<arch>,或运行前export LD_LIBRARY_PATH=<sdk>/lib/<arch>。
C++11 即可编译,推荐 C++17。
校验库依赖
liblixel_sdk.so 只依赖 glibc 与 libstdc++(pthread、dl、m、rt 均属其中),常见 Linux 发行版自带。可自行核对:
readelf -d lib/<arch>/liblixel_sdk.so | grep NEEDED
动态库只导出 xg_ 前缀符号,第三方依赖已静态并入并隐藏,调用方工程即便自带同名库的其他版本也不会冲突:
nm -D --defined-only lib/<arch>/liblixel_sdk.so | grep -v ' xg_'
连接设备
xg::Options opts;
opts.deviceIp = "192.168.123.103"; // 必填
opts.port = 0; // 0 表示默认 7448
opts.connectTimeoutMs = 0; // 0 表示默认 5000 毫秒
xg::Error err = xg::Error::Ok;
auto device = xg::Device::open(opts, &err); // 失败返回 nullptr
if (!device) {
std::fprintf(stderr, "连接失败: %s\n", xg::toString(err));
return 1;
}
Device由std::unique_ptr持有,析构时自动断开连接,无需手动 close。- 禁止拷贝与移动,避免句柄被重复释放。
- 一个
Device对应一台设备,本版本不支持同时连接多台设备。 open()会实际访问一次设备作为连通性探测,返回成功即代表链路已通、设备端服务已就绪。同网段实测open()耗时约 10 毫秒。
关于 Options.port
直接填 modeling_app 的端口,默认 7448。 保持 0 即用默认,绝大多数设备无需改动。仅当设备端调整过端口时,才填其实际端口。
订阅数据
所有数据订阅共享相同模式:
// 注册回调,SDK 内部线程触发
device->subscribePose([](const xg::Pose& pose) {
// 在此处理数据,须尽快返回
});
// 取消订阅
device->unsubscribePose();
关键约束:
- 回调在 SDK 内部线程触发,不可阻塞;
- 同一设备句柄每种数据类型最多一个活跃订阅(重复订阅返回
Rejected); - 图像按相机分三个独立订阅槽;
- 不订阅时不消耗网络带宽;
Device析构时自动取消所有订阅。
回调内存与所有权模型(最易踩的坑)
SDK 对外结构体按内存所有权分两类,混淆二者是集成方最易犯的错误。
SDK 填充型(订阅回调)
Pose、Imu、CloudFrame、ImageFrame、ErrCodeFrame。
- 结构体与其内部指针(
points/jpegData/entries)全部由 SDK 拥有。 - 变长数据(点数组、JPEG 码流、错误码数组)指向 SDK 内部的复用缓冲区:
- 指针仅在本次回调执行期间有效;
- 回调返回后,下一帧会覆盖同一块缓冲;
- 因此跨回调持有该指针 = 悬垂 / 被改写,属未定义行为。
内存管理铁律:
- 回调内绝不保存裸指针。要留数据必须在回调内深拷贝:定长标量按值存;变长数据用
memcpy(点云 / JPEG)或std::vector::assign(错误码)拷到自己的缓冲。 - 不要对回调里的任何指针调用
free/delete——不是你分配的。 - 回调不可阻塞(会拖住 SDK 会话线程,影响所有订阅)。要做重活,拷贝后转交自己的线程。
按值类型的差异:
Pose、Imu是纯标量,回调参数本身可安全按值再存一份;CloudFrame、ImageFrame的points/jpegData仍是指向 SDK 缓冲的裸指针,须回调内拷贝;ErrCodeFrame::entries(C++)是已拷出的std::vector,回调内可随意读,但跨回调持有仍需再拷一份。
拷贝示例
// 点云:整帧拷贝
device->subscribeCloud([](const xg::CloudFrame& f) {
std::vector<xg_point_t> saved(f.points, f.points + f.count); // 深拷贝
// 把 saved 转交自己的处理线程/队列
});
// 图像:拷出 JPEG 字节
device->subscribeImage(xg::Device::CameraId::Left,
[](const xg::Device::ImageFrame& f) {
std::vector<uint8_t> jpeg(f.jpegData, f.jpegData + f.jpegSize);
});
// 位姿/IMU:直接按值存
device->subscribePose([](const xg::Pose& p) {
xg::Pose saved = p; // 纯标量,安全
});
调用方分配型(同步查询 / 入参)
Options、RecordStatus、各 *Calib、DeviceInfo、InitPose。
- 内存由调用方分配,字符串是定长内嵌数组,SDK 只写内容不接管内存。
- 无需、也不允许对这些结构体调用
free/delete。 - C++ 封装已把定长字段转成
std::string,由 C++ 对象自行管理。
录制控制
// 下发开始
if (dev->startRecord() == xg::Error::Timeout) {
// 命令可能已送达,先确认实际状态再决定是否重发
xg::RecordStatus st;
if (dev->queryRecordStatus(&st) == xg::Error::Ok && st.state != xg::RecordState::Idle) {
// 已在准备或已开始,转入正常轮询即可
}
}
// 轮询确认
xg::RecordStatus st;
while (true) {
std::this_thread::sleep_for(
std::chrono::milliseconds(XG_RECOMMENDED_POLL_INTERVAL_MS));
if (dev->queryRecordStatus(&st) != xg::Error::Ok) continue; // 抖动重试
if (st.state == xg::RecordState::Recording) break;
if (st.state == xg::RecordState::Error) break;
}
Timeout 表示没收到设备应答,命令是否送达无从判断,不要直接重试,应先查询确认。语义详见核心概念。
标定与设备状态查询
xg::Device::CameraCalib cam;
if (device->getCameraCalib(0, &cam) == xg::Error::Ok) { // 0..3 标定索引
std::printf("fx=%.2f fy=%.2f model=%s\n", cam.fx, cam.fy, cam.model.c_str());
}
xg::Device::DeviceInfo info;
if (device->getDeviceInfo(&info) == xg::Error::Ok) {
std::printf("battery=%.1f%% disk=%ld/%ld KB\n",
info.batteryLevel, info.diskUsedKb, info.diskTotalKb);
}
标定/状态查询可在未录制时调用(读设备文件)。逐字段见数据类型。
C 接口
需要 C 接口或封装到其他语言时,直接使用 xg/robot_sdk_c.h。C++ 接口是它的 header-only 封装,两者能力完全一致。
#include "xg/robot_sdk_c.h"
#include <string.h>
xg_options_t opts;
memset(&opts, 0, sizeof(opts));
opts.struct_size = sizeof(opts); /* 必填 */
snprintf(opts.device_ip, sizeof(opts.device_ip), "192.168.123.103");
xg_device_t* dev = NULL;
if (xg_device_open(&opts, &dev) != XG_OK) {
return 1;
}
if (xg_record_start(dev) != XG_OK) { /* 只下发, 不等待 */
xg_device_close(dev);
return 1;
}
/* 轮询确认 */
xg_record_status_t st;
for (;;) {
usleep(XG_RECOMMENDED_POLL_INTERVAL_MS * 1000);
memset(&st, 0, sizeof(st));
st.struct_size = sizeof(st); /* 每次调用前必填 */
if (xg_record_query(dev, &st) != XG_OK) continue;
if (st.state == XG_REC_RECORDING) break;
if (st.state == XG_REC_ERROR) break;
}
xg_device_close(dev);
注:所有由调用方分配的结构体都必须先填
struct_size = sizeof(...),否则接口返回XG_ERR_INVALID_ARG。这个字段用于后续版本新增字段时保持二进制兼容。C++ 封装已代为处理。
安全须知
本版本不含身份认证,设备控制接口对同网段完全开放。 同一交换机上的任何主机都能连接设备并控制录制。
因此:
- 设备不得接入不可信网络(公共网络、访客网络、直连互联网);
- 建议设备与调用方主机使用独立交换机或专用 VLAN,与办公网隔离。
详见部署与网络。