第一部分 RDK X3 Framework 库 CMake 构建与函数框架分析

一、CMake 构建系统分析

1.1 顶层 CMakeLists.txt 结构

# ============================================================================
# 地平线 RDK X3 Framework 顶层 CMake 配置
# ============================================================================
​
cmake_minimum_required(VERSION 3.10)
project(rdk_x3_framework VERSION 1.0.0 LANGUAGES C CXX)
​
# 1. 设置编译选项
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
​
# 2. 平台检测
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
    set(PLATFORM_X3 TRUE)
    message(STATUS "Building for RDK X3 (AArch64)")
endif()
​
# 3. 编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -O2 -fPIC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -O2 -fPIC")
​
# 4. 调试模式
option(BUILD_DEBUG "Build with debug symbols" OFF)
if(BUILD_DEBUG)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -DDEBUG")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -DDEBUG")
endif()
​
# 5. 输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
​
# ============================================================================
# 子模块(Framework 库)
# ============================================================================
​
# 核心库
add_subdirectory(common)          # 公共基础库
add_subdirectory(logger)          # 日志库
add_subdirectory(thread_pool)     # 线程池
​
# 硬件抽象层(HAL)
add_subdirectory(hobot_ips)       # IPS 驱动封装
add_subdirectory(hobot_vio)       # 视频输入输出
​
# 媒体处理
add_subdirectory(hobot_spdev)     # 媒体处理 SDK
add_subdirectory(hobot_codec)     # 编解码库
add_subdirectory(hobot_sensor)    # 传感器库
​
# AI 推理
add_subdirectory(hobot_dnn)       # DNN 推理框架
add_subdirectory(hobot_ai)        # AI 算法封装
​
# 中间件
add_subdirectory(tros)            # TogetheROS.Bot 集成
​
# 示例程序
add_subdirectory(samples)         # 示例代码

1.2 hobot_spdev 库的 CMakeLists.txt

# ============================================================================
# hobot_spdev - 媒体处理 SDK CMake 配置
# ============================================================================
​
project(hobot_spdev VERSION 1.0.0)
​
# 1. 头文件路径
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/include/hobot_spdev
    ${CMAKE_SOURCE_DIR}/common/include
    ${CMAKE_SOURCE_DIR}/hobot_ips/include
    ${CMAKE_SOURCE_DIR}/hobot_vio/include
)
​
# 2. 依赖库
find_package(OpenCV REQUIRED)
find_package(PkgConfig REQUIRED)
​
# 3. 源文件列表
set(SOURCES
    src/vio_api.cpp
    src/vio_pipeline.cpp
    src/vio_camera.cpp
    src/vio_display.cpp
    src/vio_encoder.cpp
    src/vio_decoder.cpp
    src/vio_isp.cpp
    src/vio_utils.cpp
)
​
# 4. 生成动态库
add_library(hobot_spdev SHARED ${SOURCES})
​
# 5. 链接依赖库
target_link_libraries(hobot_spdev
    ${OpenCV_LIBS}
    hobot_ips
    hobot_vio
    hobot_codec
    hobot_sensor
    pthread
    dl
    stdc++
)
​
# 6. 设置链接器选项
set_target_properties(hobot_spdev PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
    LINK_FLAGS "-Wl,-rpath,/usr/lib/hobot"
)
​
# 7. 安装配置
install(TARGETS hobot_spdev
    LIBRARY DESTINATION /usr/lib/hobot
    ARCHIVE DESTINATION /usr/lib/hobot
)
​
install(DIRECTORY include/ DESTINATION /usr/include/hobot_spdev)
​
# 8. 导出 cmake 配置
configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/hobot_spdev-config.cmake.in
    ${CMAKE_BINARY_DIR}/hobot_spdev-config.cmake
    @ONLY
)
​
install(FILES
    ${CMAKE_BINARY_DIR}/hobot_spdev-config.cmake
    DESTINATION /usr/lib/cmake/hobot_spdev
)

1.3 编译生成的文件结构

# CMake 构建输出目录结构
build/
├── bin/
│   ├── vio_sample                 # 示例可执行文件
│   ├── dnn_sample
│   └── camera_test
│
├── lib/
│   ├── libhobot_spdev.so          # 媒体处理 SDK (动态库)
│   ├── libhobot_spdev.so.1        # 符号链接
│   ├── libhobot_spdev.so.1.0.0    # 实际库文件
│   ├── libhobot_dnn.so            # DNN 推理库
│   ├── libhobot_codec.so          # 编解码库
│   ├── libhobot_sensor.so         # 传感器库
│   ├── libhobot_ips.so            # IPS 驱动封装
│   ├── libhobot_vio.so            # VIO 封装
│   ├── libhobot_common.so         # 公共基础库
│   └── libhobot_logger.so         # 日志库
│
├── include/                       # 导出的头文件
│   ├── hobot_spdev/
│   │   ├── vio_api.h
│   │   ├── vio_pipeline.h
│   │   ├── vio_camera.h
│   │   └── vio_display.h
│   ├── hobot_dnn/
│   │   ├── dnn_handle.h
│   │   ├── dnn_task.h
│   │   └── dnn_tensor.h
│   └── ...
│
└── CMakeFiles/
    └── ... (编译中间文件)

二、核心 Framework 库函数框架

2.1 hobot_spdev (媒体处理 SDK)

/**
 * @file vio_api.h
 * @brief 视频输入输出 API
 * @note 这是最核心的 Framework 库,封装了摄像头、ISP、显示、编码等
 */
​
#ifndef VIO_API_H
#define VIO_API_H
​
#include <stdint.h>
#include <stdbool.h>
​
#ifdef __cplusplus
extern "C" {
#endif
​
/*=============================================================================
 * 1. 初始化与反初始化
 *============================================================================*/
​
/**
 * @brief VIO 系统初始化
 * @param config_file 配置文件路径 (JSON/YAML)
 * @return 0: 成功, <0: 失败
 */
int vio_init(const char* config_file);
​
/**
 * @brief VIO 系统反初始化
 */
void vio_deinit(void);
​
/*=============================================================================
 * 2. 摄像头控制
 *============================================================================*/
​
/**
 * @brief 打开摄像头
 * @param cam_id 摄像头 ID (0/1/2/3)
 * @param width 采集宽度
 * @param height 采集高度
 * @param fps 帧率
 * @return 0: 成功, <0: 失败
 */
int vio_camera_open(int cam_id, int width, int height, int fps);
​
/**
 * @brief 关闭摄像头
 * @param cam_id 摄像头 ID
 */
void vio_camera_close(int cam_id);
​
/**
 * @brief 获取一帧图像
 * @param cam_id 摄像头 ID
 * @param out_frame 输出帧结构
 * @param timeout_ms 超时时间 (毫秒)
 * @return 0: 成功, <0: 失败
 */
int vio_camera_get_frame(int cam_id, vio_frame_t* out_frame, int timeout_ms);
​
/**
 * @brief 释放帧
 * @param cam_id 摄像头 ID
 * @param frame 帧结构
 */
void vio_camera_put_frame(int cam_id, vio_frame_t* frame);
​
/*=============================================================================
 * 3. ISP 控制
 *============================================================================*/
​
/**
 * @brief 设置 ISP 参数
 * @param cam_id 摄像头 ID
 * @param param_id 参数 ID
 * @param value 参数值
 * @return 0: 成功, <0: 失败
 */
int vio_isp_set_param(int cam_id, int param_id, int value);
​
/**
 * @brief 设置亮度
 */
int vio_isp_set_brightness(int cam_id, int brightness);
​
/**
 * @brief 设置对比度
 */
int vio_isp_set_contrast(int cam_id, int contrast);
​
/**
 * @brief 设置饱和度
 */
int vio_isp_set_saturation(int cam_id, int saturation);
​
/**
 * @brief 设置白平衡模式
 * @param cam_id 摄像头 ID
 * @param mode 0=自动, 1=日光, 2=阴天, 3=白炽灯
 */
int vio_isp_set_wb_mode(int cam_id, int mode);
​
/*=============================================================================
 * 4. 显示控制
 *============================================================================*/
​
/**
 * @brief 初始化显示
 * @param display_type 显示类型 (HDMI/MIPI/Web)
 * @return 0: 成功, <0: 失败
 */
int vio_display_init(int display_type);
​
/**
 * @brief 显示一帧图像
 * @param frame 帧结构
 * @return 0: 成功, <0: 失败
 */
int vio_display_show(vio_frame_t* frame);
​
/**
 * @brief 显示叠加 OSD
 * @param text 文本内容
 * @param x X 坐标
 * @param y Y 坐标
 */
int vio_display_osd(const char* text, int x, int y);
​
/*=============================================================================
 * 5. 编码控制
 *============================================================================*/
​
/**
 * @brief 开始编码
 * @param codec_type 编码类型 (H264/H265)
 * @param width 宽度
 * @param height 高度
 * @param bitrate 码率 (bps)
 * @return 编码器句柄
 */
void* vio_encoder_start(int codec_type, int width, int height, int bitrate);
​
/**
 * @brief 编码一帧
 * @param handle 编码器句柄
 * @param frame 输入帧
 * @param out_buf 输出缓冲区
 * @param out_size 输出大小
 * @return 0: 成功, <0: 失败
 */
int vio_encoder_encode(void* handle, vio_frame_t* frame, 
                        uint8_t* out_buf, int* out_size);
​
/**
 * @brief 停止编码
 * @param handle 编码器句柄
 */
void vio_encoder_stop(void* handle);
​
/*=============================================================================
 * 6. 数据结构定义
 *============================================================================*/
​
/**
 * @brief VIO 帧结构
 */
typedef struct vio_frame {
    void* data[3];          /**< 图像数据指针 (Y/UV 或 RGB) */
    int width;              /**< 宽度 */
    int height;             /**< 高度 */
    int stride[3];          /**< 行跨度 */
    int format;             /**< 像素格式 (NV12/RGB888) */
    uint64_t timestamp;     /**< 时间戳 (微秒) */
    int sequence;           /**< 帧序号 */
    int fd;                 /**< DMA-BUF 文件描述符 (零拷贝) */
} vio_frame_t;
​
#ifdef __cplusplus
}
#endif
​
#endif /* VIO_API_H */

