OrangePi3588Media/Readme.md
sladro f8aa377a79
Some checks are pending
CI / host-build (push) Waiting to run
CI / rk3588-cross-build (push) Waiting to run
优化性能,对齐prd
2026-01-06 17:37:27 +08:00

932 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 回答
# RK3588 智能视频分析系统 PRD完整方案 v1.1
> 在你 v1.0 的基础上,已经补上你点名要的这几块:
> **DAG 多分支示例 + 节点通用字段 + 生命周期 Drain + Frame 结构 + 性能 / 线程 / 监控章节**,现在这份可以直接当总 PRD 用。
---
## 一、文档综述
### 1.1 项目背景
为了充分利用瑞芯微 RK3588 芯片提供的:
- **NPU约 6 TOPS**RKNNINT8 推理能力)
- **VPU多路 1080p 硬件解码/编码**
- **RGA硬件图像处理缩放/颜色转换/OSD 等)**
构建一套**纯 C++ 实现、高性能、低延迟、模块化**的边缘计算视频分析平台。平台主要部署在园区、仓库、工厂等场景,实现就地智能分析与处理,减少带宽与中心算力压力。
### 1.2 核心目标
- **高性能**
- 单 RK3588 支持 816 路 1080p 实时分析
- 单路端到端(拉流→推理→输出)延迟 < 500ms
- **低耦合**
- 基于插件 + DAG 流水线架构
- 算法业务逻辑与底层框架完全解耦
- **易运维**
- JSON 配置驱动
- 支持热更新无需重启进程即可变更流程/参数
- **多场景覆盖**
- 同一套框架通过不同配置图覆盖
- 安防报警
- 录像存储
- 转码网关RTSP/HLS
- 混合场景报警 + 存储 + 推流
---
## 二、总体架构设计
### 2.1 系统逻辑架构
```text
[ media-server 主进程 ]
|
+-- Graph Manager (图管理加载配置、构建DAG、热更新/回滚)
|
+-- Plugin Loader (插件加载dlopen .so检查 ABI 版本)
|
+-- AiScheduler (统一调度 NPU 推理请求)
|
+-- Metrics & Logger (监控指标、日志)
|
+-- HTTP Server (REST API + Web 控制台 + 状态查询)
```
- **media-server**主入口负责初始化配置图构建主事件循环
- **Graph ManagerGraphMgr**
- 对应多个 graph每个 graph 一路/一组业务流程)。
- 实例化节点Node建立边Edge负责热更新时的 diff & 切换
- **Plugin Loader**
- 动态加载插件 `.so`校验节点类型和 ABI 版本
- **AiScheduler**
- 所有 AI 节点统一将推理任务提交到 AiScheduler由其管理 NPU 资源
- **HTTP Server**
- 对外提供图状态查询节点指标配置热更新触发等
### 2.2 数据流架构DAG
- **Graph**一个完整业务通道例如Camera 1 拉流AI报警+录像+推流”。
- **Node节点**功能基本单元由动态加载的 `.so` 实例实现
- **Edge**节点间的数据通道每条边对应一个 **SPSC单生产者单消费者环形队列**
- **多分支支持**一个节点可以有多个下游多条边实现一对多分支
示意
```text
[in] → [pre] → [ai_yolo] → [det_post] ───→ [alarm]
└──────→ [osd] → [storage]
└──→ [publish]
```
> 说明:`det_post` 是检测结果后处理节点(可选)。它会对 `frame->det` 做过滤/修正/二次验证,使 OSD 与告警读取到一致的“后处理结果”。
---
## 三、数据模型Frame & 元数据)
### 3.1 检测结果结构Detection & DetectionResult
> 约定:**所有 AI 节点写入此结构**OSD / 报警 等节点只读。
```cpp
struct Rect {
float x; // 左上角X像素或归一化坐标
float y; // 左上角Y
float w; // 宽
float h; // 高
};
struct Detection {
int cls_id; // 类别ID
float score; // 置信度 [0,1]
Rect bbox; // 检测框
int track_id; // 跟踪ID可选无则 -1 或 0
};
struct DetectionResult {
std::vector<Detection> items;
int img_w; // 推理输入宽
int img_h; // 推理输入高
std::string model_name; // 模型名/版本
};
```
**约定:**
- `bbox` 坐标标准在 PRD 中约定好建议像素坐标避免后续插件互相搞混
- class 映射表由 AI 节点自行维护OSD/报警通过 `cls_id` 做规则匹配
### 3.2 Frame 结构
```cpp
enum class PixelFormat {
NV12,
YUV420,
RGB,
BGR,
UNKNOWN
};
struct Frame {
int width;
int height;
PixelFormat format;
int dma_fd; // DMA-BUF FD无则为 -1
uint8_t* data; // CPU 内存指针可选有则指向内存buffer
uint64_t pts; // 时间戳(微秒)
uint64_t frame_id; // 单调递增ID
std::shared_ptr<DetectionResult> det; // AI 检测结果
std::shared_ptr<void> user_meta; // 扩展元数据(具体类型由插件间约定)
};
```
**约定:**
- **零拷贝优先**解码/前处理尽量通过 `dma_fd` 传递必要时才用 data
- 节点默认只读图像数据**可以更新 `det` / `user_meta`**。
- 所有队列中传递的都是 `std::shared_ptr<Frame>`实现多下游共享
---
## 四、插件模型与生命周期
### 4.1 节点通用字段Node 通用配置)
每个节点配置中推荐包含以下通用字段
```json
{
"id": "唯一节点ID",
"type": "节点类型,如 input_rtsp / ai_yolo",
"enable": true,
"role": "source|filter|sink", // 可选,但建议写上,便于校验
"queue": { // 可选:覆盖边上的默认队列策略
"size": 128,
"policy": "drop_oldest" // drop_oldest / drop_newest / block
},
"...特定节点参数..."
}
```
- `enable = false`GraphMgr 构建图时可直接忽略该节点及相关 edges
- `role` 主要用于配置校验source 不应有上游sink 不应有下游等)。
### 4.2 INode 接口(含 Drain
```cpp
// NodeStatus 用于描述处理结果
enum class NodeStatus {
OK, // 正常处理
DROP, // 丢弃该帧(例如不满足条件)
ERROR // 发生错误,需要记录日志
};
class INode {
public:
virtual ~INode() = default;
// 1. 初始化:解析 JSON 配置,申请静态资源
virtual bool Init(const Json::Value& config) = 0;
// 2. 启动:启动内部线程(如有),准备接收/发送数据
virtual bool Start() = 0;
// 3. 处理数据GraphMgr 从队列中取到 Frame 后调用
// - 对 filter/sink 节点:正常消费上游 Frame
// - 对 source 节点:通常可忽略(见下文约定)
virtual NodeStatus Process(std::shared_ptr<Frame> frame) = 0;
// 4. 动态配置更新支持热更参数如改变报警阈值、bitrate
// 返回 true 表示就地更新成功,无需重建节点
virtual bool UpdateConfig(const Json::Value& new_config) { return false; }
// 5. Drain停止前**不再有新输入**,但需处理完内部队列/缓冲数据
// 如storage 完成当前切片写入publish 发送剩余帧等
virtual void Drain() {}
// 6. 停止释放资源线程、句柄、内存等Drain 之后调用
virtual void Stop() = 0;
};
```
### 4.3 插件导出与 ABI 版本
```cpp
#define ABI_VERSION 20250101
#define REGISTER_NODE(TYPE_NAME, CLASS_NAME) \
extern "C" { \
INode* CreateNode() { \
return new CLASS_NAME(); \
} \
const char* GetNodeType() { \
return TYPE_NAME; \
} \
int GetAbiVersion() { \
return ABI_VERSION; \
} \
const char* GetNodeDescription() { \
return "node: " TYPE_NAME; \
} \
}
```
GraphMgr 在加载插件 `.so`
- 调用 `GetAbiVersion()` 校验兼容性
- 通过 `GetNodeType()` 找到对应 `type` 的构造函数
### 4.4 Source 节点约定
为避免把所有采集逻辑都硬塞进 `Process()`PRD 中统一约定
- `role == "source"` 的节点 `input_rtsp` / `input_file`
- `Start()` 中启动**内部采集线程**。
- 线程中从外部拉流/读文件构造 `Frame`通过框架提供的 `FrameEmitter` 写入下游队列
- `Process()` 可以是空实现或仅做心跳/监控
- `role != "source"` 的节点
- GraphMgr 驱动 `Process(frame)`消费上游队列元素
GraphMgr 负责
- 对非 source 节点循环从对应队列取 `Frame` 调用 `Process()`
- 对所有节点在热更新 / 停止时控制 `Drain()` `Stop()` 的调用顺序
---
## 五、配置系统Config System
整个系统由 `config.json` 驱动主要包含三层
- `global`全局配置
- `templates`pipeline 模板复用节点与拓扑
- `instances` / `graphs`具体业务实例通道
### 5.1 顶层结构示例
```json
{
"global": {
"plugin_path": "/usr/lib/rknodes",
"log_level": "info",
"metrics_port": 9000
},
"templates": { ... },
"instances": [ ... ],
"graphs": [ ... ] // 可选直接手写graph
}
```
- **优先方式**使用 `templates + instances` 快速生成多路通道
- **高级用法**直接使用 `graphs` 写复杂拓扑例如一条流拆成多个完全不同的分支)。
### 5.2 DAG 多分支完整示例(带报警 + 存储 + 推流)
```json
{
"graphs": [
{
"name": "cam1_pipeline",
"nodes": [
{
"id": "in_cam1",
"type": "input_rtsp",
"role": "source",
"enable": true,
"url": "rtsp://user:pwd@ip/stream1"
},
{
"id": "pre_cam1",
"type": "preprocess",
"role": "filter",
"enable": true,
"dst_w": 640,
"dst_h": 640,
"keep_ratio": true
},
{
"id": "ai_cam1",
"type": "ai_yolo",
"role": "filter",
"enable": true,
"model_path": "/models/yolov8n.rknn",
"conf": 0.5,
"nms": 0.5,
"class_filter": [0, 1]
},
{
"id": "pp_cam1",
"type": "det_post",
"role": "filter",
"enable": true,
"default": { "enable": false },
"per_class": [
{
"class_id": 0,
"enable": true,
"processors": [
{ "type": "conf_gate", "conf_min": 0.65 },
{ "type": "bbox_gate", "min_w": 10, "min_h": 10 }
]
}
]
},
{
"id": "osd_cam1",
"type": "osd",
"role": "filter",
"enable": true,
"draw_bbox": true,
"draw_text": true
},
{
"id": "alarm_cam1",
"type": "alarm",
"role": "sink",
"enable": true,
"rules": [
{
"name": "intrusion_day",
"object": ["person"],
"roi": "roi1",
"min_duration_ms": 500,
"schedule": "08:00-20:00"
}
]
},
{
"id": "storage_cam1",
"type": "storage",
"role": "sink",
"enable": true,
"mode": "continuous",
"segment_sec": 300,
"path": "/rec/cam1"
},
{
"id": "publish_cam1",
"type": "publish",
"role": "sink",
"enable": true,
"codec": {
"video": "h264",
"bitrate_kbps": 2048,
"gop": 50
},
"outputs": [
{ "proto": "rtsp", "port": 8554, "path": "/live/cam1" },
{ "proto": "hls", "path": "/data/hls/cam1/index.m3u8", "segment_sec": 2 }
]
}
],
"edges": [
["in_cam1", "pre_cam1"],
["pre_cam1", "ai_cam1"],
["ai_cam1", "pp_cam1"],
["pp_cam1", "alarm_cam1"],
["pp_cam1", "osd_cam1"],
["osd_cam1", "storage_cam1"],
["osd_cam1", "publish_cam1"]
]
}
]
}
```
> 说明:如果不需要后处理:
> - **推荐**:保留 `pp_cam1` 节点(`enable:true`),并确保其不启用任何处理链(例如:`default.enable=false` 且 `per_class` 为空),此时 `det_post` 会作为轻量透传。
> - 若确实要移除该节点:需要**同时删除并重连拓扑**(例如改回 `ai_cam1 -> osd_cam1` 与 `ai_cam1 -> alarm_cam1`),仅设置 `enable=false` 会导致下游节点无输入而构图失败。
通过简单改动即可支持不同组合
- 不报警`alarm_cam1.enable = false` 或移除该节点/
- 只转码网关直接连 `["in_cam1", "publish_cam1"]`并禁用 AI/OSD/报警/storage
- 只录像`["in_cam1", "storage_cam1"]`。
### 5.3 模板templates与实例instances
#### 5.3.1 模板示例:标准安防流程
```json
"templates": {
"standard_security": {
"nodes": [
{ "id": "in", "type": "input_rtsp", "role": "source", "url": "${url}" },
{ "id": "pre", "type": "preprocess", "role": "filter", "dst_w": 640, "dst_h": 640 },
{ "id": "ai", "type": "ai_yolo", "role": "filter", "model_path": "/models/yolov8n.rknn", "conf": 0.5 },
{ "id": "pp", "type": "det_post", "role": "filter", "enable": true, "default": { "enable": false } },
{ "id": "osd", "type": "osd", "role": "filter" },
{ "id": "pub", "type": "publish", "role": "sink", "path": "/live/${name}" }
],
"edges": [
["in", "pre"],
["pre", "ai"],
["ai", "pp"],
["pp", "osd"],
["osd", "pub"]
]
}
}
```
#### 5.3.2 实例与 override禁用某些节点
```json
"instances": [
{
"name": "cam_01",
"template": "standard_security",
"params": {
"url": "rtsp://192.168.1.101/stream1",
"name": "office"
},
"override": {
"nodes": {
"ai": { "enable": true },
"osd": { "enable": true }
}
}
},
{
"name": "cam_02_only_transcode",
"template": "standard_security",
"params": {
"url": "rtsp://192.168.1.102/stream1",
"name": "transcoder"
},
"override": {
"nodes": {
"ai": { "enable": false },
"osd": { "enable": false }
}
}
}
]
```
> GraphMgr 在加载时将模板 + 实例 merge 成具体的 graph节点 ID 会按实例名做前缀防冲突)。
### 5.4 热更新与回滚(简要)
- 通过 inotify 监控 `config.json`
- 检测到变更时
1. 读取新配置 JSON Schema 校验
2. 构建新的图对象dry run包括插件加载与节点实例化
3. 与旧图做 diff
- `UpdateConfig` 的节点优先调用 `UpdateConfig`
- 需要重建的节点先创建新节点并预热如加载模型然后做 **边的原子切换**
4. 对旧节点依次调用 `Drain()` `Stop()`
5. 若任意步骤失败回滚到旧配置并输出错误日志
- 保留上一份有效配置支持手动回滚
---
## 六、功能模块定义(插件类型)
> 下表是“典型插件类型”,后续可以扩展更多。
### 6.1 输入与前处理
| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 |
| :------------- | :------------------------------- | :------------------------- | :--------------- |
| `input_rtsp` | 拉取 RTSP 断线重连 | `url`, `reconnect_sec` | MPP VDEC / 网络 |
| `input_file` | 读取本地 MP4/MKV 文件循环播放 | `path`, `loop` | MPP VDEC |
| `preprocess` | 缩放裁剪NV12RGB/BGR | `dst_w`, `dst_h`, `format` | RGA / GPU |
### 6.2 AI 分析
| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 |
| :----------- | :------------------------- | :----------------------------------- | :------- |
| `ai_yolo` | 通用目标检测YOLOv5/v8 | `model_path`, `model_version`, `num_classes`, `conf`, `nms`, `class_filter` | NPU(RKNN) |
| `ai_pose` | 关键点检测可选扩展 | `model_path` | NPU |
| `ai_custom` | 自定义 AI 算法 | `model_path`, `custom_param` | NPU |
#### 6.2.1 det_post检测结果后处理按类别策略链
`det_post` 是一个 **Filter** 节点位于 `ai_yolo` 之后负责对 `frame->det->items` **按类别可配置**的后处理
**设计目标:**
- 不同类别可以挂不同的处理器链Processor/Strategy Registry避免一个类别一个插件”。
- 默认改写 `frame->det` `osd` / `alarm` 读到一致结果
**配置示例:**
```json
{
"id": "pp_cam1",
"type": "det_post",
"role": "filter",
"enable": true,
"default": { "enable": false },
"per_class": [
{
"class_id": 3,
"enable": true,
"processors": [
{ "type": "conf_gate", "conf_min": 0.65 },
{ "type": "bbox_gate", "min_w": 10, "min_h": 10 },
{ "type": "roi_gate", "roi": { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 }, "mode": "center" }
]
},
{
"class_id": 5,
"enable": true,
"processors": [
{ "type": "hsv_ratio", "h_min": 0, "h_max": 20, "s_min": 20, "v_min": 70, "ratio_min": 0.7,
"max_samples": 2048, "step": 0, "min_samples": 32,
"crop": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 } }
]
},
{
"class_id": 7,
"enable": true,
"processors": [
{ "type": "lab_kmeans", "k": 3, "iters": 8, "l_max": 50, "ratio_min": 0.4,
"max_samples": 2048, "step": 0, "min_samples": 64,
"crop": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 } }
]
}
]
}
```
**内置 processors当前实现**
- `conf_gate`按置信度过滤`conf_min`
- `bbox_gate`按框尺寸过滤`min_w`, `min_h`
- `roi_gate` ROI 过滤`roi:{x,y,w,h}` 归一化`mode:center` 使用 bbox 中心点判断
- `hsv_ratio`框内采样像素转 HSV统计阈值命中占比`h_min/h_max/s_min/s_max/v_min/v_max/ratio_min`支持 `crop/max_samples/step`
- `lab_kmeans`框内采样像素转 LabKMeans 聚类后取最暗簇做判定`k/iters/l_max/ratio_min`支持 `crop/max_samples/step`
> 注意:`hsv_ratio/lab_kmeans` 需要 `Frame` 提供可读的像素数据(`frame->data` 可访问)。若上游是纯 DMA-BUF 且未映射到 CPU这两类处理器会自然退化为“不过滤”。
**ai_yolo 参数说明:**
- `model_version`: `"v5"`, `"v8"` `"auto"`自动检测
- `num_classes`: 模型类别数默认 80
- `class_filter`: 只检测指定类别 ID 的数组 `[0, 1, 2]`
> 所有 AI 节点通过 **AiScheduler** 使用 NPU不直接持有 NPU 句柄。
### 6.3 业务逻辑与输出
| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 |
| :---------- | :--------------------------------- | :----------------------------------- | :----------------- |
| `osd` | 绘制检测框文字等 | `draw_bbox`, `draw_text`, `labels`, `line_width`, `font_scale` | RGA / CPU |
| `alarm` | 报警规则 + 多种报警动作 | `rules`, `actions`见下文详细说明 | CPU |
| `storage` | 持续录像MP4/TS 切片 | `mode`, `segment_sec`, `path` | MPP VENC + IO |
| `publish` | 编码并推流RTSP/HLS | `codec`, `bitrate`, `outputs` | MPP VENC + 网络 |
| `zlm_http` | 内嵌 HTTP 文件服务 HLS 提供访问 | `root`, `prefix`, `port`, `ssl` | ZLMediaKit(mk_api) |
> 说明:当前实现中 `publish` 的 `proto:"hls"` 为**落盘生成 m3u8/ts 文件**`path` 应指向 `index.m3u8` 文件路径;若需要通过网络播放,建议新增 `zlm_http` 节点启动内嵌 HTTP并将 `root` 指向 HLS 父目录(如 `/data/hls`)。
**osd 参数说明:**
- `labels`: 自定义类别标签数组 `["cat", "dog", "bird"]`为空则使用 COCO 默认标签
### 6.4 alarm 插件详细说明
alarm 插件采用**模块化 Actions 架构**支持多种报警动作的灵活组合
**架构设计:**
```
┌─────────────────────────────────────────────────────────┐
│ AlarmNode │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ RuleEngine │ │ FrameRingBuffer (pre-event) │ │
│ │ - 类别过滤 │ │ - 保存最近 N 秒的帧 │ │
│ │ - ROI 检测 │ └──────────────────────────────────┘ │
│ │ - 持续时间 │ │
│ │ - 冷却控制 │ ┌──────────────────────────────────┐ │
│ └──────────────┘ │ AlarmActions (可配置) │ │
│ │ - LogAction → 日志输出 │ │
│ │ - HttpAction → HTTP 回调 │ │
│ │ - ExternalApiAction → 外部API报警 │ │
│ │ - SnapshotAction → 截图+上传 │ │
│ │ - ClipAction → 视频片段+上传 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
**完整配置示例:**
```json
{
"id": "alarm_cam1",
"type": "alarm",
"role": "sink",
"labels": ["矿体A", "矿体B", "异物"],
"rules": [
{
"name": "ore_detection",
"class_ids": [0, 1],
"objects": ["矿体A"],
"roi": { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 },
"min_duration_ms": 500,
"cooldown_ms": 5000,
"schedule": "08:00-20:00"
}
],
"actions": {
"log": { "enable": true, "level": "info" },
"http": {
"enable": true,
"url": "http://业务系统/api/alarm",
"timeout_ms": 3000,
"include_media_url": true
},
"external_api": {
"enable": false,
"getTokenUrl": "http://业务系统/api/getToken",
"putMessageUrl": "http://业务系统/api/putMessage",
"tenantCode": "32",
"channelNo": "${vod_channelNo}",
"timeout_ms": 3000,
"include_media_url": true,
"token_header": "X-Access-Token",
"token_json_path": "responseBody.token",
"token_cache_sec": 600
},
"snapshot": {
"enable": true,
"format": "jpg",
"quality": 85,
"upload": {
"type": "minio",
"endpoint": "http://minio:9000",
"bucket": "alarms",
"access_key": "${MINIO_ACCESS_KEY}",
"secret_key": "${MINIO_SECRET_KEY}"
}
},
"clip": {
"enable": true,
"pre_sec": 5,
"post_sec": 10,
"format": "mp4",
"upload": { "type": "local", "path": "/tmp/alarms" }
}
}
}
```
**external_api 动作说明(对接外部系统报警:先取 token再上报告警**
- token `getTokenUrl` 发起 HTTP POST默认从响应 JSON `responseBody.token` 读取可用 `token_json_path` 修改点号路径)。
- 上报告警 `putMessageUrl` 发起 HTTP POST并携带 Header`Content-Type: application/json`、`X-Access-Token: <token>`(可用 `token_header` 修改)。
- 上报请求体(固定结构):
```json
{
"tenantCode": "32",
"channelNo": "<vod_channelNo>",
"alarmContent": "<msg>",
"alarmTime": "YYYY-MM-DD HH:MM:SS",
"picInfo": [{"url": "<picUrl>"}],
"videoInfo": [{"url": "<videoUrl>"}]
}
```
- `alarmTime` 使用本地时间格式化。
- `picUrl/videoUrl` 来自 `snapshot/clip` 动作上传后的 URL通常为 MinIO 返回链接);若为空则对应数组为空。
- `alarmContent`:若配置中未提供 `alarmContent`,默认使用 `rule_name`
**external_api 参数说明:**
- 必填:`getTokenUrl`, `putMessageUrl`, `tenantCode`, `channelNo`
- 常用可选:`timeout_ms`(默认 3000、`include_media_url`(默认 true、`token_cache_sec`(默认 600为 0 时仅在 401/403 时刷新 token
- 其它可选:`token_header`、`token_json_path`、`alarmContent`、`max_queue_size` / `queue_policy`、`max_retries` / `retry_backoff_ms`
**规则参数说明:**
- `class_ids`: 要监控的类别 ID 数组(与模型输出对应)
- `objects`: 要监控的类别名称数组(需配合 `labels` 使用)
- `roi`: 感兴趣区域,归一化坐标 (0-1)
- `min_duration_ms`: 目标在 ROI 内持续多久才触发报警
- `cooldown_ms`: 同一规则连续触发的冷却时间
- `schedule`: 有效时间段,格式 "HH:MM-HH:MM"
**上传器类型:**
- `local`: 保存到本地文件系统
- `minio`: 上传到 MinIO 对象存储
- `s3`: 上传到 AWS S3兼容 MinIO
### 6.5 storage 插件详细说明
storage 插件用于**持续录像**7x24 小时),与 alarm 的事件录像功能互补。
**配置示例:**
```json
{
"id": "storage_cam1",
"type": "storage",
"role": "sink",
"mode": "continuous",
"format": "mp4",
"codec": "h264",
"segment_sec": 300,
"path": "/rec/cam1",
"filename_pattern": "%Y%m%d/%H%M%S",
"fps": 25,
"bitrate_kbps": 2000
}
```
**参数说明:**
- `mode`: 录像模式,当前支持 `continuous`(持续录像)
- `format`: 输出格式,`mp4` 或 `ts`
- `segment_sec`: 单个文件时长(秒)
- `filename_pattern`: 文件名模式,支持 strftime 格式
---
## 七、线程模型Thread Model
### 7.1 线程类型与职责
| 线程类型 | 职责 | 绑核策略(可配置) |
| :-------------- | :-------------------------------------- | :-------------------------- |
| `main` | 程序入口、配置加载、GraphMgr 调度 | 默认 CPU0 |
| `input-io` | RTSP 网络 IO | 大核/通用 |
| `decode` | 调用 MPP 解码 | 大核 |
| `preprocess` | RGA/GPU 前处理 | 视情况绑核 |
| `ai-worker` | 调度 AiScheduler异步调用 NPU | 大核 |
| `post/osd` | OSD 绘制、报警逻辑等 | 小核优先 |
| `storage` | 文件写入、切片管理 | 小核 |
| `publish` | RTSP/HLS 推流 | 小核 |
| `metrics/log` | 指标采集、日志异步写入 | 任意核 |
### 7.2 配置控制
- 节点配置中可增加字段:
```json
"cpu_affinity": [2, 3] // 可选
```
- 若未配置,由框架根据 `role` 和负载自动分配。
---
## 八、性能指标Performance Targets
| 场景 | 指标 | 目标值 |
| :------------- | :------------- | :--------------------- |
| 8×1080p@25fps | 整机总处理帧率 | ≥ 200 fps |
| 单路端到端 | 延迟 | ≤ 500 ms典型 ≤ 380 |
| NPU | 利用率 | ≥ 90% |
| 8 路全开 | CPU 占用 | ≤ 30% |
| 7×24 小时运行 | 内存泄漏 | 无明显增长(< 10MB |
测试要求简要
- 在实际 RK3588 设备上使用典型 YOLO 模型和 8 RTSP 输入进行长稳测试
- 统计长时间运行中的 CPU内存NPU 使用情况
---
## 九、监控与运维Metrics & Ops
### 9.1 节点级指标
每个节点提供以下基本指标通过 HTTP / Prometheus 等暴露
- `node_input_fps` / `node_output_fps`
- `queue_length`入队/出队队列长度
- `frame_drop_count`
- `error_count`
- `avg_process_time_ms`Process 平均处理耗时
### 9.2 图 / 通道级指标
- 通道状态running / error / disabled
- 报警次数 / 最近报警时间
- 当前推流客户端数RTSP/HLS
- 当前录像文件信息路径 / 大小 / 最近片段时间
### 9.3 管理接口REST 简要)
- `GET /api/graphs`列出所有 graph 状态
- `GET /api/graphs/{name}`单通道详情
- `POST /api/config/reload`触发配<EFBFBD><EFBFBD><EFBFBD>重加载
- `GET /api/nodes/{id}/metrics`查询节点指标
- `POST /api/nodes/{id}/config`动态更新节点配置调用 `UpdateConfig`
---
## 十、开发与部署环境
- **硬件平台**RK35884×A76 + 4×A55 + NPU + VPU + RGA
- **工具链**
- 板上编译GCC 10+推荐
- 交叉编译`aarch64-linux-gnu-gcc 12+`可选
- 构建CMake 3.20
- **调试**
- `gdbserver` + VSCode 远程调试
- RKNN Profiler 分析 NPU 利用率
- **日志**
- `spdlog` 异步日志
- 日志级别可运行时调整通过配置或 REST 接口
### 10.1 板上编译(推荐)
RK3588 板子上直接编译无需配置交叉编译环境
**依赖安装:**
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install -y build-essential cmake git pkg-config \
libavformat-dev libavcodec-dev libavutil-dev libswscale-dev
```
**编译命令:**
```bash
cd Rk3588Sys
# 配置(启用所有硬件加速)
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DRK3588_ENABLE_FFMPEG=ON \
-DRK3588_ENABLE_MPP=ON \
-DRK3588_ENABLE_RGA=ON \
-DRK3588_ENABLE_ZLMEDIAKIT=ON \
-DRK_ZLMK_API_LIB_PATH=$PWD/third_party/rknpu2/examples/3rdparty/zlmediakit/aarch64/libmk_api.so \
-DRK_ZLMEDIAKIT_INCLUDE_DIR=$PWD/third_party/rknpu2/examples/3rdparty/zlmediakit/include
# 编译
cmake --build build -j$(nproc)
```
**CMake 选项说明:**
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `RK3588_ENABLE_FFMPEG` | 启用 FFmpeg 拉流/软解码 | OFF |
| `RK3588_ENABLE_MPP` | 启用 MPP 硬件编解码 | OFF |
| `RK3588_ENABLE_RGA` | 启用 RGA 硬件图像处理 | OFF |
| `RK3588_ENABLE_RKNN` | 启用 RKNN NPU 推理 | OFF |
| `RK3588_ENABLE_ZLMEDIAKIT` | 启用内置 RTSP Server | OFF |
**运行:**
```bash
# 纯转码网关
./build/media-server --config configs/sample_cam1.json
# 带预处理的 pipeline
./build/media-server --config configs/sample_preprocess.json
# 内置 RTSP Server 模式
./build/media-server --config configs/sample_cam1_rtsp_server.json
```
### 10.2 交叉编译(可选)
x86 主机上交叉编译适合 CI/CD 或批量部署
```bash
# 需要安装交叉编译工具链
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# 配置
cmake -S . -B build-cross \
-DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DRK3588_ENABLE_FFMPEG=ON \
-DRK3588_ENABLE_MPP=ON \
-DRK3588_ENABLE_RGA=ON
# 编译
cmake --build build-cross -j$(nproc)
# 同步到板子
rsync -avz build-cross/ user@board_ip:/opt/media-server/
```
---
## 十一、扩展与演进
1. **新增算法插件**
- 实现新的 `INode` 子类例如 `ai_cls`, `ai_reid`)。
- 使用 `REGISTER_NODE` 编译为 `.so` 放入 `plugin_path`
- `config.json` 中新增节点即可接入全流程
2. **新增输出类型**
- 新增 `publish_webrtc`、`storage_s3` 等节点类型
- 不改动上游图结构只调整 sink 部分
3. **分布式扩展(后续版本)**
- 引入 ZeroMQ / NanoMSG将远端设备作为特殊节点处理 `remote_sink`)。
- 中控只维护抽象图实际在多台 RK3588 设备上分布执行
---