diff --git a/configs/stress_5ch_face_recog.json b/configs/stress_5ch_face_recog.json new file mode 100644 index 0000000..098ab74 --- /dev/null +++ b/configs/stress_5ch_face_recog.json @@ -0,0 +1,340 @@ +{ + "global": { + "metrics_port": 9000, + "web_root": "web" + }, + "queue": { + "size": 4, + "strategy": "drop_oldest" + }, + "templates": { + "face_recog_pipeline": { + "nodes": [ + { + "id": "in", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "${url}", + "fps": "${fps}", + "width": "${src_w}", + "height": "${src_h}", + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "${name}", + "use_rga": true + }, + { + "id": "ai", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/best-640.rknn", + "model_version": "v8", + "num_classes": 3, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { + "accept": 0.45, + "margin": 0.05 + }, + "gallery": { + "backend": "sqlite", + "path": "${face_gallery_path}", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "${name}", + "track_classes": [0, 1, 2], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { + "stats": false, + "stats_interval": 200 + } + }, + { + "id": "osd", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "line_width": 2, + "font_scale": 1, + "labels": [], + "use_rga_bbox": false, + "draw_face_det": true, + "draw_face_bbox": true + }, + { + "id": "post", + "type": "preprocess", + "role": "filter", + "enable": true, + "queue": { + "size": 2, + "policy": "drop_oldest" + }, + "dst_w": "${src_w}", + "dst_h": "${src_h}", + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "${name}", + "use_rga": true + }, + { + "id": "alarm", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "object_in_view", + "class_ids": [0, 1, 2], + "roi": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0}, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "actions": { + "log": {"enable": true, "level": "info"}, + "snapshot": { + "enable": true, + "min_interval_ms": 15000, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "${minio_endpoint}", + "bucket": "${minio_bucket}", + "region": "us-east-1", + "access_key": "${minio_ak}", + "secret_key": "${minio_sk}" + } + }, + "clip": { + "enable": true, + "min_interval_ms": 15000, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": "${fps}", + "upload": { + "type": "minio", + "endpoint": "${minio_endpoint}", + "bucket": "${minio_bucket}", + "region": "us-east-1", + "access_key": "${minio_ak}", + "secret_key": "${minio_sk}" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": true, + "getTokenUrl": "http://10.0.0.49:8080/api/getToken", + "putMessageUrl": "http://10.0.0.49:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${name}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "pub", + "type": "publish", + "role": "filter", + "enable": true, + "queue": {"size": 2, "policy": "drop_oldest"}, + "codec": "h264", + "fps": "${fps}", + "gop": "${gop}", + "bitrate_kbps": "${bitrate_kbps}", + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + {"proto": "rtsp_server", "port": 8555, "path": "/live/${name}"}, + {"proto": "hls", "path": "./web/hls/${name}/index.m3u8", "segment_sec": 2} + ], + "debug": {"stats": true, "stats_interval": 100} + } + ], + "edges": [ + ["in", "pre"], + ["pre", "ai"], + ["ai", "trk"], + ["trk", "face_det"], + ["face_det", "face_recog"], + ["face_recog", "osd"], + ["osd", "post"], + ["post", "pub"], + ["pub", "alarm"] + ] + } + }, + "instances": [ + { + "name": "cam1", + "template": "face_recog_pipeline", + "params": { + "name": "cam1", + "url": "rtsp://10.0.0.49:8554/cam", + "src_w": 1280, + "src_h": 720, + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "face_gallery_path": "./models/face_gallery_cam1.db", + "minio_endpoint": "http://10.0.0.49:9000", + "minio_bucket": "myminio", + "minio_ak": "minioadmin", + "minio_sk": "minioadmin" + } + }, + { + "name": "cam2", + "template": "face_recog_pipeline", + "params": { + "name": "cam2", + "url": "rtsp://10.0.0.49:8554/cam", + "src_w": 1280, + "src_h": 720, + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "face_gallery_path": "./models/face_gallery_cam2.db", + "minio_endpoint": "http://10.0.0.49:9000", + "minio_bucket": "myminio", + "minio_ak": "minioadmin", + "minio_sk": "minioadmin" + } + }, + { + "name": "cam3", + "template": "face_recog_pipeline", + "params": { + "name": "cam3", + "url": "rtsp://10.0.0.49:8554/cam", + "src_w": 1280, + "src_h": 720, + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "face_gallery_path": "./models/face_gallery_cam3.db", + "minio_endpoint": "http://10.0.0.49:9000", + "minio_bucket": "myminio", + "minio_ak": "minioadmin", + "minio_sk": "minioadmin" + } + }, + { + "name": "cam4", + "template": "face_recog_pipeline", + "params": { + "name": "cam4", + "url": "rtsp://10.0.0.49:8554/cam", + "src_w": 1280, + "src_h": 720, + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "face_gallery_path": "./models/face_gallery_cam4.db", + "minio_endpoint": "http://10.0.0.49:9000", + "minio_bucket": "myminio", + "minio_ak": "minioadmin", + "minio_sk": "minioadmin" + } + }, + { + "name": "cam5", + "template": "face_recog_pipeline", + "params": { + "name": "cam5", + "url": "rtsp://10.0.0.49:8554/cam", + "src_w": 1280, + "src_h": 720, + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "face_gallery_path": "./models/face_gallery_cam5.db", + "minio_endpoint": "http://10.0.0.49:9000", + "minio_bucket": "myminio", + "minio_ak": "minioadmin", + "minio_sk": "minioadmin" + } + } + ] +} diff --git a/docs/deployment.md b/docs/deployment.md index e747762..1949553 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -3,20 +3,40 @@ ## 架构说明 ``` -┌─────────────────┐ ┌─────────────────┐ -│ Media Server │ │ RK3588 Agent │ -│ (systemd) │◄────│ (systemd) │ -│ 高可用独立服务 │ │ 监控/控制 │ -└─────────────────┘ └─────────────────┘ - │ │ - ▼ ▼ - RTSP 输出流 Web 管理界面 - HLS 输出流 http://IP:9100 +┌─────────────────────────────────────────────────────────────┐ +│ RK3588 Device │ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ Media Server │ │ RK3588 Agent │ │ +│ │ (systemd) │◄────│ (systemd) │ │ +│ │ │ │ │ │ +│ │ - RTSP输出 │ │ - Web管理界面 │ │ +│ │ - HLS输出 │ │ - 服务监控 │ │ +│ │ - NPU推理 │ │ - 远程配置 │ │ +│ └─────────┬───────────┘ └─────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ 运行时数据目录 │ │ +│ │ /var/lib/rk3588-media-server/ │ │ +│ │ ├── hls/ - HLS分片输出 │ │ +│ │ ├── logs/ - 应用日志 │ │ +│ │ ├── alarms/ - 告警截图 │ │ +│ │ └── clips/ - 告警视频片段 │ │ +│ └───────────────────────────────────────────────────┘ │ +│ │ +│ /opt/rk3588-media-server/ # 程序文件 │ +│ /opt/rk3588-agent/ # Agent程序 │ +└─────────────────────────────────────────────────────────────┘ ``` +### 设计特点 + - **Media Server**: 独立的 systemd 服务,高可用(崩溃自动重启) - **Agent**: 独立的 systemd 服务,通过 `systemctl` 监控和控制 Media Server - **两者可独立重启**: 重启 Agent 不会影响 Media Server +- **运行时数据分离**: 程序文件在 `/opt/`,数据在 `/var/lib/` +- **自动清理**: HLS 分片、日志、告警数据自动轮转清理 --- @@ -34,47 +54,188 @@ mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) -# 编译 Agent +# 编译 Agent(可选) cd ../agent go build -o rk3588-agent_linux_arm64 ./cmd/rk3588-agent ``` -### 2. 安装 +### 2. 部署 ```bash cd ~/apps/OrangePi3588Media -sudo ./scripts/manage.sh install +sudo ./scripts/deploy.sh install ``` -安装过程会提示选择**配置文件**: -- `test_cam1_face_det_rtsp_server.json` - 人脸检测(推荐) -- `sample_cam1_native_rtsp_face.json` - 完整功能 -- `test_cam1_face_det_recog_rtsp_server.json` - 人脸识别 -- `sample_cam1.json` - 完整功能(需手动修复) +部署过程会: +1. 安装 Media Server 和 Agent 到 `/opt/` +2. 创建运行时数据目录 `/var/lib/rk3588-media-server/` +3. 配置日志轮转和自动清理 +4. 启动 systemd 服务 ### 3. 查看状态 ```bash -sudo ./scripts/manage.sh status +sudo ./scripts/deploy.sh status ``` -显示内容: -- 系统运行时间 -- NPU 负载 -- Media Server 状态(PID、CPU/内存、端口) -- Agent 状态 -- 最近日志 - ### 4. 卸载 ```bash -sudo ./scripts/manage.sh uninstall +sudo ./scripts/deploy.sh uninstall +``` + +--- + +## 完整命令参考 + +### 部署脚本命令 + +| 命令 | 说明 | +|------|------| +| `sudo ./deploy.sh install` | 安装/部署服务 | +| `sudo ./deploy.sh upgrade` | 升级服务(保留配置和数据)| +| `sudo ./deploy.sh status` | 查看详细运行状态 | +| `sudo ./deploy.sh logs` | 查看实时日志 | +| `sudo ./deploy.sh clean-hls [天数]` | 清理 HLS 旧分片 | +| `sudo ./deploy.sh uninstall` | 卸载服务 | + +### Media Server 服务管理 + +```bash +# 启动/停止/重启 +sudo systemctl start media-server +sudo systemctl stop media-server +sudo systemctl restart media-server + +# 查看状态 +sudo systemctl status media-server + +# 查看日志 +sudo journalctl -u media-server -f +sudo journalctl -u media-server --since "1 hour ago" +``` + +### Agent 服务管理 + +```bash +# 启动/停止/重启(不影响 Media Server) +sudo systemctl start rk3588-agent +sudo systemctl stop rk3588-agent +sudo systemctl restart rk3588-agent + +# 查看状态 +sudo systemctl status rk3588-agent + +# 查看日志 +sudo journalctl -u rk3588-agent -f +``` + +--- + +## 目录结构 + +### 程序目录 (/opt/) + +``` +/opt/rk3588-media-server/ +├── bin/ +│ ├── media-server # 主程序 +│ ├── cleanup-hls.sh # HLS清理脚本 +│ └── plugins/ # 插件目录 +│ ├── libinput_rtsp.so +│ ├── libai_yolo.so +│ ├── libosd.so +│ ├── libtracker.so +│ └── ... +├── lib/ # 依赖库 +├── etc/ +│ └── media-server.json # 配置文件 +├── models/ # AI模型文件 +│ ├── best-640.rknn +│ ├── yolov5s-640-640.rknn +│ └── ... +└── web/ # Web静态文件 + ├── index.html + ├── graph.html + ├── hls_player.html # HLS播放器 + └── hls/ # HLS输出(符号链接) + +/opt/rk3588-agent/ +├── rk3588-agent # Agent程序 +└── agent.config.json # Agent配置 +``` + +### 运行时数据目录 (/var/lib/) + +``` +/var/lib/rk3588-media-server/ +├── hls/ # HLS直播分片 +│ ├── cam1/ +│ │ ├── index.m3u8 +│ │ ├── index0.ts +│ │ └── ... +│ ├── cam2/ +│ └── ... +├── logs/ # 应用日志 +├── alarms/ # 告警截图 +└── clips/ # 告警视频片段 + +/var/lib/rk3588-agent/ +└── device_id # 设备唯一标识 +``` + +--- + +## 配置修改 + +编辑配置文件: + +```bash +sudo nano /opt/rk3588-media-server/etc/media-server.json +``` + +修改后重启服务: + +```bash +sudo systemctl restart media-server +``` + +--- + +## 数据管理 + +### HLS 分片清理 + +HLS 分片自动清理策略: +- **默认保留**: 3天 +- **自动执行**: 每天凌晨3点 +- **手动执行**: `sudo /opt/rk3588-media-server/bin/cleanup-hls.sh [天数]` + +### 日志轮转 + +日志自动轮转策略: +- **保留周期**: 7天 +- **压缩**: 启用 +- **位置**: `/var/lib/rk3588-media-server/logs/` + +### 告警数据清理 + +自动清理策略(通过 cron): +- **告警视频** (clips): 保留30天 +- **告警图片** (alarms): 保留90天 + +查看当前存储使用: + +```bash +sudo ./scripts/deploy.sh status ``` --- ## 升级 +升级步骤(保留配置和数据): + ```bash cd ~/apps/OrangePi3588Media @@ -89,46 +250,16 @@ make -j$(nproc) cd ../agent go build -o rk3588-agent_linux_arm64 ./cmd/rk3588-agent -# 3. 重新安装 +# 3. 执行升级 cd .. -sudo ./scripts/manage.sh install +sudo ./scripts/deploy.sh upgrade ``` --- -## 管理命令 +## Web 管理界面 -### Media Server(独立服务) - -```bash -# 启动/停止/重启 -sudo systemctl start media-server -sudo systemctl stop media-server -sudo systemctl restart media-server - -# 查看状态 -sudo systemctl status media-server - -# 查看日志 -sudo journalctl -u media-server -f -``` - -### Agent(独立服务) - -```bash -# 启动/停止/重启(不影响 Media Server) -sudo systemctl start rk3588-agent -sudo systemctl stop rk3588-agent -sudo systemctl restart rk3588-agent - -# 查看状态 -sudo systemctl status rk3588-agent - -# 查看日志 -sudo journalctl -u rk3588-agent -f -``` - -### Web 管理界面 +### Agent 管理界面 ``` http://<设备IP>:9100 @@ -136,45 +267,20 @@ http://<设备IP>:9100 功能: - 查看 Media Server 运行状态 -- 启动/停止/重启 Media Server(通过 systemctl) +- 启动/停止/重启 Media Server - 查看配置信息 ---- - -## 配置修改 - -编辑配置文件: - -```bash -sudo nano /opt/rk3588-media-server/etc/media-server.json -``` - -修改后重启 Media Server: - -```bash -sudo systemctl restart media-server -``` - ---- - -## 目录结构 +### HLS 播放器 ``` -/opt/rk3588-media-server/ -├── bin/ -│ ├── media-server # 主程序 -│ └── plugins/ # 插件 -├── etc/ -│ └── media-server.json # 配置文件 -├── models/ # AI 模型 -├── logs/ # 日志 -└── web/hls/ # HLS 输出 - -/opt/rk3588-agent/ -├── rk3588-agent # Agent 程序 -└── agent.config.json # Agent 配置 +http://<设备IP>:9000/hls_player.html ``` +功能: +- 5 路视频同时播放 +- 自适应布局 +- 实时状态显示 + --- ## 故障排查 @@ -182,34 +288,113 @@ sudo systemctl restart media-server ### 服务无法启动 ```bash -# Media Server 错误 +# 查看详细错误 sudo journalctl -u media-server -n 50 -# Agent 错误 -sudo journalctl -u rk3588-agent -n 50 +# 检查配置文件语法 +sudo cat /opt/rk3588-media-server/etc/media-server.json | python3 -m json.tool + +# 检查目录权限 +ls -la /var/lib/rk3588-media-server/ +ls -la /opt/rk3588-media-server/ ``` -### 检查 NPU +### NPU 问题 ```bash -cat /sys/kernel/debug/rknpu/load -ls /dev/rknpu +# 查看 NPU 状态 +cat /proc/rknpu/load +cat /proc/rknpu/version + +# 查看设备节点 +ls -la /dev/rknpu* + +# 查看温度 +cat /sys/class/thermal/thermal_zone6/temp ``` -### RTSP 测试 +### HLS 无法播放 + +```bash +# 检查 HLS 输出目录 +ls -la /var/lib/rk3588-media-server/hls/ + +# 检查分片生成 +ls -la /var/lib/rk3588-media-server/hls/cam1/ + +# 检查服务端口 +ss -tlnp | grep 9000 +``` + +### RTSP 无法连接 ```bash # 本地测试 -ffplay rtsp://localhost:8555/live/cam1_face_det +ffplay rtsp://localhost:8555/live/cam1 -# 远程测试 -ffplay rtsp://<设备IP>:8555/live/cam1_face_det +# 检查端口 +ss -tlnp | grep 8555 + +# 查看编码器状态 +grep "RKVENC" /proc/mpp_service/sessions-summary ``` --- ## 系统要求 -- **硬件**: Orange Pi 5 Plus (RK3588) -- **系统**: Ubuntu 22.04 LTS (ARM64) +- **硬件**: Orange Pi 5 Plus (RK3588) 或其他 RK3588 设备 +- **系统**: Ubuntu 22.04 LTS (ARM64) 或 Debian 11 +- **内存**: 建议 4GB 以上 +- **存储**: 建议 32GB 以上(HLS 分片占用空间) - **依赖**: RKNN Runtime, MPP, FFmpeg + +--- + +## 性能参考 + +5 路 720p@30fps 并发处理: + +| 资源 | 使用率 | 备注 | +|------|--------|------| +| NPU Core0 | 70-80% | YOLO 推理 | +| CPU | 20-30% | 预处理、编码 | +| 编码器 | 5 路 720p | H264 CBR 2Mbps | +| 温度 | 50-60°C | 正常范围 | +| 内存 | 3-4GB | 包含系统 | + +--- + +## 备份与恢复 + +### 备份配置和数据 + +```bash +# 创建备份 +BACKUP_DIR="/root/rk3588-backup-$(date +%Y%m%d)" +mkdir -p "$BACKUP_DIR" + +# 备份配置 +cp /opt/rk3588-media-server/etc/media-server.json "$BACKUP_DIR/" + +# 备份模型 +cp -r /opt/rk3588-media-server/models "$BACKUP_DIR/" + +# 备份 HLS(可选,占用空间大) +cp -r /var/lib/rk3588-media-server/hls "$BACKUP_DIR/" + +echo "备份完成: $BACKUP_DIR" +``` + +### 恢复 + +```bash +# 恢复配置 +cp "$BACKUP_DIR/media-server.json" /opt/rk3588-media-server/etc/ + +# 恢复模型 +cp -r "$BACKUP_DIR/models"/* /opt/rk3588-media-server/models/ + +# 重启服务 +sudo systemctl restart media-server +``` diff --git a/models/face_gallery_cam1.db b/models/face_gallery_cam1.db new file mode 100644 index 0000000..cd89af7 Binary files /dev/null and b/models/face_gallery_cam1.db differ diff --git a/models/face_gallery_cam2.db b/models/face_gallery_cam2.db new file mode 100644 index 0000000..cd89af7 Binary files /dev/null and b/models/face_gallery_cam2.db differ diff --git a/models/face_gallery_cam3.db b/models/face_gallery_cam3.db new file mode 100644 index 0000000..cd89af7 Binary files /dev/null and b/models/face_gallery_cam3.db differ diff --git a/models/face_gallery_cam4.db b/models/face_gallery_cam4.db new file mode 100644 index 0000000..cd89af7 Binary files /dev/null and b/models/face_gallery_cam4.db differ diff --git a/models/face_gallery_cam5.db b/models/face_gallery_cam5.db new file mode 100644 index 0000000..cd89af7 Binary files /dev/null and b/models/face_gallery_cam5.db differ diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..7d7956e --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,737 @@ +#!/bin/bash +# RK3588 Media Server + Agent 完善部署脚本 +# 部署到 /opt,包含完整的运行时目录、权限、日志轮转等 +# +# 用法: sudo ./deploy.sh [install|upgrade|status|logs|clean-hls|uninstall] + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# 目录定义 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$PROJECT_DIR/build" + +# 安装目录 +INSTALL_DIR="/opt/rk3588-media-server" +AGENT_INSTALL_DIR="/opt/rk3588-agent" +SERVICE_DIR="/etc/systemd/system" +LOGROTATE_DIR="/etc/logrotate.d" + +# 运行时数据目录(持久化数据) +RUNTIME_DIR="/var/lib/rk3588-media-server" +HLS_DIR="$RUNTIME_DIR/hls" +LOGS_DIR="$RUNTIME_DIR/logs" +ALARMS_DIR="$RUNTIME_DIR/alarms" +CLIPS_DIR="$RUNTIME_DIR/clips" + +# 创建必要的运行时目录 +create_runtime_dirs() { + echo -e "${CYAN}[创建运行时目录]${NC}" + + # 主运行时目录 + mkdir -p "$RUNTIME_DIR" + mkdir -p "$HLS_DIR" + mkdir -p "$LOGS_DIR" + mkdir -p "$ALARMS_DIR" + mkdir -p "$CLIPS_DIR" + + # 设置权限(root用户运行,但允许orangepi组读取) + chown -R root:root "$RUNTIME_DIR" + chmod 755 "$RUNTIME_DIR" + chmod 755 "$HLS_DIR" + chmod 755 "$LOGS_DIR" + chmod 755 "$ALARMS_DIR" + chmod 755 "$CLIPS_DIR" + + echo -e "${GREEN}✓${NC} 运行时目录创建完成:" + echo " HLS输出: $HLS_DIR" + echo " 日志: $LOGS_DIR" + echo " 告警图片: $ALARMS_DIR" + echo " 告警视频: $CLIPS_DIR" +} + +# 安装日志轮转配置 +install_logrotate() { + echo -e "${CYAN}[配置日志轮转]${NC}" + + # Media Server 日志轮转 + cat > "$LOGROTATE_DIR/rk3588-media-server" << 'EOF' +/var/lib/rk3588-media-server/logs/*.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + create 644 root root + sharedscripts + postrotate + /bin/kill -HUP $(cat /var/run/syslogd.pid 2>/dev/null) 2>/dev/null || true + endscript +} +EOF + + # HLS 分片清理(保留最近7天) + cat > "$LOGROTATE_DIR/rk3588-hls" << 'EOF' +/var/lib/rk3588-media-server/hls/*/index*.ts { + daily + rotate 1 + maxage 7 + missingok + notifempty + nocompress +} + +/var/lib/rk3588-media-server/hls/*/index*.m3u8 { + daily + rotate 1 + maxage 7 + missingok + notifempty + nocompress +} +EOF + + chmod 644 "$LOGROTATE_DIR/rk3588-media-server" + chmod 644 "$LOGROTATE_DIR/rk3588-hls" + + echo -e "${GREEN}✓${NC} 日志轮转配置完成" +} + +# 安装 HLS 清理脚本 +install_hls_cleanup() { + echo -e "${CYAN}[安装 HLS 清理脚本]${NC}" + + cat > "$INSTALL_DIR/bin/cleanup-hls.sh" << 'EOF' +#!/bin/bash +# HLS 分片清理脚本 - 保留最近 N 天的分片 + +KEEP_DAYS=${1:-3} +HLS_DIR="/var/lib/rk3588-media-server/hls" + +if [ ! -d "$HLS_DIR" ]; then + echo "HLS 目录不存在: $HLS_DIR" + exit 1 +fi + +# 清理旧的分片文件 +find "$HLS_DIR" -name "*.ts" -type f -mtime +$KEEP_DAYS -delete 2>/dev/null + +# 清理空的摄像头目录 +find "$HLS_DIR" -type d -empty -delete 2>/dev/null + +echo "$(date): HLS 清理完成 (保留最近 $KEEP_DAYS 天)" +EOF + + chmod +x "$INSTALL_DIR/bin/cleanup-hls.sh" + + # 添加到 crontab(每天凌晨3点执行) + CRON_FILE="/etc/cron.d/rk3588-media-server" + cat > "$CRON_FILE" << 'EOF' +# RK3588 Media Server 定时任务 +SHELL=/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# 每天凌晨3点清理HLS旧分片(保留3天) +0 3 * * * root /opt/rk3588-media-server/bin/cleanup-hls.sh 3 >> /var/lib/rk3588-media-server/logs/cleanup.log 2>&1 + +# 每周日凌晨4点清理告警视频(保留30天) +0 4 * * 0 root find /var/lib/rk3588-media-server/clips -name "*.mp4" -type f -mtime +30 -delete 2>/dev/null + +# 每周日凌晨4点清理告警图片(保留90天) +30 4 * * 0 root find /var/lib/rk3588-media-server/alarms -name "*.jpg" -type f -mtime +90 -delete 2>/dev/null +EOF + + chmod 644 "$CRON_FILE" + + echo -e "${GREEN}✓${NC} HLS 清理脚本安装完成" + echo " 清理策略:" + echo " - HLS分片: 保留3天" + echo " - 告警视频: 保留30天" + echo " - 告警图片: 保留90天" +} + +# 安装 Media Server +cmd_install_media_server() { + echo -e "${BLUE}[1/5] 安装 Media Server...${NC}" + + # 检查编译产物 + if [ ! -f "$BUILD_DIR/media-server" ]; then + echo -e "${RED}错误: 未找到编译好的 media-server${NC}" + echo "请先编译: cd $PROJECT_DIR && cmake --build build" + exit 1 + fi + + # 创建目录结构 + mkdir -p "$INSTALL_DIR/bin/plugins" + mkdir -p "$INSTALL_DIR/lib" + mkdir -p "$INSTALL_DIR/etc" + mkdir -p "$INSTALL_DIR/web" + mkdir -p "$INSTALL_DIR/models" + + # 复制二进制文件 + cp "$BUILD_DIR/media-server" "$INSTALL_DIR/bin/" + chmod +x "$INSTALL_DIR/bin/media-server" + + # 复制插件 + if [ -d "$BUILD_DIR/plugins" ]; then + cp "$BUILD_DIR"/plugins/*.so "$INSTALL_DIR/bin/plugins/" 2>/dev/null || true + fi + + # 复制依赖库 + if ls "$BUILD_DIR"/*.so 1> /dev/null 2>&1; then + cp "$BUILD_DIR"/*.so "$INSTALL_DIR/lib/" 2>/dev/null || true + fi + + # 复制 web 静态文件 + if [ -d "$PROJECT_DIR/web" ]; then + cp -r "$PROJECT_DIR/web"/* "$INSTALL_DIR/web/" 2>/dev/null || true + fi + + # 复制模型文件 + if [ -d "$PROJECT_DIR/models" ]; then + cp -r "$PROJECT_DIR/models"/* "$INSTALL_DIR/models/" 2>/dev/null || true + fi + if [ -d "$PROJECT_DIR/third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588" ]; then + cp "$PROJECT_DIR/third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588"/*.rknn "$INSTALL_DIR/models/" 2>/dev/null || true + fi + + # 选择并复制配置文件 + select_config + + # 修复配置文件路径 + fix_config_paths + + echo -e "${GREEN}✓${NC} Media Server 安装完成" +} + +# 选择配置文件 +select_config() { + echo -e "${CYAN}[选择配置文件]${NC}" + + # 查找所有可用的配置文件 + CONFIGS=() + for f in "$PROJECT_DIR/configs"/*.json; do + if [ -f "$f" ]; then + CONFIGS+=("$(basename "$f")") + fi + done + + echo "可用的配置文件:" + for i in "${!CONFIGS[@]}"; do + printf " %2d) %s\n" $((i+1)) "${CONFIGS[$i]}" + done + echo "" + + printf "请选择 [1-${#CONFIGS[@]},默认1]: " + read choice + choice=${choice:-1} + + if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#CONFIGS[@]}" ]; then + choice=1 + fi + + SELECTED_CONFIG="$PROJECT_DIR/configs/${CONFIGS[$((choice-1))]}" + cp "$SELECTED_CONFIG" "$INSTALL_DIR/etc/media-server.json" + + echo -e "${GREEN}✓${NC} 使用配置: ${CONFIGS[$((choice-1))]}" +} + +# 修复配置文件中的路径 +fix_config_paths() { + echo -e "${CYAN}[修复配置路径]${NC}" + + python3 << 'EOF' +import json +import os +import re + +config_file = "/opt/rk3588-media-server/etc/media-server.json" +install_dir = "/opt/rk3588-media-server" +runtime_dir = "/var/lib/rk3588-media-server" +project_dir = os.path.expanduser("~/apps/OrangePi3588Media") + +def fix_path(path): + """修复配置文件中的路径""" + if not path or not isinstance(path, str): + return path + + # 模型路径 -> 安装目录的 models + if 'third_party/rknpu2' in path or '.rknn' in path: + return os.path.join(install_dir, 'models', os.path.basename(path)) + + # HLS 输出路径 -> 运行时目录的 hls + if 'web/hls' in path or path.startswith('./web/hls'): + return os.path.join(runtime_dir, 'hls', os.path.basename(os.path.dirname(path)), 'index.m3u8') + + # web 根目录 -> 安装目录的 web + if path.startswith('./web/'): + return os.path.join(install_dir, path[2:]) + + # models 目录 -> 安装目录的 models + if path.startswith('./models/'): + return os.path.join(install_dir, path[2:]) + + # 其他相对路径 -> 尝试解析 + if path.startswith('./'): + abs_path = os.path.join(project_dir, path) + if os.path.exists(abs_path): + return abs_path + return os.path.join(install_dir, path[2:]) + + return path + +def process_node(node): + """处理节点配置""" + # 修复模型路径 + if 'model_path' in node: + node['model_path'] = fix_path(node['model_path']) + + # 修复图库路径 + if 'gallery' in node and isinstance(node['gallery'], dict) and 'path' in node['gallery']: + node['gallery']['path'] = fix_path(node['gallery']['path']) + + # 修复输出路径(HLS、报警图片等) + if 'outputs' in node and isinstance(node['outputs'], list): + for output in node['outputs']: + if 'path' in output: + output['path'] = fix_path(output['path']) + + # 修复 MinIO 端点(如果是本地) + if 'minio_endpoint' in node: + endpoint = node['minio_endpoint'] + if '127.0.0.1' in endpoint or 'localhost' in endpoint: + # 保持本地端点不变 + pass + + return node + +try: + with open(config_file, 'r') as f: + config = json.load(f) + + # 处理 graphs 格式 + if 'graphs' in config: + for graph in config['graphs']: + if 'nodes' in graph: + for node in graph['nodes']: + process_node(node) + + # 处理 templates + instances 格式 + if 'templates' in config and 'instances' in config: + for template_name, template in config['templates'].items(): + if 'nodes' in template: + for node in template['nodes']: + process_node(node) + + # 添加全局配置(如果不存在) + if 'global' not in config: + config['global'] = {} + + # 确保 web_root 指向正确位置 + config['global']['web_root'] = os.path.join(install_dir, 'web') + config['global']['metrics_port'] = config['global'].get('metrics_port', 9000) + + with open(config_file, 'w') as f: + json.dump(config, f, indent=2) + + print(f"配置路径修复完成") + +except Exception as e: + print(f"配置处理出错: {e}") + import traceback + traceback.print_exc() +EOF +} + +# 安装 Agent +cmd_install_agent() { + echo -e "${BLUE}[2/5] 安装 Agent...${NC}" + + # 查找 Agent 编译产物 + AGENT_SOURCE="" + if [ -f "$PROJECT_DIR/agent/rk3588-agent_linux_arm64" ]; then + AGENT_SOURCE="$PROJECT_DIR/agent/rk3588-agent_linux_arm64" + elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" ]; then + AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" + fi + + if [ -z "$AGENT_SOURCE" ]; then + echo -e "${YELLOW}警告: 未找到编译好的 Agent,跳过安装${NC}" + return + fi + + mkdir -p "$AGENT_INSTALL_DIR" + mkdir -p "/var/lib/rk3588-agent" + + cp "$AGENT_SOURCE" "$AGENT_INSTALL_DIR/rk3588-agent" + chmod +x "$AGENT_INSTALL_DIR/rk3588-agent" + + # Agent 配置 + cat > "$AGENT_INSTALL_DIR/agent.config.json" << EOF +{ + "agent": { + "listen": "0.0.0.0:9100", + "token": "$(openssl rand -hex 16)", + "require_token_for_read": false, + "discovery_enable": true, + "discovery_port": 35688, + "device_name": "rk3588_$(hostname)", + "device_id_path": "/var/lib/rk3588-agent/device_id", + "models_dir": "$INSTALL_DIR/models", + "max_upload_mb": 200, + "config_path": "$INSTALL_DIR/etc/media-server.json", + "media_server_process": { + "enable": false + }, + "media_server_base_url": "http://127.0.0.1:9000", + "media_server_timeout_ms": 3000, + "media_server_retry": { "max_attempts": 3, "backoff_ms": [200, 500] } + } +} +EOF + + chown -R orangepi:orangepi "/var/lib/rk3588-agent" + + echo -e "${GREEN}✓${NC} Agent 安装完成" +} + +# 安装 systemd 服务 +cmd_install_services() { + echo -e "${BLUE}[3/5] 安装 Systemd 服务...${NC}" + + # Media Server 服务 + cat > "$SERVICE_DIR/media-server.service" << EOF +[Unit] +Description=RK3588 Media Server +After=network.target +Wants=network.target + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=$INSTALL_DIR +Environment="LD_LIBRARY_PATH=$INSTALL_DIR/lib:/usr/local/lib" +Environment="RKNPU_LOG_LEVEL=2" +ExecStartPre=/bin/mkdir -p $HLS_DIR $LOGS_DIR $ALARMS_DIR $CLIPS_DIR +ExecStart=$INSTALL_DIR/bin/media-server --config $INSTALL_DIR/etc/media-server.json +Restart=always +RestartSec=5 +StartLimitInterval=60s +StartLimitBurst=3 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=media-server + +# 资源限制 +LimitNOFILE=65536 +LimitNPROC=4096 + +# 优雅关闭 +TimeoutStopSec=30 +KillSignal=SIGTERM + +[Install] +WantedBy=multi-user.target +EOF + + # Agent 服务 + cat > "$SERVICE_DIR/rk3588-agent.service" << EOF +[Unit] +Description=RK3588 Agent Service +After=network.target media-server.service +Wants=media-server.service + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=$AGENT_INSTALL_DIR +ExecStart=$AGENT_INSTALL_DIR/rk3588-agent --config $AGENT_INSTALL_DIR/agent.config.json +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=rk3588-agent + +[Install] +WantedBy=multi-user.target +EOF + + systemctl daemon-reload + + echo -e "${GREEN}✓${NC} Systemd 服务安装完成" +} + +# 主安装流程 +cmd_install() { + echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BLUE}║ RK3588 Media Server 完善部署脚本 ║${NC}" + echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" + echo "" + + # 检查 root 权限 + if [ "$(id -u)" -ne 0 ]; then + echo -e "${RED}错误: 请使用 sudo 运行此脚本${NC}" + exit 1 + fi + + echo "项目目录: $PROJECT_DIR" + echo "安装目录: $INSTALL_DIR" + echo "运行时目录: $RUNTIME_DIR" + echo "" + + # 执行安装步骤 + cmd_install_media_server + cmd_install_agent + create_runtime_dirs + cmd_install_services + install_logrotate + install_hls_cleanup + + # 启动服务 + echo -e "${BLUE}[启动服务]${NC}" + systemctl enable media-server + systemctl start media-server + systemctl enable rk3588-agent + systemctl start rk3588-agent + + echo "" + echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ 部署完成! ║${NC}" + echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "目录结构:" + echo " 程序: $INSTALL_DIR/" + echo " 数据: $RUNTIME_DIR/" + echo " 配置: $INSTALL_DIR/etc/media-server.json" + echo "" + echo "服务管理:" + echo " 状态: sudo systemctl status media-server" + echo " 日志: sudo journalctl -u media-server -f" + echo " 界面: http://$(hostname -I | awk '{print $1}'):9100" + echo "" + echo "HLS播放:" + echo " http://$(hostname -I | awk '{print $1}'):9000/hls_player.html" +} + +# 升级 +cmd_upgrade() { + echo -e "${YELLOW}========== 升级 Media Server ==========${NC}" + + if [ "$(id -u)" -ne 0 ]; then + echo -e "${RED}错误: 请使用 sudo 运行${NC}" + exit 1 + fi + + # 备份配置 + BACKUP_DIR="/root/rk3588-backup-$(date +%Y%m%d%H%M%S)" + mkdir -p "$BACKUP_DIR" + [ -f "$INSTALL_DIR/etc/media-server.json" ] && cp "$INSTALL_DIR/etc/media-server.json" "$BACKUP_DIR/" + echo "配置已备份到: $BACKUP_DIR" + + # 停止服务 + echo "停止服务..." + systemctl stop media-server rk3588-agent + + # 重新安装 + cmd_install + + echo -e "${GREEN}升级完成${NC}" +} + +# 查看状态 +cmd_status() { + echo -e "${BLUE}========== RK3588 Media Server 状态 ==========${NC}" + echo "" + + # 系统信息 + echo -e "${CYAN}[系统信息]${NC}" + echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo " 运行时间: $(uptime -p 2>/dev/null || uptime | awk -F',' '{print $1}')" + echo "" + + # NPU + echo -e "${CYAN}[NPU 负载]${NC}" + if [ -f /proc/rknpu/load ]; then + cat /proc/rknpu/load | sed 's/^/ /' + else + echo " NPU 信息不可用" + fi + echo "" + + # Media Server + echo -e "${CYAN}[Media Server]${NC}" + if systemctl is-active --quiet media-server 2>/dev/null; then + echo -e " 状态: ${GREEN}● 运行中${NC}" + PID=$(systemctl show --property=MainPID --value media-server 2>/dev/null) + echo " PID: $PID" + if [ -n "$PID" ] && [ "$PID" != "0" ]; then + CPU_MEM=$(ps -p $PID -o %cpu,%mem --no-headers 2>/dev/null || echo "N/A") + echo " CPU/MEM: $CPU_MEM" + fi + echo " 端口:" + ss -tlnp 2>/dev/null | grep -E "(9000|8555)" | head -3 | sed 's/^/ /' + else + echo -e " 状态: ${RED}○ 停止${NC}" + fi + echo "" + + # HLS 输出 + echo -e "${CYAN}[HLS 输出]${NC}" + if [ -d "$HLS_DIR" ]; then + CHANNELS=$(find "$HLS_DIR" -maxdepth 1 -type d | wc -l) + CHANNELS=$((CHANNELS - 1)) + echo " 通道数: $CHANNELS" + if [ $CHANNELS -gt 0 ]; then + echo " 分片统计:" + for d in "$HLS_DIR"/*/; do + [ -d "$d" ] || continue + name=$(basename "$d") + count=$(find "$d" -name "*.ts" | wc -l) + size=$(du -sh "$d" 2>/dev/null | cut -f1) + echo " $name: ${count}个分片, ${size}" + done + fi + else + echo " HLS 目录不存在" + fi + echo "" + + # Agent + echo -e "${CYAN}[RK3588 Agent]${NC}" + if systemctl is-active --quiet rk3588-agent 2>/dev/null; then + echo -e " 状态: ${GREEN}● 运行中${NC}" + echo " 管理界面: http://$(hostname -I | awk '{print $1}'):9100" + else + echo -e " 状态: ${RED}○ 停止${NC}" + fi + echo "" + + # 存储使用 + echo -e "${CYAN}[存储使用]${NC}" + echo " HLS 输出: $(du -sh "$HLS_DIR" 2>/dev/null | cut -f1)" + echo " 日志: $(du -sh "$LOGS_DIR" 2>/dev/null | cut -f1)" + echo " 告警图片: $(du -sh "$ALARMS_DIR" 2>/dev/null | cut -f1)" + echo " 告警视频: $(du -sh "$CLIPS_DIR" 2>/dev/null | cut -f1)" + echo "" + + echo "常用命令:" + echo " 查看日志: sudo journalctl -u media-server -f" + echo " HLS清理: sudo $INSTALL_DIR/bin/cleanup-hls.sh" + echo " 实时监控: watch -n 1 cat /proc/rknpu/load" +} + +# 查看日志 +cmd_logs() { + SERVICE="${2:-media-server}" + echo -e "${BLUE}查看 $SERVICE 日志 (按 Ctrl+C 退出)...${NC}" + journalctl -u "$SERVICE" -f +} + +# 清理 HLS +cmd_clean_hls() { + echo -e "${YELLOW}清理 HLS 旧分片...${NC}" + KEEP_DAYS="${2:-3}" + + if [ -f "$INSTALL_DIR/bin/cleanup-hls.sh" ]; then + "$INSTALL_DIR/bin/cleanup-hls.sh" "$KEEP_DAYS" + else + echo "清理脚本不存在" + exit 1 + fi +} + +# 卸载 +cmd_uninstall() { + echo -e "${YELLOW}========== 卸载 RK3588 Media Server ==========${NC}" + + if [ "$(id -u)" -ne 0 ]; then + echo -e "${RED}错误: 请使用 sudo 运行${NC}" + exit 1 + fi + + read -p "确定要卸载? (y/N): " -r + [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && echo "已取消" && exit 0 + + echo "[1/4] 停止服务..." + systemctl stop media-server rk3588-agent 2>/dev/null || true + systemctl disable media-server rk3588-agent 2>/dev/null || true + + echo "[2/4] 删除服务文件..." + rm -f "$SERVICE_DIR/media-server.service" + rm -f "$SERVICE_DIR/rk3588-agent.service" + rm -f "$LOGROTATE_DIR/rk3588-media-server" + rm -f "$LOGROTATE_DIR/rk3588-hls" + rm -f "/etc/cron.d/rk3588-media-server" + systemctl daemon-reload + + echo "[3/4] 备份数据..." + BACKUP_DIR="/root/rk3588-backup-$(date +%Y%m%d%H%M%S)" + mkdir -p "$BACKUP_DIR" + [ -f "$INSTALL_DIR/etc/media-server.json" ] && cp "$INSTALL_DIR/etc/media-server.json" "$BACKUP_DIR/" + [ -d "$HLS_DIR" ] && cp -r "$HLS_DIR" "$BACKUP_DIR/" 2>/dev/null || true + echo "数据已备份到: $BACKUP_DIR" + + echo "[4/4] 删除文件..." + read -p "是否删除所有数据 (包括 HLS/日志/告警)? (y/N): " -r + if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then + rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR" "$RUNTIME_DIR" + echo "所有数据已删除" + else + rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR" + echo "程序已删除,数据保留在: $RUNTIME_DIR" + fi + + echo -e "${GREEN}卸载完成${NC}" +} + +# 主入口 +case "${1:-install}" in + install) + cmd_install + ;; + upgrade) + cmd_upgrade + ;; + status) + cmd_status + ;; + logs) + cmd_logs "$@" + ;; + clean-hls) + cmd_clean_hls "$@" + ;; + uninstall) + cmd_uninstall + ;; + *) + echo "RK3588 Media Server 部署脚本" + echo "" + echo "用法: sudo ./deploy.sh [命令]" + echo "" + echo "命令:" + echo " install 安装/部署服务 (默认)" + echo " upgrade 升级服务 (保留配置和数据)" + echo " status 查看运行状态" + echo " logs 查看日志" + echo " clean-hls 清理 HLS 旧分片" + echo " uninstall 卸载服务" + echo "" + echo "示例:" + echo " sudo ./deploy.sh install" + echo " sudo ./deploy.sh status" + echo " sudo ./deploy.sh clean-hls 7 # 保留最近7天" + exit 1 + ;; +esac