2.2 hobot_dnn (AI 推理框架)

/**
 * @file dnn_handle.h
 * @brief DNN 推理框架
 */
​
#ifndef DNN_HANDLE_H
#define DNN_HANDLE_H
​
#include <stdint.h>
#include <stdbool.h>
​
#ifdef __cplusplus
extern "C" {
#endif
​
/*=============================================================================
 * 1. 模型管理
 *============================================================================*/
​
/**
 * @brief 加载模型
 * @param model_path 模型文件路径 (.bin)
 * @return 模型句柄
 */
void* dnn_model_load(const char* model_path);
​
/**
 * @brief 卸载模型
 * @param handle 模型句柄
 */
void dnn_model_unload(void* handle);
​
/*=============================================================================
 * 2. 推理执行
 *============================================================================*/
​
/**
 * @brief 创建推理任务
 * @param model_handle 模型句柄
 * @return 任务句柄
 */
void* dnn_task_create(void* model_handle);
​
/**
 * @brief 设置输入张量
 * @param task_handle 任务句柄
 * @param index 输入索引
 * @param data 数据指针
 * @param size 数据大小
 * @return 0: 成功, <0: 失败
 */
int dnn_task_set_input(void* task_handle, int index, void* data, int size);
​
/**
 * @brief 执行推理(同步)
 * @param task_handle 任务句柄
 * @return 0: 成功, <0: 失败
 */
int dnn_task_run(void* task_handle);
​
/**
 * @brief 获取输出张量
 * @param task_handle 任务句柄
 * @param index 输出索引
 * @param out_data 输出数据指针
 * @param out_size 输出数据大小
 * @return 0: 成功, <0: 失败
 */
int dnn_task_get_output(void* task_handle, int index, void** out_data, int* out_size);
​
/**
 * @brief 销毁推理任务
 * @param task_handle 任务句柄
 */
void dnn_task_destroy(void* task_handle);
​
/*=============================================================================
 * 3. 异步推理
 *============================================================================*/
​
/**
 * @brief 异步推理回调
 */
typedef void (*dnn_callback_t)(void* task_handle, void* user_data);
​
/**
 * @brief 异步执行推理
 * @param task_handle 任务句柄
 * @param callback 完成回调
 * @param user_data 用户数据
 * @return 0: 成功, <0: 失败
 */
int dnn_task_run_async(void* task_handle, dnn_callback_t callback, void* user_data);
​
/**
 * @brief 等待异步任务完成
 * @param task_handle 任务句柄
 * @param timeout_ms 超时时间
 * @return 0: 成功, <0: 失败
 */
int dnn_task_wait(void* task_handle, int timeout_ms);
​
#ifdef __cplusplus
}
#endif
​
#endif /* DNN_HANDLE_H */

2.3 tros (TogetheROS.Bot 中间件)

/**
 * @file node.hpp
 * @brief ROS 2 节点封装
 */
​
#ifndef TROS_NODE_HPP
#define TROS_NODE_HPP
​
#include <memory>
#include <string>
#include <functional>
​
namespace tros {
​
/*=============================================================================
 * 1. 节点类
 *============================================================================*/
​
class Node {
public:
    /**
     * @brief 创建节点
     * @param node_name 节点名称
     */
    explicit Node(const std::string& node_name);
    
    ~Node();
​
    /**
     * @brief 创建发布者
     * @param topic_name 主题名称
     * @param queue_size 队列大小
     */
    template<typename T>
    auto create_publisher(const std::string& topic_name, int queue_size);
​
    /**
     * @brief 创建订阅者
     * @param topic_name 主题名称
     * @param queue_size 队列大小
     * @param callback 回调函数
     */
    template<typename T>
    auto create_subscription(const std::string& topic_name, int queue_size,
                              std::function<void(const T&)> callback);
​
    /**
     * @brief 创建零拷贝发布者(共享内存)
     * @param topic_name 主题名称
     */
    template<typename T>
    auto create_zero_copy_publisher(const std::string& topic_name);
​
    /**
     * @brief 获取节点名称
     */
    std::string get_name() const;
​
private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};
​
/*=============================================================================
 * 2. 图像消息类型(用于零拷贝)
 *============================================================================*/
​
struct ImageMsg {
    uint32_t width;
    uint32_t height;
    uint32_t step;
    uint32_t encoding;      // NV12, RGB8, etc.
    uint64_t timestamp;
    uint32_t data_fd;       // DMA-BUF 文件描述符
    uint32_t data_size;
};
​
/*=============================================================================
 * 3. 初始化
 *============================================================================*/
​
/**
 * @brief 初始化 tros
 * @param argc 命令行参数个数
 * @param argv 命令行参数
 */
void init(int argc, char** argv);
​
/**
 * @brief 执行事件循环
 */
void spin(std::shared_ptr<Node> node);
​
/**
 * @brief 关闭
 */
void shutdown();
​
}  // namespace tros
​
#endif /* TROS_NODE_HPP */

三、应用开发示例

3.1 使用 Framework 库的完整示例

/**
 * @file camera_app.cpp
 * @brief 使用 Framework 库的完整相机应用
 * @note 编译时需要链接: hobot_spdev, hobot_dnn, tros
 */
​
#include <iostream>
#include <memory>
#include "vio_api.h"
#include "dnn_handle.h"
#include "node.hpp"
​
// 全局 DNN 模型句柄
static void* g_model_handle = nullptr;
​
// 推理完成回调
void inference_callback(void* task_handle, void* user_data) {
    void* output_data = nullptr;
    int output_size = 0;
    
    // 获取推理结果
    dnn_task_get_output(task_handle, 0, &output_data, &output_size);
    
    // 后处理
    float* detections = (float*)output_data;
    int num_detections = output_size / sizeof(float) / 6;
    
    std::cout << "Detected " << num_detections << " objects" << std::endl;
    
    // 销毁任务
    dnn_task_destroy(task_handle);
}
​
int main(int argc, char** argv) {
    // 1. 初始化 tros
    tros::init(argc, argv);
    auto node = std::make_shared<tros::Node>("camera_node");
    
    // 2. 创建零拷贝发布者
    auto publisher = node->create_zero_copy_publisher<tros::ImageMsg>("camera_image");
    
    // 3. 初始化 VIO
    if (vio_init("/etc/vio_config.json") != 0) {
        std::cerr << "VIO init failed" << std::endl;
        return -1;
    }
    
    // 4. 打开摄像头
    if (vio_camera_open(0, 1920, 1080, 30) != 0) {
        std::cerr << "Camera open failed" << std::endl;
        return -1;
    }
    
    // 5. 加载 AI 模型
    g_model_handle = dnn_model_load("/data/model/yolov5s.bin");
    if (!g_model_handle) {
        std::cerr << "Model load failed" << std::endl;
        return -1;
    }
    
    // 6. 设置 ISP 参数
    vio_isp_set_brightness(0, 0);   // 默认亮度
    vio_isp_set_contrast(0, 0);     // 默认对比度
    vio_isp_set_wb_mode(0, 0);      // 自动白平衡
    
    // 7. 主循环
    vio_frame_t frame;
    while (true) {
        // 获取一帧
        if (vio_camera_get_frame(0, &frame, 1000) != 0) {
            continue;
        }
        
        // 创建推理任务
        void* task = dnn_task_create(g_model_handle);
        
        // 设置输入(零拷贝,直接使用 DMA-BUF fd)
        dnn_task_set_input(task, 0, (void*)(intptr_t)frame.fd, frame.width * frame.height * 1.5);
        
        // 异步推理
        dnn_task_run_async(task, inference_callback, nullptr);
        
        // 零拷贝发布图像消息
        tros::ImageMsg msg;
        msg.width = frame.width;
        msg.height = frame.height;
        msg.data_fd = frame.fd;
        msg.data_size = frame.width * frame.height * 1.5;
        msg.timestamp = frame.timestamp;
        publisher->publish(msg);
        
        // 释放帧
        vio_camera_put_frame(0, &frame);
    }
    
    // 8. 清理
    vio_camera_close(0);
    vio_deinit();
    dnn_model_unload(g_model_handle);
    tros::shutdown();
    
    return 0;
}

