|
43 | 43 | #include "video/detection_result.h" |
44 | 44 | #include "video/api_detection.h" |
45 | 45 | #include "video/motion_detection.h" |
| 46 | +#include "video/onvif_detection.h" |
46 | 47 | #include "video/mp4_writer.h" |
47 | 48 | #include "video/mp4_writer_internal.h" |
48 | 49 | #include "video/mp4_recording.h" |
@@ -120,6 +121,53 @@ static bool is_motion_detection_model(const char *model_path) { |
120 | 121 | return strcmp(model_path, "motion") == 0; |
121 | 122 | } |
122 | 123 |
|
| 124 | +/** |
| 125 | + * Check if a model path indicates ONVIF event-based detection |
| 126 | + * Returns true if the path is exactly "onvif" |
| 127 | + */ |
| 128 | +static bool is_onvif_detection_model(const char *model_path) { |
| 129 | + if (!model_path || model_path[0] == '\0') { |
| 130 | + return false; |
| 131 | + } |
| 132 | + return strcmp(model_path, "onvif") == 0; |
| 133 | +} |
| 134 | + |
| 135 | +/** |
| 136 | + * Derive an ONVIF base URL (http://host) from a stream URL. |
| 137 | + * Strips the scheme, credentials, port, and path, then prepends "http://". |
| 138 | + * Examples: |
| 139 | + * rtsp://admin:pass@192.168.1.100:554/stream → http://192.168.1.100 |
| 140 | + * onvif://192.168.1.100/onvif/device_service → http://192.168.1.100 |
| 141 | + */ |
| 142 | +static void extract_onvif_base_url(const char *stream_url, char *onvif_url, size_t onvif_url_size) { |
| 143 | + if (!stream_url || !onvif_url || onvif_url_size == 0) { |
| 144 | + return; |
| 145 | + } |
| 146 | + onvif_url[0] = '\0'; |
| 147 | + |
| 148 | + /* Skip scheme (rtsp://, onvif://, http://, etc.) */ |
| 149 | + const char *host_start = strstr(stream_url, "://"); |
| 150 | + if (!host_start) { |
| 151 | + snprintf(onvif_url, onvif_url_size, "http://%s", stream_url); |
| 152 | + return; |
| 153 | + } |
| 154 | + host_start += 3; /* skip "://" */ |
| 155 | + |
| 156 | + /* Skip optional credentials (user:pass@) */ |
| 157 | + const char *at_sign = strchr(host_start, '@'); |
| 158 | + if (at_sign) { |
| 159 | + host_start = at_sign + 1; |
| 160 | + } |
| 161 | + |
| 162 | + /* Find end of bare hostname: stop at ':', '/', or NUL */ |
| 163 | + const char *host_end = host_start; |
| 164 | + while (*host_end && *host_end != ':' && *host_end != '/') { |
| 165 | + host_end++; |
| 166 | + } |
| 167 | + |
| 168 | + snprintf(onvif_url, onvif_url_size, "http://%.*s", (int)(host_end - host_start), host_start); |
| 169 | +} |
| 170 | + |
123 | 171 | /** |
124 | 172 | * Initialize the unified detection thread system |
125 | 173 | */ |
@@ -1595,6 +1643,49 @@ static bool run_detection_on_frame(unified_detection_ctx_t *ctx, AVPacket *pkt) |
1595 | 1643 | return mot_triggered; |
1596 | 1644 | } |
1597 | 1645 |
|
| 1646 | + // ONVIF event-based detection — no frame decoding needed |
| 1647 | + if (is_onvif_detection_model(ctx->model_path)) { |
| 1648 | + // Fetch the stream config to obtain the camera URL and ONVIF credentials |
| 1649 | + stream_config_t onvif_cfg; |
| 1650 | + memset(&onvif_cfg, 0, sizeof(onvif_cfg)); |
| 1651 | + if (get_stream_config_by_name(ctx->stream_name, &onvif_cfg) != 0) { |
| 1652 | + log_warn("[%s] ONVIF detection: failed to look up stream config", ctx->stream_name); |
| 1653 | + return false; |
| 1654 | + } |
| 1655 | + |
| 1656 | + // Derive http://host from the stream's RTSP/ONVIF URL |
| 1657 | + char onvif_url[MAX_PATH_LENGTH]; |
| 1658 | + extract_onvif_base_url(onvif_cfg.url, onvif_url, sizeof(onvif_url)); |
| 1659 | + |
| 1660 | + if (onvif_url[0] == '\0') { |
| 1661 | + log_warn("[%s] ONVIF detection: could not derive ONVIF URL from stream URL: %s", |
| 1662 | + ctx->stream_name, onvif_cfg.url); |
| 1663 | + return false; |
| 1664 | + } |
| 1665 | + |
| 1666 | + log_debug("[%s] ONVIF detection: polling events at %s (user=%s)", |
| 1667 | + ctx->stream_name, onvif_url, onvif_cfg.onvif_username); |
| 1668 | + |
| 1669 | + // detect_motion_onvif already handles DB storage, MQTT publish, and |
| 1670 | + // recording trigger internally — do not duplicate those calls here. |
| 1671 | + int onvif_ret = detect_motion_onvif(onvif_url, |
| 1672 | + onvif_cfg.onvif_username, |
| 1673 | + onvif_cfg.onvif_password, |
| 1674 | + &result, |
| 1675 | + ctx->stream_name); |
| 1676 | + if (onvif_ret != 0) { |
| 1677 | + log_warn("[%s] ONVIF detection failed with error %d", ctx->stream_name, onvif_ret); |
| 1678 | + return false; |
| 1679 | + } |
| 1680 | + |
| 1681 | + bool onvif_triggered = (result.count > 0); |
| 1682 | + if (onvif_triggered) { |
| 1683 | + ctx->total_detections += result.count; |
| 1684 | + log_info("[%s] ONVIF motion detected (%d event(s))", ctx->stream_name, result.count); |
| 1685 | + } |
| 1686 | + return onvif_triggered; |
| 1687 | + } |
| 1688 | + |
1598 | 1689 | // Embedded model detection - requires frame decoding |
1599 | 1690 | if (!pkt || !ctx->decoder_ctx) return false; |
1600 | 1691 |
|
|
0 commit comments