XProto用户手册

通过本文介绍,你可以熟悉XProto Framework原型框架的内部核心概念和使用。

整体综述

XStream聚集算法模型、策略的集成以及最终业务Workflow/SDK生成。而XProto是在XStream基础上,为基于XStream构建算法SDK提供APP的运行环境。它支持快速将XStream Workflow封装成可运行的APP,进行运行在地平线边缘芯片中。

下图是XProto APP基本架构图:

框架

它内置了VioPlugin,SmartPlugin,HbipcPlugin,通过这些Plugin,实现图像数据的输入,编解码,图像预测,智能结构化以及到可视化的多阶段处理,完成智能应用的快速交付。

同时我们开放了XProto应用组件Plugin接口,支持用户对XProto框架进行扩展。

XProto消息总线

XProto Framework原型框架主要包括Plugin插件管理和消息分发两个部分。Plugin插件是一个任务实体,所有的Plugin插件都连接到XProto消息总线中,当一个Plubin插件产生消息并把消息Push到总线之后,其他订阅该消息的Plugin插件就可以被调用。

每一个Plugin插件都可以向总线订阅和发布消息,通过消息驱动方式来实现整个原型应用的落地。

XProto结构

Plugin插件开发

XProto原型框架包括总线、消息、插件三部分,其中插件都是继承于XPluginAsync,通过override的函数包括:Init()、Start()、Stop()、Desc();来管理插件的生命周期。

插件可能会生产消息或者向总线注册监听某类消息。如果生产消息需要调用PushMsg()将消息发送到总线分发;如果监听消息,需要实现消息处理函数,并在Init函数中注册需要监听的消息类型,并绑定对应的消息处理函数,同时在Init函数返回前调用父plugin的Init方法,通常是XPluginAsync::Init()。

消息的声明与定义:

  • 使用宏XPLUGIN_REGISTER_MSG_TYPE,自定义消息类型,每个消息名字唯一;

  • 新的Message类型需要继承XProtoMessage;

  • 需要监听消息的插件需要:

    • 实现消息处理函数;

    • 覆盖Init函数,在其中完成监听消息注册,并绑定对应的消息处理函数,

    • 及其他初始化工作,同时在函数返回前需要调用父plugin的Init方法,

    • 通常是XPluginAsync::Init()。

sample程序参考xsdk/common/xproto/framework/sample/sample_plugin.cpp

//定义消息名称
#define TYPE_SAMPLE_MESSAGE "XPLUGIN_SAMPLE_MESSAGE"
XPLUGIN_REGISTER_MSG_TYPE(XPLUGIN_SAMPLE_MESSAGE)
//实现一个Message消息类型
struct NumberProdMessage : XProtoMessage {
  float num_;
  explicit NumberProdMessage(float num) :num_(num) {
    type_ = TYPE_SAMPLE_MESSAGE;//指定消息类型
  }
  std::string Serialize() override {
    std::ostringstream ss;
    ss << num_;
    return std::string(ss.str());
  }
};

class NumberProducerPlugin : public XPluginAsync {
 public:
  //启动插件,并在插件里面管理了一个发送消息线程。
  int Start() {
    prd_thread_ = new std::thread([&] (){
      for (uint32_t i = 0; i < total_cnt_ && !prd_stop_; i++) {
        auto np_msg = std::make_shared<NumberProdMessage>(5);
        PushMsg(np_msg);
        std::this_thread::sleep_for(milliseconds(40));
      }
      LOGI << desc() << " prd exit";
    });
    return 0;
  }
  //关闭插件,关闭发送消息线程。
  int Stop() {
    prd_stop_ = true;
    prd_thread_->join();
    return 0;
  }
};