3.2 CMakeLists.txt(应用项目)

cmake_minimum_required(VERSION 3.10)
project(camera_app)
​
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 14)
​
# 查找 Framework 库
find_package(hobot_spdev REQUIRED)
find_package(hobot_dnn REQUIRED)
find_package(tros REQUIRED)
​
# 包含头文件
include_directories(
    ${HOBOT_SPDEV_INCLUDE_DIRS}
    ${HOBOT_DNN_INCLUDE_DIRS}
    ${TROS_INCLUDE_DIRS}
)
​
# 生成可执行文件
add_executable(camera_app camera_app.cpp)
​
# 链接 Framework 库
target_link_libraries(camera_app
    ${HOBOT_SPDEV_LIBRARIES}
    ${HOBOT_DNN_LIBRARIES}
    ${TROS_LIBRARIES}
    pthread
    dl
)
​
# 安装
install(TARGETS camera_app DESTINATION bin)

四、Framework 库总结

库名 生成文件 核心功能 使用场景
libhobot_spdev.so 约 2-3 MB 摄像头、ISP、显示、编码 媒体处理主框架
libhobot_dnn.so 约 1-2 MB AI 模型加载、推理 目标检测、分类
libhobot_codec.so 约 1 MB H.264/H.265 编解码 视频存储、推流
libhobot_sensor.so 约 0.5 MB 传感器抽象 摄像头配置
libhobot_common.so 约 0.5 MB 日志、线程池、工具 公共基础
tros (静态库) 约 10-20 MB ROS 2 节点通信 分布式/复杂系统

第二部分 调试地平线

一、Framework集成会遇到的主要问题

1.1 编译与依赖问题树形图

Framework集成问题分类
│
├── 1. 交叉编译环境配置问题
│   ├── 工具链路径配置错误
│   │   └── 解决: 使用build.sh脚本自动检测
│   ├── 头文件路径缺失
│   │   └── 需从开发板拷贝/usr/include
│   ├── 库文件版本不匹配
│   │   └── 需匹配AppSDK版本
│   └── ffmpeg依赖问题
│       ├── 缺少avcodec/avformat/avutil头文件
│       └── 需手动创建avconfig.h
│
├── 2. API调用与适配问题
│   ├── 模型结构与后处理不匹配
│   │   ├── YOLO需3层输出,但ONNX模型只有1层
│   │   └── 解决: 修改模型导出方式或修改后处理代码
│   ├── 输入尺寸不匹配
│   │   ├── 模型期望672x672,实际输入1280x1280
│   │   └── 解决: 修改代码中的输入尺寸配置
│   └── 编码格式问题
│       └── 模型需要RGB,但VIO输出NV12
│
├── 3. 运行时问题
│   ├── 内存不足/进程被Kill
│   │   └── 解决: 增加swap或优化内存使用
│   ├── 摄像头无法识别
│   │   └── 检查MIPI连接和设备树配置
│   ├── 推理帧率低
│   │   └── CPU占用过高,需优化模型或使用BPU
│   └── 远程连接断开
│       └── 网络不稳定,改用VNC或SSH
│
└── 4. 部署与调试问题
    ├── SSH无法使用root登录
    │   └── 使用sunrise用户,需要时su提权
    ├── VNC连接超时
    │   └── 改用向日葵或TeamViewer
    └── 串口配置问题
        └── 波特率921600,关闭RTS/CTS

1.2 典型问题案例

问题1:hobot-spdev交叉编译ffmpeg依赖缺失
# 错误现象
fatal error: libavcodec/avcodec.h: No such file or directory
​
# 解决方案
# 1. 从开发板拷贝头文件
scp -r root@192.168.x.x:/usr/include/libav* /test/sysroot/usr/include/
​
# 2. 或手动创建avconfig.h
cat > /test/sysroot/usr/include/libavutil/avconfig.h << EOF
#ifndef AVUTIL_AVCONFIG_H
#define AVUTIL_AVCONFIG_H
#define AV_HAVE_BIGENDIAN 0
#define AV_HAVE_FAST_UNALIGNED 0
#endif
EOF
问题2:模型输出结构与后处理不匹配
# 问题:YOLO模型导出后只有1层输出,但hobot-spdev需要3层
# 解决方案:修改模型导出脚本,保持3个检测头
​
# 查看模型结构
netron test_model.onnx
​
# 正确导出方式(保留3个输出层)
torch.onnx.export(model, dummy_input, "model.onnx",
                  output_names=["output1", "output2", "output3"])

二、代码跟踪工具与方法

2.1 调试工具全景图

┌─────────────────────────────────────────────────────────────────────────────┐
│                          代码跟踪工具体系                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 系统层调试                                                              │
│  ├── ssh: 远程登录开发板                                        │
│  │   └── ssh sunrise@192.168.x.x                                           │
│  ├── scp: 文件传输                                                          │
│  │   └── scp ./app sunrise@192.168.x.x:/home/sunrise/                      │
│  ├── 串口: 查看内核启动日志                                     │
│  │   └── 波特率921600,关闭RTS/CTS                                          │
│  └── VNC/向日葵: 远程桌面调试                                   │
│                                                                             │
│  2. 编译调试                                                                │
│  ├── gdb: 板端调试                                                          │
│  ├── gdbserver: 远程调试                                                    │
│  ├── strace: 系统调用跟踪                                                   │
│  └── valgrind: 内存检测                                                     │
│                                                                             │
│  3. Framework层跟踪                                                         │
│  ├── 日志输出: 修改源码添加printf                                           │
│  ├── rqt_graph: 查看ROS节点拓扑                                 │
│  ├── rqt_console: 查看ROS日志                                               │
│  └── topic echo: 查看消息流                                                 │
│                                                                             │
│  4. 性能分析                                                                │
│  ├── top/htop: CPU/内存监控                                     │
│  ├── perf: 性能采样                                                         │
│  └── cat /sys/class/thermal/thermal_zone*/temp: 温度监控                    │
│                                                                             │
│  5. 模型调试                                                                │
│  ├── Netron: 查看模型结构                                       │
│  ├── hb_model_tool: 地平线模型验证                                          │
│  └── 校准数据集: 减少量化精度损失                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 关键调试命令

# 1. 查看系统状态
cat /proc/cpuinfo                    # CPU信息
cat /proc/meminfo                    # 内存信息
df -h                                # 磁盘空间
cat /sys/class/thermal/thermal_zone*/temp  # 温度
​
# 2. 查看进程
top -H                               # 查看线程
ps aux | grep camera                 # 查找进程
​
# 3. 查看摄像头
v4l2-ctl --list-devices              # 列出V4L2设备
media-ctl -p -d /dev/media0          # 查看media拓扑
​
# 4. 查看ROS2状态
source /opt/tros/humble/setup.bash
rqt_graph                            # 节点图
ros2 topic list                      # 主题列表
ros2 topic echo /camera_image        # 查看消息
​
# 5. 调试模型
hb_model_tool --model-file model.bin --list-tensors  # 查看模型输入输出

三、中间件关键函数分析

3.1 hobot-spdev 关键函数树

