diff --git a/MppDecode.cpp b/MppDecode.cpp index 245e30e..87df68e 100755 --- a/MppDecode.cpp +++ b/MppDecode.cpp @@ -86,10 +86,11 @@ void convert_a_frame(MppFrame frame) printf("failed to allocate a frame"); return; } - base = (RK_U8 *)mpp_buffer_get_ptr(buffer); - rga_client.convert(base, width, height, h_stride, v_stride, out); - // printf("convert a frame ok"); - if (pthread_mutex_trylock(&frame_mutex) == 0) + base = (RK_U8 *)mpp_buffer_get_ptr(buffer); + rga_client.convert(base, width, height, h_stride, v_stride, out); + send_inference_heartbeat_if_needed(); + // printf("convert a frame ok"); + if (pthread_mutex_trylock(&frame_mutex) == 0) { // lock acquired, should create thread and use pose_body diff --git a/README.md b/README.md index 305dacb..aca0410 100644 --- a/README.md +++ b/README.md @@ -179,16 +179,18 @@ h264 分段文件会写到: ## 上报逻辑 -程序向本地管理程序发送: +推理结果业务包向本地管理程序发送: ```text POST http://127.0.0.1:/video/post ``` -JSON 结构保持兼容: +JSON 结构: ```json { + "message_type": "inference_result", + "schema_version": 1, "serial": "camera001", "type": 19, "at": 1778826272, @@ -202,7 +204,59 @@ JSON 结构保持兼容: } ``` -当前版本只主动上传 `number > 0` 的正例。目标未检出时不上传 `number=0`,只写日志。 +字段说明: + +- `message_type` 固定为 `inference_result`,用于和心跳包区分。 +- `schema_version` 当前为 `1`。 +- `serial` 来自启动参数 `-c`,在管理程序中可作为推理进程身份。 +- `type` 来自启动参数 `-t`。 +- `at` 为推理端当前时间戳。 +- `params[].class_idx` 来自模型目录下 `_code.txt`。 +- `params[].name` 来自模型目录下 `.txt`。 +- `params[].number` 为当前帧同一 `class_idx` 的检测数量;多个 label 映射到同一个 `class_idx` 时会合并数量。 + +当前版本只主动上传 `number > 0` 的正例。目标未检出时不上传 `number=0`,只写日志。业务包不会承担心跳职责。 + +## 心跳机制 + +推理程序新增独立心跳接口: + +```text +POST http://127.0.0.1:/heartbeat +``` + +只有启动参数指定了有效 `-p `,并且 `-c ` 非空时,才会发送心跳。当前心跳周期为 `HEARTBEAT_SECS = 60` 秒。 + +心跳包结构: + +```json +{ + "message_type": "heartbeat", + "schema_version": 1, + "serial": "camera001", + "process_uuid": "camera001", + "status": "running", + "timestamp": 1778826272 +} +``` + +字段说明: + +- `message_type` 固定为 `heartbeat`。 +- `schema_version` 当前为 `1`。 +- `serial` 来自启动参数 `-c`,必填且非空。 +- `process_uuid` 同样来自启动参数 `-c`,用于管理程序校验端口对应的推理进程。 +- `status` 当前固定为 `running`。 +- `timestamp` 为推理端当前时间戳。 + +心跳发送点在解码帧完成 RGA 转换之后、`-P/--time-period` 时间段判断之前。因此即使当前时间段不执行识别,只要程序仍在正常读流解码,也会继续发送心跳。 + +管理程序建议按以下规则区分: + +```text +/heartbeat + message_type=heartbeat -> 只刷新心跳,不进入业务判断 +/video/post + message_type=inference_result -> 推理业务结果 +``` ## G1000 截图通知客户端 diff --git a/docs/2026-07-06-heartbeat-and-message-type.md b/docs/2026-07-06-heartbeat-and-message-type.md new file mode 100644 index 0000000..ad45cf7 --- /dev/null +++ b/docs/2026-07-06-heartbeat-and-message-type.md @@ -0,0 +1,91 @@ +# 2026-07-06 修改记录 + +## 推理程序心跳 + +新增独立心跳上报: + +```text +POST http://127.0.0.1:/heartbeat +``` + +心跳只在启动参数指定了有效 `-p `,且 `-c ` 非空时发送。当前发送周期沿用 `HEARTBEAT_SECS = 60`。 + +心跳包结构: + +```json +{ + "message_type": "heartbeat", + "schema_version": 1, + "serial": "", + "process_uuid": "", + "status": "running", + "timestamp": 1778826272 +} +``` + +其中 `serial` 和 `process_uuid` 都来自启动参数 `-c`。例如 `fire_check` 进程应传入 `deviceUUID_fire_check`。 + +心跳发送点放在解码帧完成 RGA 转换之后、时间段判断之前。因此即使 `-P/--time-period` 当前不执行识别,只要推理程序仍在读流解码,也会继续刷新心跳。 + +## 推理结果业务包 + +业务结果仍然走原接口: + +```text +POST http://127.0.0.1:/video/post +``` + +本次只新增顶层区分字段,不改变原有业务字段: + +```json +{ + "message_type": "inference_result", + "schema_version": 1, + "serial": "", + "type": 19, + "at": 1778826272, + "params": [ + { + "class_idx": 1, + "name": "person", + "number": 1 + } + ] +} +``` + +管理程序可按以下规则区分: + +```text +/heartbeat + message_type=heartbeat -> 只刷新心跳 +/video/post + message_type=inference_result -> 业务推理结果 +``` + +## 未修改内容 + +- 未修改识别阈值、workspace 过滤、类别判断、NMS、模型后处理逻辑。 +- 未修改 `/video/post` 中 `serial/type/at/params` 原有字段含义。 +- 未恢复旧的 `number=0` 心跳业务包。 +- 当前推理程序缓存文件仍是 `.h264`,没有改成 `.mp4`。 + +## mp4-merge 代码确认 + +已查看本地 `mp4-merge/` 源码。该项目是 MP4 box 级合并工具,依赖输入文件已有 `moov/mdat/stbl` 等 MP4 容器结构,当前不能直接合并裸 `.h264`。 + +如果要支持 `.h264`,推荐在 `mp4-merge` 命令入口增加预处理: + +```text +.h264 -> ffmpeg 无损封装为临时 .mp4 -> 复用现有 join_files 合并 +``` + +不建议直接在现有 MP4 box 合并逻辑里解析裸 H264。 + +## 构建验证 + +已使用 WSL + Linaro 交叉编译通过,生成产物: + +```text +C:\Users\Smos.DESKTOP-6MT98U8\Desktop\yolov5-inference\build-aarch64\yolov5 +``` + +构建输出为 ARM aarch64 ELF。构建过程中出现 Windows/WSL 时间戳导致的 `Clock skew` warning,但目标文件已成功生成。 diff --git a/rknn.cpp b/rknn.cpp index c845625..f099d98 100644 --- a/rknn.cpp +++ b/rknn.cpp @@ -24,12 +24,14 @@ #include "vertix.h" #include "snapshot_notify.h" #include "myrga.h" +#include "utils.h" #ifdef mkdir #undef mkdir #endif void *upload_info(void *arg); +static void detect_log_line(const char *fmt, ...); typedef struct _upload_target_info_t { @@ -59,7 +61,9 @@ int TypeID = 0; int UploadPort = 0; static char is_uploading = 0; +static char is_heartbeat_uploading = 0; static time_t last_upload = 0; +static time_t last_heartbeat_post = 0; static time_t last_heartbeat_log = 0; static int last_object_count[OBJ_CLASS_MAX] = {0}; static pthread_mutex_t upload_state_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -107,6 +111,80 @@ static void release_upload() pthread_mutex_unlock(&upload_state_mutex); } +static int try_reserve_heartbeat_upload() +{ + int reserved = 0; + pthread_mutex_lock(&upload_state_mutex); + if (is_heartbeat_uploading == 0) + { + is_heartbeat_uploading = 1; + reserved = 1; + } + pthread_mutex_unlock(&upload_state_mutex); + return reserved; +} + +static void release_heartbeat_upload() +{ + pthread_mutex_lock(&upload_state_mutex); + is_heartbeat_uploading = 0; + pthread_mutex_unlock(&upload_state_mutex); +} + +static void *upload_heartbeat_info(void *arg) +{ + (void)arg; + + cJSON *heartbeat = cJSON_CreateObject(); + cJSON_AddStringToObject(heartbeat, "message_type", "heartbeat"); + cJSON_AddNumberToObject(heartbeat, "schema_version", 1); + cJSON_AddStringToObject(heartbeat, "serial", CameraID); + cJSON_AddStringToObject(heartbeat, "process_uuid", CameraID); + cJSON_AddStringToObject(heartbeat, "status", "running"); + cJSON_AddNumberToObject(heartbeat, "timestamp", current_time_stamp()); + + int ret = post_json("127.0.0.1", UploadPort, "localhost", "/heartbeat", heartbeat); + detect_log_line("heartbeat_post camera=%s port=%d result=%d", + CameraID ? CameraID : "", UploadPort, ret); + + cJSON_Delete(heartbeat); + release_heartbeat_upload(); + return NULL; +} + +void send_inference_heartbeat_if_needed() +{ + if (UploadPort <= 0 || CameraID == NULL || CameraID[0] == '\0') + { + return; + } + + time_t now_timestamp; + time(&now_timestamp); + if (now_timestamp - last_heartbeat_post < HEARTBEAT_SECS) + { + return; + } + + if (!try_reserve_heartbeat_upload()) + { + return; + } + + last_heartbeat_post = now_timestamp; + pthread_t uploadth; + if (pthread_create(&uploadth, NULL, upload_heartbeat_info, NULL) == 0) + { + pthread_detach(uploadth); + } + else + { + detect_log_line("heartbeat_thread_create_failed camera=%s port=%d", + CameraID ? CameraID : "", UploadPort); + release_heartbeat_upload(); + } +} + static int is_edge_touch(int left, int top, int right, int bottom) { return left <= 2 || top <= 2 || right >= DSTWidth - 3 || bottom >= DSTHeight - 3; @@ -823,6 +901,8 @@ void *upload_info(void *arg) return NULL; } cJSON *uploadinfo = cJSON_CreateObject(); + cJSON_AddStringToObject(uploadinfo, "message_type", "inference_result"); + cJSON_AddNumberToObject(uploadinfo, "schema_version", 1); cJSON_AddStringToObject(uploadinfo, "serial", CameraID); // 内部事件编码,因为没法表示多个,所以,即将废弃 cJSON_AddNumberToObject(uploadinfo, "type", TypeID); diff --git a/rknn.h b/rknn.h index ddbe4cf..633d889 100644 --- a/rknn.h +++ b/rknn.h @@ -69,6 +69,7 @@ rknn_t *do_init_model(); rknn_t *reload_rknn(char *model_path); */ int process_a_frame(char *buf, int sw, int sh); +void send_inference_heartbeat_if_needed(); /* int post_process_u8(uint8_t *input0, uint8_t *input1, uint8_t *input2, int model_in_h, int model_in_w,