class SumConsumerPlugin : public XPluginAsync {
 public:
  int Init() override {
    sum_ = 0.f;
    // 注册消息回掉函数函数
    RegisterMsg(TYPE_SAMPLE_MESSAGE, 
      std::bind(&SumConsumerPlugin::Sum,
      this, std::placeholders::_1));
    return XPluginAsync::Init();
  }
  int Sum(XProtoMessagePtr msg) {
    auto np_msg = std::static_pointer_cast<NumberProdMessage>(msg);
    sum_ += np_msg->num_;
    LOGI << "curr sum:" << sum_;
    return sum_;
  }
  int Stop() {
    return 0;
  }
 private:
  float sum_;
};

重要接口说明

// 声明消息类型;每一类消息都有一个字符串形式的消息类型和结构体来表示.
// 该接口为一个宏, 参数MSG_TYPE用来表示声明的消息类型, 需要直接使用标识符的格式书写, 宏内部会将其转成字符串.  
// 需要在消费者Plugin调用订阅消息接口之前调用该接口声明消息类型,一般将该宏放在全局变量声明的位置.
// 参数:MSGTYPE: 消息类型
XPLUGIN_REGISTER_MSG_TYPE(MSG_TYPE)

// 初始化Plugin
// 该接口需要继承XPluginAsync类的自定义Plugin实现该接口定义. 该接口用来初始化Plugin,自定义Plugin一般在该接口内调用订阅消息接口, 然后继续调用XPluginAsync::Init接口以初始化父类.
// 返回值:0: 成功;非0: 失败
int XPluginAsync::Init() override;

// 启动Plugin
// 该接口需要继承XPluginAsync类的自定义Plugin实现该接口定义. 该接口用来启动Plugin. 
// 返回值:0: 成功;非0: 失败
int XPluginAsync::Start();

// 停止Plugin
// 该接口需要继承XPluginAsync类的自定义Plugin实现该接口定义. 该接口用来停止Plugin. 
// 返回值:0: 成功;非0: 失败
int XPluginAsync::Stop();

// 反初始化Plugin
// 该接口需要继承XPluginAsync类的自定义Plugin实现该接口定义. 该接口用来反初始化Plugin.  继承自XPluginAsync子plugin类,在完成自己的反初始化任务后,最后需要调用XPluginAsync::DeInit接口以反初始化父类.
// 返回值:0: 成功;非0: 失败
int XPluginAsync::DeInit() override;

// 发布消息
// 该接口用来将消息发布到XProto内部总线上. 接收一个类型为XProtoMessage的结构体指针, XProto的所有消息都继承于该类型.
// 参数:XProtoMessagePtr msg: 发布到总线的消息. 
void XPluginAsync::PushMsg(XProtoMessagePtr msg);

// 订阅消息
// 订阅指定类型的消息. 监听总线, 当指定的消息类型发布时, 调用回调函数.  
// 自定义的Plugin需要在Init函数中,调用XPluginAsync::Init之前调用该接口完成监听消息注册。
// 参数:const std::string& type: 消息类型字符串.
// 参数:XProtoMessageFunc callback: 该类型消息的回调函数.
void XPluginAsync::RegisterMsg(const std::string& type, XProtoMessageFunc callback);

// 插件描述信息
// 该接口需要继承XPluginAsync类的自定义Plugin实现该接口定义. 
// 返回值:描述当前自定义Plugin的字符串.
std::string XPluginAsync::desc() const;

内置Plugin列表

1. VIOPlugin

VIOPlugin负责获取、转换图像数据并控制图像数据获取速率,然后将图像数据或丢帧消息推送给消息总线。它主要分为两部分:

  • 图像数据的获取:图像来源可以分为SIF(摄像头)、JPEG回灌图片和NV12回灌图片。

  • 控制图像数据获取速率:VioPlugin中通过定长buffer来实现速率控制。buffer大小可以通过JSON文件进行配置。单输入图像源的情况下理论最多可达7个,当智能帧产生速率过慢,buffer耗尽时,VioPlugin产生主动丢帧数据

  • 消息的产生和推送:消息分为图像数据消息与主动丢帧数据消息,由第一部分产生后推送至消息总线。

VioPlugin配置文件如下:

{
  "cam_type": "mono",
  "data_source": "ipc_camera",
  "max_vio_buffer": 3,
  "ts_type": "input_coded",
  "file_path": "name.list",
  "pad_width": 1920,
  "pad_height": 1080,
  "vio_cfg_file": {
    "ipc_camera": "configs/vio/hb_vio.json",
    "panel_camera": "configs/vio/panel_camera.json",
    "jpeg_image_list": "configs/vio/vio_onsemi0230_fb.json",
    "nv12_image_list": "configs/vio/vio_onsemi0230_fb.json",
    "image": "configs/vio/vio_onsemi0230_fb.json"
  }
}

各个字段的含义:

  • cam_type : 镜头类型,单目(mono)或双目(dual)

  • data_source : 输入源类型:

    • ipc_camera : IPC等后接场景,输入通常为bt1120

    • panel_camera : 面板机等前接场景,输入通常为mipi

    • jpeg_image_list : jpeg格式的回灌图片

    • nv12_image_list : nv12格式回灌图片

  • max_vio_buffer : 控制Vio pending帧数上限,最大缓存数量。如果递交给消息总线的消息大于该数量, 则新的消息会丢弃,

  • ts_type : VIO时间戳类型. 该时间戳会填充到视频帧消息中

    • input_coded : 通过y图的前16个字节的编码获得时间戳,通常用于ipc等后接场景

    • frame_id : 读取vio数据结构的frame_id字段作为时间戳,96board等使用该配置

    • raw_ts : 读取vio数据结构中的timestamp字段作为时间戳,面板机standalone方案使用该类型

  • file_path : 回灌图片的name list,用于回灌场景。

  • pad_width : jpeg回灌时图片对齐参数,用于回灌场景。

  • pad_height : jpeg回灌时图片对齐参数,用于回灌场景。

  • vio_cfg_file : 对应每种输入源的详细配置文件。

VioPlugin会向消息总线推送”生产消息类型”和“丢帧消息”两种消息类型:

  • XPLUGIN_IMAGE_MESSAGE : 图像帧类型的消息struct ImageVioMessage : VioMessage {};

  • XPLUGIN_DROP_MESSAGE : 丢帧消息struct DropVioMessage : VioMessage {};

其中图像帧消息和丢帧消息的共同继承于VioMessage结构体.

struct VioMessage : public XProtoMessage {
  public:
    VioMessage() { type_ = TYPE_IMAGE_MESSAGE; };
    virtual ~VioMessage() = default;
    // 图像帧个数
    uint32_t num_ = 0;
    // 帧ID:同时进入VIO模块的多张图片会分配同一个ID
    uint64_t sequence_id_ = 0;
    // 时间戳
    uint64_t time_stamp_ = 0;
    // 用于回灌场景,表示回灌图片uri是不是一张有效的图片帧数据
    bool is_valid_uri_ = true;
};

同时,VioPlugin提供了以下公共接口,支持客户在业务逻辑中进行集成:

class VioPlugin : public xproto::XPluginAsync {
public:
  VioPlugin() = delete;
  // 构造VioPlugin对象, 并指定VIO配置文件.
  explicit VioPlugin(const std::string &path);
  ~VioPlugin() override;
  // 初始化Plugin.
  int Init() override;
  // 启动Plugin.
  int Start() override;
  // 停止VioPlugin。
  int Stop() override;
};

2. SmartPlugin

SmartPlugin是基于XStream通用SDK接口开发的通用智能应用运行框架。它监听XProto总线上面的VioPlugin送来的XPLUGIN_IMAGE_MESSAGE类型消息,并且将它送到XStream的workflow实例里面,同时接收workflow输出的结果,并将其序列化后产生智能消息然后送回到消息总线上。

SmartPlugin主要是作为XStream的Container,它会将XStream Output数据以XPLUGIN_IMAGE_MESSAGE类型推送到总线中。