// 基于代码分析的关键函数
​
hobot-spdev 核心API
│
├── 初始化与配置
│   ├── vio_init()           // 初始化VIO系统
│   ├── vio_deinit()         // 反初始化
│   └── vio_set_config()     // 配置参数
│
├── 摄像头采集
│   ├── vio_camera_open()    // 打开摄像头
│   ├── vio_camera_close()   // 关闭
│   ├── vio_camera_get_frame() // 获取帧
│   └── vio_camera_put_frame() // 释放帧
│
├── ISP控制
│   ├── vio_isp_set_brightness()  // 亮度
│   ├── vio_isp_set_contrast()    // 对比度
│   └── vio_isp_set_wb_mode()     // 白平衡
│
├── 编解码
│   ├── vio_encoder_start()  // 启动编码
│   ├── vio_encoder_encode() // 编码一帧
│   └── vio_encoder_stop()   // 停止编码
│
└── 显示
    ├── vio_display_init()   // 初始化显示
    ├── vio_display_show()   // 显示帧
    └── vio_display_osd()    // OSD叠加

3.2 内部HAL操作过程

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Framework到HAL的调用链                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  应用层调用                                                                  │
│  vio_camera_get_frame()                                                     │
│       │                                                                     │
│       ▼                                                                     │
│  hobot-spdev (Framework)                                                    │
│  ├── 参数校验                                                               │
│  ├── 从缓冲区队列取帧                                                        │
│  └── 调用底层API                                                             │
│       │                                                                     │
│       ▼                                                                     │
│  hobot_vio / hobot_ips (HAL封装)                                            │
│  ├── ioctl(fd, VIDIOC_DQBUF, &buf)  // V4L2操作                             │
│  ├── 获取DMA-BUF fd                                                         │
│  └── 缓存同步 dma_buf_sync()                                                │
│       │                                                                     │
│       ▼                                                                     │
│  内核驱动 (hobot_dev_ips.c)                                                 │
│  ├── ips_isr() 中断处理                                                     │
│  ├── 寄存器操作 ips_hw_set_reg()                                            │
│  └── DMA数据传输                                                             │
│       │                                                                     │
│       ▼                                                                     │
│  硬件层 (ISP/VIN)                                                            │
│  ├── MIPI接收                                                                │
│  ├── ISP处理                                                                 │
│  └── DMA写入DDR                                                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

四、常见难点问题与解决思路

4.1 模型部署难点

难点 现象 排查思路 解决方案
模型转换失败 pt/onnx转bin报错 检查算子兼容性 使用Netron查看模型结构,替换不支持的算子
量化精度下降 检测率明显降低 检查校准数据集 增加校准图片数量(>100张),覆盖各种场景
推理速度慢 FPS低于预期 确认是否使用BPU 检查模型是否正确部署到BPU,而非CPU
内存不足 进程被kill free -h查看内存 增加swap或优化模型大小

4.2 摄像头调试难点

难点 现象 排查思路 解决方案
无图像输出 /dev/video0存在但无数据 检查MIPI连接 重新插拔排线,检查设备树配置
图像花屏 画面有彩色条纹 MIPI信号问题 检查排线是否松动,降低帧率测试
ISP参数不生效 调整亮度无变化 检查参数范围 确认参数值在有效范围内(0-100)
帧率不达标 实际帧率低于设定值 top查看CPU占用 减少推理负载,使用硬件编码

4.3 远程调试连接问题

问题 解决方案
SSH无法连接 检查网络配置,确保静态IP已设置
root无法SSH登录 使用sunrise用户登录,需要时su提权
VNC超时 改用向日葵软件远程连接
串口无输出 检查波特率(921600),关闭RTS/CTS

五、总结:最佳实践建议

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Framework集成最佳实践                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 开发环境搭建                                                             │
│  ├── 使用官方SDK和文档                                           │
│  ├── 配置静态IP避免地址变化                                       │
│  └── 准备SD卡作为备用启动介质                                     │
│                                                                             │
│  2. 模型部署流程                                                             │
│  ├── pt/onnx → 校准 → 量化 → bin                                │
│  ├── 使用Netron检查模型结构                                       │
│  └── 先用示例模型验证环境,再替换自己的模型                                    │
│                                                                             │
│  3. 调试策略                                                                 │
│  ├── 模块化调试: 摄像头→编码→推理→显示                                        │
│  ├── 利用官方示例代码作为起点                                     │
│  ├── 加入详细日志输出                                                        │
│  └── 使用rqt工具可视化ROS数据                                    │
│                                                                             │
│  4. 性能优化                                                                 │
│  ├── 确保模型运行在BPU而非CPU                                    │
│  ├── 使用硬件编码器减少CPU负载                                               │
│  ├── 合理设置缓冲区数量(4-6个)                                               │
│  └── 监控CPU/内存/温度                                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

第三部分 从 HAL 到应用全生命周期

一、C++ 文件扩展名差异分析

1.1 .cpp vs .cc 对比表

维度 .cpp .cc
起源 C++ 标准扩展名 Unix 传统扩展名
编译器 g++/clang++ g++/clang++
功能差异 完全相同,只是命名习惯不同 完全相同
主流使用 Windows、跨平台 Google、Chromium、Unreal Engine
代码风格 无差异 无差异

1.2 场景选择建议

┌─────────────────────────────────────────────────────────────────────────────┐
│                     .cpp vs .cc 选择建议                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  选择 .cpp 的场景 (推荐 80% 情况)                                            │
│  ├── 通用跨平台项目                                                          │
│  ├── Windows + Linux 双平台开发                                             │
│  ├── 团队没有特殊偏好                                                        │
│  └── 示例: 地平线 SDK 主要使用 .cpp                                         │
│                                                                             │
│  选择 .cc 的场景 (推荐 20% 情况)                                             │
│  ├── Google 风格项目 (如 Chromium)                                          │
│  ├── Unreal Engine 插件开发                                                  │
│  ├── 大型 Unix/Linux 项目                                                   │
│  └── 团队已有 .cc 代码规范                                                   │
│                                                                             │
│  结论: 功能完全相同,推荐统一使用 .cpp,更通用                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

二、Framework 库与 HAL 驱动的关系

2.1 架构层次图(修正版)

┌─────────────────────────────────────────────────────────────────────────────┐
│                     RDK X3 完整软件架构                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  应用层 (test代码)                                                           │
│  ├── main.cpp                                                               │
│  ├── CameraService.cpp     ← test服务                                      │
│  └── AIService.cpp                                                          │
│                    ↓ 调用                                                    │
│                                                                             │
│  Framework 层 (第三方库)                                                     │
│  ├── libhobot_spdev.so     ← 媒体处理 SDK                                    │
│  ├── libhobot_dnn.so       ← AI 推理                                        │
│  ├── libhobot_codec.so     ← 编解码                                         │
│  └── libhobot_common.so    ← 基础库                                         │
│                    ↓ 调用                                                    │
│                                                                             │
│  HAL 层 (isp驱动)                                                     │
│  ├── hobot_dev_ips.c       ← IPS 字符设备驱动                                │
│  ├── hobot_ips_hw_reg.c    ← 寄存器操作                                     │
│  └── vio_group_api.h       ← VIO 组 API                                     │
│                    ↓ ioctl/mmap                                              │
│                                                                             │
│  硬件层                                                                      │
│  ├── ISP                                                                   │
│  ├── VIN (视频输入)                                                          │
│  └── BPU (AI 核心)                                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 Framework 库内部操作 HAL 的流程

// libhobot_spdev.so 内部如何操作 HAL 驱动
​
// 1. 打开设备 (对应 hobot_dev_ips.c 的 x3_ips_probe)
int vio_init() {
    // 打开字符设备
    int fd = open("/dev/ips", O_RDWR);  // ← 触发内核 probe
    // 映射寄存器
    void* regs = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    return 0;
}
​
// 2. 配置 ISP (对应 ips_set_md_cfg)
int vio_isp_set_brightness(int value) {
    // 通过 ioctl 调用内核驱动
    struct ips_ctrl cmd = {
        .id = IPS_SET_BRIGHTNESS,
        .value = value
    };
    return ioctl(fd, IPS_IOCTL_SET_CTRL, &cmd);  // ← 触发 ips_isr 或 ioctl 处理
}
​
// 3. 获取帧 (对应 V4L2 DQBUF)
int vio_camera_get_frame(vio_frame_t* frame) {
    // 通过 V4L2 接口获取
    struct v4l2_buffer buf;
    ioctl(video_fd, VIDIOC_DQBUF, &buf);  // ← 阻塞等待帧
    frame->fd = dma_buf_fds[buf.index];   // ← 获取 DMA-BUF
    return 0;
}

三、应用层服务模块设计

3.1 服务模块架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                      应用层服务架构                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                        main.cpp (主入口)                              │   │
│  │  ├── 信号处理 (SIGINT/SIGTERM)                                      │   │
│  │  ├── 服务生命周期管理                                                │   │
│  │  └── 配置加载                                                        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│          ┌─────────────────────────┼─────────────────────────┐              │
│          ▼                         ▼                         ▼              │
│  ┌───────────────┐       ┌───────────────┐       ┌───────────────┐         │
│  │ CameraService │       │  AIService    │       │  MediaService │         │
│  │ (摄像头服务)   │       │  (AI推理服务)  │       │  (媒体服务)    │         │
│  ├───────────────┤       ├───────────────┤       ├───────────────┤         │
│  │ 采集线程      │       │ 推理线程      │       │ 编码线程      │         │
│  │ ISP控制       │       │ 模型加载      │       │ 推流线程      │         │
│  │ 帧分发        │       │ 后处理        │       │ 存储线程      │         │
│  └───────────────┘       └───────────────┘       └───────────────┘         │
│          │                         │                         │              │
│          └─────────────────────────┼─────────────────────────┘              │
│                                    ▼                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                      EventBus (事件总线)                              │   │
│  │  ├── 观察者模式                                                     │   │
│  │  ├── 异步事件分发                                                   │   │
│  │  └── 线程安全队列                                                   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 核心服务实现

/**
 * @file CameraService.h
 * @brief 摄像头服务 - 封装 Framework 层 API
 */
​
#ifndef CAMERA_SERVICE_H
#define CAMERA_SERVICE_H
​
#include <memory>
#include <thread>
#include <atomic>
#include <functional>
#include "vio_api.h"      // ← Framework 层 API
​
class CameraService {
public:
    using FrameCallback = std::function<void(const vio_frame_t*)>;
    
    /**
     * @brief 获取单例实例
     */
    static CameraService& getInstance() {
        static CameraService instance;
        return instance;
    }
    
    /**
     * @brief 初始化摄像头服务
     * @param config_file 配置文件路径
     * @return 成功返回0
     */
    int init(const char* config_file) {
        // 1. 初始化 VIO 框架 (调用 libhobot_spdev.so)
        int ret = vio_init(config_file);
        if (ret != 0) {
            return -1;
        }
        
        // 2. 打开摄像头 (调用 Framework API)
        ret = vio_camera_open(m_cam_id, m_width, m_height, m_fps);
        if (ret != 0) {
            vio_deinit();
            return -2;
        }
        
        // 3. 设置默认 ISP 参数
        vio_isp_set_brightness(m_cam_id, 0);
        vio_isp_set_contrast(m_cam_id, 0);
        vio_isp_set_saturation(m_cam_id, 0);
        vio_isp_set_wb_mode(m_cam_id, 0);
        
        m_initialized = true;
        return 0;
    }
    
    /**
     * @brief 启动采集线程
     */
    void start() {
        if (!m_initialized || m_running) return;
        
        m_running = true;
        m_capture_thread = std::thread(&CameraService::captureLoop, this);
    }
    
    /**
     * @brief 停止服务
     */
    void stop() {
        m_running = false;
        if (m_capture_thread.joinable()) {
            m_capture_thread.join();
        }
        vio_camera_close(m_cam_id);
        vio_deinit();
        m_initialized = false;
    }
    
    /**
     * @brief 注册帧回调 (用于 AI 服务)
     */
    void setFrameCallback(FrameCallback cb) {
        m_callback = cb;
    }
    
    /**
     * @brief 设置 ISP 参数
     */
    void setBrightness(int value) {
        vio_isp_set_brightness(m_cam_id, value);
    }
    
private:
    CameraService() 
        : m_cam_id(0), m_width(1920), m_height(1080), m_fps(30),
          m_initialized(false), m_running(false) {}
    
    ~CameraService() { stop(); }
    
    void captureLoop() {
        vio_frame_t frame;
        
        while (m_running) {
            // 调用 Framework API 获取帧
            int ret = vio_camera_get_frame(m_cam_id, &frame, 1000);
            if (ret != 0) {
                continue;  // 超时或错误
            }
            
            // 分发帧给回调
            if (m_callback) {
                m_callback(&frame);
            }
            
            // 释放帧
            vio_camera_put_frame(m_cam_id, &frame);
        }
    }
    
    // 成员变量
    int m_cam_id;
    int m_width;
    int m_height;
    int m_fps;
    std::atomic<bool> m_initialized;
    std::atomic<bool> m_running;
    std::thread m_capture_thread;
    FrameCallback m_callback;
    
    // 禁止拷贝
    CameraService(const CameraService&) = delete;
    CameraService& operator=(const CameraService&) = delete;
};
​
#endif // CAMERA_SERVICE_H
/**
 * @file AIService.h
 * @brief AI 推理服务
 */
​
#ifndef AI_SERVICE_H
#define AI_SERVICE_H
​
#include <memory>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include "dnn_handle.h"   // ← Framework 层 API
​
class AIService {
public:
    using ResultCallback = std::function<void(const float*, int)>;
    
    static AIService& getInstance() {
        static AIService instance;
        return instance;
    }
    
    /**
     * @brief 初始化 AI 服务
     * @param model_path 模型文件路径
     */
    int init(const char* model_path) {
        // 1. 加载模型 (调用 libhobot_dnn.so)
        m_model_handle = dnn_model_load(model_path);
        if (!m_model_handle) {
            return -1;
        }
        
        m_initialized = true;
        return 0;
    }
    
    /**
     * @brief 启动推理线程
     */
    void start() {
        if (!m_initialized || m_running) return;
        
        m_running = true;
        m_inference_thread = std::thread(&AIService::inferenceLoop, this);
    }
    
    /**
     * @brief 停止服务
     */
    void stop() {
        m_running = false;
        m_cv.notify_all();
        if (m_inference_thread.joinable()) {
            m_inference_thread.join();
        }
        
        if (m_model_handle) {
            dnn_model_unload(m_model_handle);
            m_model_handle = nullptr;
        }
        m_initialized = false;
    }
    
    /**
     * @brief 提交推理任务
     * @param data 图像数据 (DMA-BUF fd)
     * @param size 数据大小
     */
    void submitTask(int fd, int size) {
        if (!m_initialized) return;
        
        std::lock_guard<std::mutex> lock(m_queue_mutex);
        m_task_queue.push({fd, size});
        m_cv.notify_one();
    }
    
    /**
     * @brief 注册推理结果回调
     */
    void setResultCallback(ResultCallback cb) {
        m_result_callback = cb;
    }
    
private:
    struct Task {
        int fd;
        int size;
    };
    
    AIService() : m_model_handle(nullptr), m_initialized(false), m_running(false) {}
    
    void inferenceLoop() {
        while (m_running) {
            Task task;
            {
                std::unique_lock<std::mutex> lock(m_queue_mutex);
                m_cv.wait(lock, [this] { 
                    return !m_task_queue.empty() || !m_running; 
                });
                
                if (!m_running) break;
                
                task = m_task_queue.front();
                m_task_queue.pop();
            }
            
            // 创建推理任务
            void* task_handle = dnn_task_create(m_model_handle);
            
            // 设置输入 (零拷贝,直接使用 DMA-BUF fd)
            dnn_task_set_input(task_handle, 0, (void*)(intptr_t)task.fd, task.size);
            
            // 执行推理
            dnn_task_run(task_handle);
            
            // 获取输出
            void* output = nullptr;
            int output_size = 0;
            dnn_task_get_output(task_handle, 0, &output, &output_size);
            
            // 回调结果
            if (m_result_callback) {
                m_result_callback((float*)output, output_size);
            }
            
            // 销毁任务
            dnn_task_destroy(task_handle);
        }
    }
    
    void* m_model_handle;
    std::atomic<bool> m_initialized;
    std::atomic<bool> m_running;
    std::thread m_inference_thread;
    std::queue<Task> m_task_queue;
    std::mutex m_queue_mutex;
    std::condition_variable m_cv;
    ResultCallback m_result_callback;
    
    AIService(const AIService&) = delete;
    AIService& operator=(const AIService&) = delete;
};
​
#endif // AI_SERVICE_H
/**
 * @file main.cpp
 * @brief 主入口 - 服务生命周期管理
 */