对SmartPlugin定制主要分为三步:

  • 根据自己的场景需要依赖的XStream Method,创建一个Method Factory。

  • 针对自己场景的智能化应用需要输出的智能化结果修改protobuf文件,并重新实现SmartMessage的Serialize方法。

  • 修改SmartPlugin配置文件

    {
        "xstream_workflow_file": "configs/det_mot.json",
        "enable_profile": 0,
        "profile_log_path": "/userdata/log/profile.txt"
    }
    
    • xstream_workflow_file: 指定xstream workflow配置文件;

    • enable_profile: 是否使能online profile,该feature是XStream支持的feature,如果method开发中包括了profile信息可通过该开关online使能;

    • profile_log_path: online profile 日志输出路径。

同时,VioPlugin也提供了以下公共接口,支持客户在业务逻辑中进行集成:

class SmartPlugin : public XPluginAsync {
 public:
  SmartPlugin() = default;
  // 构造SmartPlugin对象, 并指定VIO配置文件.
  explicit SmartPlugin(const std::string& config_file);
  void SetConfig(const std::string& config_file) { config_file_ = config_file; }
  // 初始化Plugin.
  int Init() override;
  // 启动Plugin.
  int Start() override;
  // 停止VioPlugin。
  int Stop() override;
};

SmartPlugin只支持加载一个workflow配置文件,但是在一些AI盒子等复合应用场景,需要支持多Workflow数据流。关于多路输入和多Workflow数据流方案,详细参考README

3. HbipcPlugin

HbipcPlugin为跨板传输插件,主要负责实现AP与地平线CP端的双向通信功能,CP端通过HbipcPlugin将智能帧等数据发送到AP端,或者通过HbipcPlugin接收AP端发过来的配置数据与命令数据。

HbipcPlugin是基于地平线HBIPC框架实现,使用之前需要先提供一个HBIPC的配置参数:

{
  "domain_name": "X2SD001",
  "server_id": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14]
}
  • domain_name表示HBIPC通信域, 可选的值有X2SD001X2BIF001

  • server_id表示CP侧的服务ID,UUID格式。AP侧在建立连接的时候会用到该UUID。

当HBIPCPlugin收到AP侧发来的配置数据和命令数据时,会将其以XPLUGIN_HBIPC_MESSAGE类型的消息发送到XProto内部总线。消息对应的结构体定义如下:

struct HbipcMessage : XProtoMessage {
  HbipcMessage() { type_ = "XPLUGIN_HBIPC_MESSAGE"; }
  std::string Serialize() override { return "Default hbipc message"; }
  virtual ~HbipcMessage() = default;
};

struct CustomHbipcMessage : HbipcMessage {
  explicit CustomHbipcMessage(std::string proto) : proto_(proto) {
    type_ = "XPLUGIN_HBIPC_MESSAGE";
  }
  std::string Serialize() override;

 private:
  std::string proto_;
};

HBIPCPlugin负责将VioPlugin和SmartPlugin产生的消息发送到AP侧,所以它需要监听总线上面的XPLUGIN_SMART_MESSAGEXPLUGIN_IMAGE_MESSAGEXPLUGIN_DROP_MESSAGE

  • XPLUGIN_SMART_MESSAGE: 智能数据帧。

  • XPLUGIN_IMAGE_MESSAGE:图像数据帧。

  • XPLUGIN_DROP_MESSAGE:报告VIOPlugin丢弃的帧。

同时,HbipcPlugin也提供了以下公共接口,支持客户在业务逻辑中进行集成:

class HbipcPlugin : public XPluginAsync {
 public:
  HbipcPlugin() = default;
  // 构造HbipcPlugin对象, 并指定VIO配置文件.
  explicit HbipcPlugin(const std::string& config_file);
  void SetConfig(const std::string& config_file) { config_file_ = config_file; }
  // 初始化Plugin.
  int Init() override;
  // 反初始化plugin
  int Deinit();
  // 启动Plugin.
  int Start() override;
  // 停止VioPlugin。
  int Stop() override;
};