​
#include <csignal>
#include <atomic>
#include <iostream>
#include "CameraService.h"
#include "AIService.h"
​
static std::atomic<bool> g_running{true};
​
void signalHandler(int sig) {
    std::cout << "Received signal " << sig << ", shutting down..." << std::endl;
    g_running = false;
}
​
int main(int argc, char** argv) {
    // 1. 注册信号处理
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);
    
    // 2. 初始化服务
    auto& camera = CameraService::getInstance();
    auto& ai = AIService::getInstance();
    
    if (camera.init("/etc/vio_config.json") != 0) {
        std::cerr << "Camera init failed" << std::endl;
        return -1;
    }
    
    if (ai.init("/data/model/yolov5s.bin") != 0) {
        std::cerr << "AI init failed" << std::endl;
        return -2;
    }
    
    // 3. 设置回调 (服务间通信)
    camera.setFrameCallback([&ai](const vio_frame_t* frame) {
        // 将帧提交给 AI 服务
        ai.submitTask(frame->fd, frame->width * frame->height * 1.5);
    });
    
    ai.setResultCallback([](const float* results, int size) {
        // 处理推理结果
        int num_detections = size / 6;  // 假设每框6个float
        std::cout << "Detected " << num_detections << " objects" << std::endl;
    });
    
    // 4. 启动服务
    camera.start();
    ai.start();
    
    std::cout << "Services started. Press Ctrl+C to stop." << std::endl;
    
    // 5. 主线程等待
    while (g_running) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    
    // 6. 优雅停止 (逆序)
    ai.stop();
    camera.stop();
    
    std::cout << "Services stopped. Exiting." << std::endl;
    
    return 0;
}

四、全生命周期跟踪

4.1 生命周期状态机

┌─────────────────────────────────────────────────────────────────────────────┐
│                     服务生命周期状态机                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                    ┌─────────────────────────────────────────┐              │
│                    │                                         │              │
│                    ▼                                         │              │
│  ┌─────────┐    ┌──────────┐    ┌──────────┐    ┌─────────┐  │              │
│  │ UNKNOWN │───▶│ CREATED  │───▶│ STARTED  │───▶│ RUNNING │──┘              │
│  └─────────┘    └──────────┘    └──────────┘    └─────────┘                  │
│       │              │               │               │                       │
│       │              │               │               │                       │
│       ▼              ▼               ▼               ▼                       │
│  ┌─────────┐    ┌──────────┐    ┌──────────┐    ┌─────────┐                  │
│  │  init   │    │  open    │    │  start   │    │  loop   │                  │
│  │ 失败    │    │ 失败     │    │ 线程失败 │    │ 异常    │                  │
│  └─────────┘    └──────────┘    └──────────┘    └─────────┘                  │
│       │              │               │               │                       │
│       └──────────────┴───────────────┴───────────────┘                       │
│                                    │                                         │
│                                    ▼                                         │
│                              ┌──────────┐                                    │
│                              │ STOPPED  │                                    │
│                              │ (清理资源)│                                    │
│                              └──────────┘                                    │
│                                    │                                         │
│                                    ▼                                         │
│                              ┌──────────┐                                    │
│                              │ DESTROYED│                                    │
│                              └──────────┘                                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 跟踪点与日志设计

/**
 * @file LifecycleTracker.h
 * @brief 生命周期跟踪器
 */
​
class LifecycleTracker {
public:
    enum State {
        STATE_UNKNOWN,
        STATE_CREATED,
        STATE_INITIALIZED,
        STATE_STARTED,
        STATE_RUNNING,
        STATE_STOPPING,
        STATE_STOPPED,
        STATE_DESTROYED
    };
    
    void transitionTo(State new_state, const char* func, int line) {
        auto now = std::chrono::system_clock::now();
        auto time_t = std::chrono::system_clock::to_time_t(now);
        
        printf("[LIFECYCLE] %s:%d: %s → %s\n",
               func, line,
               stateToString(m_current_state),
               stateToString(new_state));
        
        m_current_state = new_state;
        m_last_transition = now;
    }
    
    State getState() const { return m_current_state; }
    
private:
    State m_current_state = STATE_UNKNOWN;
    std::chrono::time_point<std::chrono::system_clock> m_last_transition;
    
    const char* stateToString(State s) {
        switch(s) {
            case STATE_UNKNOWN: return "UNKNOWN";
            case STATE_CREATED: return "CREATED";
            case STATE_INITIALIZED: return "INITIALIZED";
            case STATE_STARTED: return "STARTED";
            case STATE_RUNNING: return "RUNNING";
            case STATE_STOPPING: return "STOPPING";
            case STATE_STOPPED: return "STOPPED";
            case STATE_DESTROYED: return "DESTROYED";
            default: return "?";
        }
    }
};

五、技术点总结

"RDK X3 的 Framework 层(如 libhobot_spdev.so、libhobot_dnn.so)本质上是对 HAL 层驱动的封装。例如 vio_camera_get_frame() 内部会调用 ioctl(VIDIOC_DQBUF),最终触发 hobot_dev_ips.c 中的中断处理。

应用层服务设计

  • 采用单例模式管理服务实例

  • 使用观察者模式实现服务间通信(帧回调)

  • 独立的工作线程处理采集和推理

  • 生命周期状态机确保资源正确释放

全生命周期跟踪

  • 状态:UNKNOWN → CREATED → INITIALIZED → STARTED → RUNNING → STOPPED → DESTROYED

  • 每个状态转换都记录日志

  • 信号处理保证优雅退出

最难的问题

  1. DMA-BUF 跨服务传递 - 通过文件描述符传递,注意引用计数

  2. 缓存一致性 - 需要调用 dma_buf_sync 保证 CPU/BPU 看到一致数据

  3. 服务间同步 - 使用无锁队列 + 条件变量避免死锁"

第四部分 多模块架构设计

一、基于实际开源代码的架构分析

1.1 从实际仓库看到的模块结构

D-Robotics 组织下的关键仓库分析
│
├── hobot-spdev          # 媒体处理 SDK (C API)
│   ├── include/         # 头文件
│   ├── src/             # 实现
│   └── samples/         # 示例
│
├── hobot-dnn            # AI 推理框架 (C++ API)
│   ├── include/hobot_dnn/
│   │   ├── dnn_handle.h
│   │   ├── dnn_task.h
│   │   └── dnn_tensor.h
│   └── src/
│
├── hobot-codec          # 编解码库
├── hobot-sensor         # 传感器抽象
└── tros                 # TogetheROS.Bot (ROS2 中间件)

1.2 现有架构的局限性

从实际代码看,地平线的中间件主要是 C API 风格,缺少面向对象的多态设计:

// 现有的 C API 风格
int vio_camera_open(int cam_id, int width, int height, int fps);
int vio_camera_get_frame(int cam_id, vio_frame_t* frame, int timeout_ms);
int vio_isp_set_brightness(int cam_id, int value);

问题

  • 硬编码摄像头 ID,难以扩展多类型相机

  • 函数式 API,无法利用多态特性

  • ISP 参数分散,没有统一接口

二、C++ 多态纯虚函数架构设计

2.1 核心抽象类设计

/**
 * @file ICamera.hpp
 * @brief 相机抽象接口 - 纯虚函数
 * @note 设计模式: 策略模式 + 工厂模式
 */
​
#ifndef ICAMERA_HPP
#define ICAMERA_HPP
​
#include <memory>
#include <string>
#include <functional>
#include <vector>
​
namespace horizon {
namespace camera {
​
/**
 * @brief 帧数据结构
 */
struct Frame {
    void* data[3];          // 图像数据指针
    int width;
    int height;
    int stride[3];
    int format;             // NV12, RGB888, etc.
    uint64_t timestamp;
    int sequence;
    int dma_fd;             // DMA-BUF 文件描述符 (零拷贝)
};
​
/**
 * @brief ISP 参数接口
 */
struct ISPCapability {
    bool has_manual_exposure;
    bool has_manual_gain;
    bool has_manual_focus;
    bool has_auto_wb;
    int min_exposure;
    int max_exposure;
    int min_gain;
    int max_gain;
};
​
/**
 * @brief 相机抽象接口 (纯虚函数)
 * 
 * 设计模式: 策略模式 - 不同相机类型实现不同策略
 */
class ICamera {
public:
    virtual ~ICamera() = default;
    
    // ========== 生命周期管理 ==========
    virtual int init(const std::string& config_path) = 0;
    virtual int start() = 0;
    virtual int stop() = 0;
    virtual void deinit() = 0;
    
    // ========== 帧采集 ==========
    virtual int getFrame(Frame& frame, int timeout_ms) = 0;
    virtual void putFrame(Frame& frame) = 0;
    
    // ========== ISP 控制 ==========
    virtual int setBrightness(int value) = 0;
    virtual int setContrast(int value) = 0;
    virtual int setSaturation(int value) = 0;
    virtual int setWhiteBalance(int mode) = 0;
    
    // ========== 曝光控制 ==========
    virtual int setExposure(int value) = 0;
    virtual int setGain(int value) = 0;
    virtual int setAutoExposure(bool enable) = 0;
    
    // ========== 属性查询 ==========
    virtual std::string getModelName() = 0;
    virtual ISPCapability getCapability() = 0;
    virtual int getWidth() = 0;
    virtual int getHeight() = 0;
    virtual int getFps() = 0;
    
    // ========== 回调注册 ==========
    using FrameCallback = std::function<void(const Frame&)>;
    virtual void setFrameCallback(FrameCallback cb) = 0;
};
​
} // namespace camera
} // namespace horizon
​
#endif // ICAMERA_HPP

2.2 具体相机实现类

/**
 * @file MipiCamera.hpp
 * @brief MIPI 相机实现 (RDK X3)
 */
​
#ifndef MIPI_CAMERA_HPP
#define MIPI_CAMERA_HPP
​
#include "ICamera.hpp"
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
​
extern "C" {
#include "vio_api.h"      // 地平线 Framework API
}
​
namespace horizon {
namespace camera {
​
/**
 * @brief MIPI 相机 - 适配 RDK X3
 * 
 * 设计模式: 策略模式 - 具体策略
 * 设计模式: 适配器模式 - 适配地平线 C API
 */
class MipiCamera : public ICamera {
public:
    MipiCamera(int cam_id, int width, int height, int fps);
    ~MipiCamera() override;
    
    // ICamera 接口实现
    int init(const std::string& config_path) override;
    int start() override;
    int stop() override;
    void deinit() override;
    
    int getFrame(Frame& frame, int timeout_ms) override;
    void putFrame(Frame& frame) override;
    
    int setBrightness(int value) override;
    int setContrast(int value) override;
    int setSaturation(int value) override;
    int setWhiteBalance(int mode) override;
    
    int setExposure(int value) override;
    int setGain(int value) override;
    int setAutoExposure(bool enable) override;
    
    std::string getModelName() override { return "RDK X3 MIPI Camera"; }
    ISPCapability getCapability() override;
    int getWidth() override { return m_width; }
    int getHeight() override { return m_height; }
    int getFps() override { return m_fps; }
    
    void setFrameCallback(FrameCallback cb) override { m_callback = cb; }
    
private:
    void captureLoop();
    Frame convertToFrame(const vio_frame_t& vframe);
    
    int m_cam_id;
    int m_width;
    int m_height;
    int m_fps;
    std::string m_config_path;
    
    std::atomic<bool> m_initialized;
    std::atomic<bool> m_running;
    std::thread m_capture_thread;
    
    FrameCallback m_callback;
    
    // 缓冲区队列
    std::queue<Frame> m_frame_queue;
    std::mutex m_queue_mutex;
};
​
} // namespace camera
} // namespace horizon
​
#endif // MIPI_CAMERA_HPP
/**
 * @file MipiCamera.cpp
 * @brief MIPI 相机实现
 */
​
#include "MipiCamera.hpp"
#include <iostream>
​
namespace horizon {
namespace camera {
​
MipiCamera::MipiCamera(int cam_id, int width, int height, int fps)
    : m_cam_id(cam_id)
    , m_width(width)
    , m_height(height)
    , m_fps(fps)
    , m_initialized(false)
    , m_running(false) {
}
​
MipiCamera::~MipiCamera() {
    deinit();
}
​
int MipiCamera::init(const std::string& config_path) {
    m_config_path = config_path;
    
    // 调用地平线 Framework API
    int ret = vio_init(config_path.c_str());
    if (ret != 0) {
        std::cerr << "vio_init failed: " << ret << std::endl;
        return -1;
    }
    
    ret = vio_camera_open(m_cam_id, m_width, m_height, m_fps);
    if (ret != 0) {
        vio_deinit();
        std::cerr << "vio_camera_open failed: " << ret << std::endl;
        return -2;
    }
    
    m_initialized = true;
    return 0;
}
​
int MipiCamera::start() {
    if (!m_initialized || m_running) return -1;
    
    m_running = true;
    m_capture_thread = std::thread(&MipiCamera::captureLoop, this);
    
    return 0;
}
​
int MipiCamera::stop() {
    if (!m_running) return 0;
    
    m_running = false;
    if (m_capture_thread.joinable()) {
        m_capture_thread.join();
    }
    
    return 0;
}
​
void MipiCamera::deinit() {
    stop();
    
    if (m_initialized) {
        vio_camera_close(m_cam_id);
        vio_deinit();
        m_initialized = false;
    }
}
​
int MipiCamera::getFrame(Frame& frame, int timeout_ms) {
    std::lock_guard<std::mutex> lock(m_queue_mutex);
    if (m_frame_queue.empty()) {
        return -1;
    }
    frame = m_frame_queue.front();
    m_frame_queue.pop();
    return 0;
}
​
void MipiCamera::putFrame(Frame& frame) {
    // 回收帧资源
    // 实际需要调用 vio_camera_put_frame
}
​
void MipiCamera::captureLoop() {
    vio_frame_t vframe;
    
    while (m_running) {
        int ret = vio_camera_get_frame(m_cam_id, &vframe, 1000);
        if (ret != 0) {
            continue;
        }
        
        Frame frame = convertToFrame(vframe);
        
        // 加入队列
        {
            std::lock_guard<std::mutex> lock(m_queue_mutex);
            m_frame_queue.push(frame);
        }
        
        // 回调
        if (m_callback) {
            m_callback(frame);
        }
        
        vio_camera_put_frame(m_cam_id, &vframe);
    }
}
​
Frame MipiCamera::convertToFrame(const vio_frame_t& vframe) {
    Frame frame;
    frame.data[0] = vframe.data[0];
    frame.data[1] = vframe.data[1];
    frame.data[2] = vframe.data[2];
    frame.width = vframe.width;
    frame.height = vframe.height;
    frame.stride[0] = vframe.stride[0];
    frame.stride[1] = vframe.stride[1];
    frame.stride[2] = vframe.stride[2];
    frame.format = vframe.format;
    frame.timestamp = vframe.timestamp;
    frame.sequence = vframe.sequence;
    frame.dma_fd = vframe.fd;
    return frame;
}
​
int MipiCamera::setBrightness(int value) {
    return vio_isp_set_brightness(m_cam_id, value);
}
​
int MipiCamera::setContrast(int value) {
    return vio_isp_set_contrast(m_cam_id, value);
}
​
int MipiCamera::setSaturation(int value) {
    return vio_isp_set_saturation(m_cam_id, value);
}
​
int MipiCamera::setWhiteBalance(int mode) {
    return vio_isp_set_wb_mode(m_cam_id, mode);
}
​
int MipiCamera::setExposure(int value) {
    // 调用对应 Framework API
    return 0;
}
​
int MipiCamera::setGain(int value) {
    return 0;
}
​
int MipiCamera::setAutoExposure(bool enable) {
    return 0;
}
​
ISPCapability MipiCamera::getCapability() {
    ISPCapability cap;
    cap.has_manual_exposure = true;
    cap.has_manual_gain = true;
    cap.has_manual_focus = false;
    cap.has_auto_wb = true;
    cap.min_exposure = 1;
    cap.max_exposure = 1000;
    cap.min_gain = 1;
    cap.max_gain = 16;
    return cap;
}
​
} // namespace camera
} // namespace horizon

2.3 USB 相机适配器

/**
 * @file UsbCamera.hpp
 * @brief USB 相机实现 (通过 V4L2)
 */
​
#ifndef USB_CAMERA_HPP
#define USB_CAMERA_HPP
​
#include "ICamera.hpp"
#include <linux/videodev2.h>
​
namespace horizon {
namespace camera {
​
/**
 * 设计模式: 策略模式 - 另一种具体策略
 * 设计模式: 适配器模式 - 适配 V4L2
 */
class UsbCamera : public ICamera {
public:
    UsbCamera(const std::string& device, int width, int height, int fps);
    ~UsbCamera() override;
    
    // ICamera 接口实现
    int init(const std::string& config_path) override;
    int start() override;
    int stop() override;
    void deinit() override;
    
    int getFrame(Frame& frame, int timeout_ms) override;
    void putFrame(Frame& frame) override;
    
    int setBrightness(int value) override;
    int setContrast(int value) override;
    int setSaturation(int value) override;
    int setWhiteBalance(int mode) override;
    int setExposure(int value) override;
    int setGain(int value) override;
    int setAutoExposure(bool enable) override;
    
    std::string getModelName() override { return "USB Camera (V4L2)"; }
    ISPCapability getCapability() override;
    int getWidth() override { return m_width; }
    int getHeight() override { return m_height; }
    int getFps() override { return m_fps; }
    
    void setFrameCallback(FrameCallback cb) override { m_callback = cb; }
    
private:
    void captureLoop();
    int initV4L2();
    
    std::string m_device;
    int m_width;
    int m_height;
    int m_fps;
    
    int m_fd;  // V4L2 设备描述符
    void* m_buffers[4];
    int m_buffer_count;
    
    std::atomic<bool> m_initialized;
    std::atomic<bool> m_running;
    std::thread m_capture_thread;
    FrameCallback m_callback;
    
    std::queue<Frame> m_frame_queue;
    std::mutex m_queue_mutex;
};
​
} // namespace camera
} // namespace horizon
​
#endif // USB_CAMERA_HPP

2.4 工厂模式创建相机

/**
 * @file CameraFactory.hpp
 * @brief 相机工厂
 */
​
#ifndef CAMERA_FACTORY_HPP
#define CAMERA_FACTORY_HPP
​
#include <memory>
#include <string>
#include "ICamera.hpp"
#include "MipiCamera.hpp"
#include "UsbCamera.hpp"
​
namespace horizon {
namespace camera {
​
/**
 * 设计模式: 工厂模式 - 根据类型创建具体相机
 */
class CameraFactory {
public:
    enum CameraType {
        CAMERA_MIPI,
        CAMERA_USB,
        CAMERA_VIRTUAL
    };
    
    /**
     * @brief 创建相机实例
     * @param type 相机类型
     * @return 相机智能指针
     */
    static std::unique_ptr<ICamera> create(CameraType type, const std::string& param,
                                            int width, int height, int fps) {
        switch (type) {
            case CAMERA_MIPI:
                return std::make_unique<MipiCamera>(std::stoi(param), width, height, fps);
            case CAMERA_USB:
                return std::make_unique<UsbCamera>(param, width, height, fps);
            default:
                return nullptr;
        }
    }
};
​
} // namespace camera
} // namespace horizon
​
#endif // CAMERA_FACTORY_HPP

2.5 AI 推理抽象接口

/**
 * @file IInference.hpp
 * @brief AI 推理抽象接口
 */
​
#ifndef IINFERENCE_HPP
#define IINFERENCE_HPP
​
#include <memory>
#include <vector>
#include <functional>
#include "ICamera.hpp"
​
namespace horizon {
namespace inference {
​
/**
 * @brief 检测框
 */
struct Detection {
    float x1, y1, x2, y2;
    float confidence;
    int class_id;
    std::string class_name;
};
​
/**
 * @brief 推理结果
 */
struct Result {
    uint32_t frame_seq;
    uint64_t timestamp;
    std::vector<Detection> detections;
    float inference_time_ms;
};
​
/**
 * 设计模式: 策略模式 - AI 推理抽象
 */
class IInference {
public:
    virtual ~IInference() = default;
    
    // 生命周期
    virtual int init(const std::string& model_path) = 0;
    virtual int start() = 0;
    virtual int stop() = 0;
    virtual void deinit() = 0;
    
    // 推理接口
    virtual int process(const camera::Frame& frame, Result& result) = 0;
    virtual int processAsync(const camera::Frame& frame, 
                              std::function<void(const Result&)> callback) = 0;
    
    // 属性
    virtual std::vector<std::string> getClassNames() = 0;
    virtual int getInputWidth() = 0;
    virtual int getInputHeight() = 0;
};
​
} // namespace inference
} // namespace horizon
​
#endif // IINFERENCE_HPP

2.6 YOLO 推理实现

/**
 * @file YoloInference.hpp
 * @brief YOLO 推理实现
 */
​
#ifndef YOLO_INFERENCE_HPP
#define YOLO_INFERENCE_HPP
​
#include "IInference.hpp"
​
extern "C" {
#include "dnn_handle.h"
}
​
namespace horizon {
namespace inference {
​
/**
 * 设计模式: 策略模式 - YOLO 具体策略
 */
class YoloInference : public IInference {
public:
    YoloInference(float conf_threshold = 0.5f, float nms_threshold = 0.45f);
    ~YoloInference() override;
    
    int init(const std::string& model_path) override;
    int start() override;
    int stop() override;
    void deinit() override;
    
    int process(const camera::Frame& frame, Result& result) override;
    int processAsync(const camera::Frame& frame,
                      std::function<void(const Result&)> callback) override;
    
    std::vector<std::string> getClassNames() override;
    int getInputWidth() override { return 640; }
    int getInputHeight() override { return 640; }
    
private:
    void postprocess(float* output, int output_size, Result& result);
    float calculateIoU(const Detection& a, const Detection& b);
    void nms(std::vector<Detection>& detections, float threshold);
    
    void* m_model_handle;
    float m_conf_threshold;
    float m_nms_threshold;
    std::vector<std::string> m_class_names;
    std::atomic<bool> m_initialized;
};
​
} // namespace inference
} // namespace horizon
​
#endif // YOLO_INFERENCE_HPP

2.7 应用层组装 - 多态使用

/**
 * @file main.cpp
 * @brief 应用主入口 - 展示多态架构
 */
​
#include <iostream>
#include <memory>
#include <csignal>
#include "CameraFactory.hpp"
#include "YoloInference.hpp"
​
using namespace horizon;
​
static std::atomic<bool> g_running{true};
​
void signalHandler(int sig) {
    g_running = false;
}
​
int main(int argc, char** argv) {
    signal(SIGINT, signalHandler);
    
    // ========== 1. 工厂创建相机 (多态) ==========
    // 可以通过配置切换相机类型,代码无需修改
    auto camera = camera::CameraFactory::create(
        camera::CameraFactory::CAMERA_MIPI,  // 或 CAMERA_USB
        "0",  // MIPI ID 或 USB 设备路径
        1920, 1080, 30
    );
    
    if (!camera || camera->init("/etc/vio_config.json") != 0) {
        std::cerr << "Camera init failed" << std::endl;
        return -1;
    }
    
    // ========== 2. 创建 AI 推理引擎 (多态) ==========
    auto ai = std::make_unique<inference::YoloInference>(0.5f, 0.45f);
    if (ai->init("/data/model/yolov5s.bin") != 0) {
        std::cerr << "AI init failed" << std::endl;
        return -2;
    }
    
    // ========== 3. 注册回调 (观察者模式) ==========
    camera->setFrameCallback([&ai](const camera::Frame& frame) {
        inference::Result result;
        if (ai->process(frame, result) == 0) {
            std::cout << "Detected " << result.detections.size() 
                      << " objects, time: " << result.inference_time_ms << "ms" << std::endl;
        }
    });
    
    // ========== 4. 启动所有服务 ==========
    camera->start();
    ai->start();
    
    std::cout << "System running. Press Ctrl+C to stop." << std::endl;
    
    // ========== 5. 主线程可以动态调整参数 ==========
    while (g_running) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
        // 示例: 根据环境光调整 ISP 参数
        static int brightness = 0;
        brightness = (brightness + 1) % 100;
        camera->setBrightness(brightness);
    }
    
    // ========== 6. 优雅停止 ==========
    ai->stop();
    camera->stop();
    ai->deinit();
    camera->deinit();
    
    return 0;
}

三、架构优势总结

┌─────────────────────────────────────────────────────────────────────────────┐
│                    多态架构 vs 地平线原生 API 对比                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  地平线原生 C API                    多态 C++ 架构                          │
│  ──────────────────                  ──────────────                        │
│  ✗ 硬编码摄像头 ID                    ✓ 工厂模式动态创建                      │
│  ✗ 无法扩展新相机类型                  ✓ 继承 ICamera 即可扩展                 │
│  ✗ ISP 参数分散                       ✓ 统一接口                             │
│  ✗ 测试困难                           ✓ Mock 测试简单                        │
│  ✗ 代码重复                           ✓ 基类复用                             │
│  ✗ 编译期绑定                         ✓ 运行时多态                           │
│                                                                             │
│  设计模式应用:                                                               │
│  ├── 策略模式: ICamera 定义接口,MipiCamera/UsbCamera 实现                   │
│  ├── 工厂模式: CameraFactory 创建具体相机                                    │
│  ├── 适配器模式: 将地平线 C API 适配为 C++ 接口                              │
│  ├── 观察者模式: FrameCallback 回调机制                                      │
│  └── 模板方法模式: ICamera 定义生命周期框架                                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

四、扩展性示例

// 添加新的相机类型只需:
class ThermalCamera : public ICamera {
    // 实现所有纯虚函数
};
​
// 工厂注册
CameraFactory::register("thermal", [](){ return std::make_unique<ThermalCamera>(); });
​
// 添加新的推理引擎:
class ResNetInference : public IInference {
    // 实现分类逻辑
};
​
// 使用方代码完全不需要修改!

这种架构让相机应用开发变得极其灵活——换相机类型、换AI模型、换ISP策略,都不需要修改上层业务逻辑,完美体现了开闭原则

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